diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..327f5fb --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +max-line-length = 88 +per-file-ignores = + # imported but unused + __init__.py: F401 + diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..fccf9ac --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,24 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + pip install -e . + - name: Analysing the code with pylint + run: | + pylint -d C0301,C0103,C0209 --fail-under 8.5 $(git ls-files '*.py') diff --git a/.gitignore b/.gitignore index f86f759..a87e2cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,11 @@ .DS_Store -src/.DS_Store -src/RT_tables/.DS_Store -examples/.DS_Store -examples/materials/.DS_Store -tests/.DS_Store -tests/materials/.DS_Store -src/__pycache__/ -examples/.ipynb_checkpoints +__pycache__/ +*.ipynb_checkpoints +*.egg-info +*.swp examples/WASP52b_dT.csv examples/WASP52b_sigmaT.csv examples/WASP52b_nsig_fit.csv env/ -src/sunbather -*.egg-info \ No newline at end of file +dist/ +src/sunbather/cloudy diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..31dbf0d --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# 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: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0629df0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include src/sunbather * diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# 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) diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..f4f7565 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,8 @@ +API +=== + +.. autosummary:: + :toctree: generated + :recursive: + + sunbather diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..f2d9419 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,43 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import sys +import os +from pathlib import Path + +sys.path.insert(0, str(Path('..', 'src').resolve())) + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "sunbather" +copyright = "2024, Dion Linssen" +author = "Dion Linssen" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx_rtd_theme", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + #"myst_parser", + "myst_nb", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +# html_theme = 'alabaster' +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] + + +# Configuration for Read the Docs +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/") diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..ee8a284 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,92 @@ +# FAQ + +## How do I create Parker wind profiles? + +Add the parameters of the planet/star system to the *$SUNBATHER_PROJECT_PATH/planets.txt* file. Make sure the SED you specify in _planets.txt_ is present in the _$CLOUDY_PATH/data/SED/_ folder in the right format. Then run the `construct_parker.py` module in your terminal (use `-help` to see the arguments). + +## How do I choose the composition of the atmosphere? + +The composition usually has to be specified at two stages: + +1. When creating the Parker wind profiles with the `construct_parker.py` module: You can choose to use a pure H/He composition (which uses `p-winds` standalone) by specifying the hydrogen fraction by number with the `-fH` argument. You can also choose an arbitrary composition that includes metal species (which uses `p-winds` and _Cloudy_ in tandem) with the `-z` and `-zelem` arguments. In this case, `-z` specifies a metallicity relative to solar and is thus a scaling factor for all metal species. `-zelem` can be used to scale the abundance of individual elements, for example as `-zelem Na=3 Mg+=10 Fe+2=0.5 K=0`. Note that `-z` and `-zelem` can be used together and are **multiplicative**. In `construct_parker.py`, the composition only affects the wind structure through the mean molecular weight. Therefore, using `-z` and `-zelem` is only needed for (highly) supersolar metallicities; using `-fH 0.9` will usually suffice for a solar composition atmosphere. + +2. When simulating Parker wind profiles with _Cloudy_ with the `convergeT_parker.py` module: You can specify the composition with the `-z` and `-zelem` arguments as explained under point 1. The default is a solar composition, so `-z 1`. If you want to simulate a pure H/He composition with _Cloudy_, you can pass `-z 0` (and specify the He abundance through `-zelem He=...)`. Contrary to point 1 however, in `convergeT_parker.py`, the metal content directly affects the thermal structure and XUV absorption, so we recommend using `-z 1` even when you only make hydrogen and helium spectra. + +## How do I calculate the transmission spectrum? + +Create the Parker wind profile with `construct_parker.py` and simulate it with _Cloudy_ with `convergeT_parker.py` while making sure you specify for which species you want to save output with the `-save_sp` argument (if unsure, just pass `-save_sp all`). Then, load the _Cloudy_ output in your Python script with the `tools.Sim` class (see FAQ below), and use the `RT.FinFout()` function to make the transit spectrum. At minimum, `RT.FinFout()` expects the `Sim` object, a wavelength array, and a list of species for which to calculate the spectrum. See the _sunbather/examples/fit_helium.ipynb_ notebook for an example. + +## How do I simulate one planet with different stellar SEDs? + +The safest way is to add another entry in the *$SUNBATHER_PROJECT_PATH/planets.txt* file, with the same parameter values, but a different "name" and "SEDname" (the "full name" can be the same). + +Alternatively and more prone to mistakes, the `construct_parker.py` and `convergeT_parker.py` modules also has the `-SEDname` argument which allows you to specify a different name of the SED file without making a new entry in the _planets.txt_ file. In this case, it is **strongly advised** to use a different `-pdir` and `-dir` (that references the SED type) as well. + +## Why do I have to specify a `-pdir` and a `-dir`? + +Generally, for one planet you may want to create Parker wind profiles with different temperatures, mass-loss rates, but also different atmospheric compositions. The `-pdir` and `-dir` correspond to actual folders on your machine. Each folder groups together profiles with different $T$ and $\dot{M}$, so the `-pdir` and `-dir` effectively allow you to separate the profiles by composition. `-pdir` corresponds to the folder where the Parker wind **structure** (i.e. density and velocity as a function of radius) is stored: *$SUNBATHER_PROJECT_PATH/parker_profiles/planetname/pdir/*, and `-dir` corresponds to the folder where the _Cloudy_ simulations of the profiles are stored: *$SUNBATHER_PROJECT_PATH/sims/1D/planetname/dir/*. + +For example, you can make one `-pdir` which stores a grid of $T-\dot{M}$ profiles at a H/He ratio of 90/10, and another which stores a grid of profiles at a ratio of 99/01. The reason that the `-dir` argument is not the exact same as the `-pdir` argument, is that you may want to create your Parker wind structure profile only once (in one `-pdir` folder) but then run it multiple times with _Cloudy_ while changing the abundance of one particular trace element (in multiple `-dir` folders). The latter would usually not really change the atmospheric structure, but could produce a very different spectral feature. + +## How do I read / plot the output of Cloudy in Python? + +The `Sim` class in the `tools.py` module can be used to read in simulations by giving the full path to the simulation. _Cloudy_ output is separated into different output files, which all have the same name but a different extension. The bulk structure of the atmosphere (including temperature and density) is stored in the ".ovr" file. The radiative heating and cooling rates as a function of radius are stored in the ".heat" and ".cool" files. The densities of different energy levels of different atomic/ionic species are stored in the ".den" file. These files are all read in as a Pandas dataframe and can be accessed as follows: + +``` python +import sys +sys.path.append("/path/to/sunbather/src/") +import tools + +mysimulation = tools.Sim(tools.projectpath+"/sims/1D/planetname/dir/parker_T_Mdot/converged") + +#to get the planet parameters of this simulation: +mysimulation.p.R #radius +mysimulation.p.Mstar #mass of host star + +#to get Cloudy output +mysimulation.ovr.alt #radius grid of the following profiles: +mysimulation.ovr.rho #density profile +mysimulation.ovr.Te #temperature profile +mysimulation.ovr.v #velocity profile +mysimulation.cool.ctot #total radiative cooling +mysimulation.den['H[1]'] #density of ground-state atomic hydrogen +mysimulation.den['He[2]'] #density of metastable helium +mysimulation.den['Fe+2[10]'] #density of the tenth energy level of Fe 2+ +``` + +## Can I run a Parker wind profile through Cloudy while using the isothermal temperature profile? + +Yes, you can pass the `-constantT` flag to `convergeT_parker.py` to simulate the Parker wind profile without converging on a nonisothermal temperature structure. This will save a _Cloudy_ simulation called "constantT" and the folder structure works the same way as for converged simulations: you again need to pass a `-dir` where the simulation is saved, and you can in principle use the same directory that you use for converged profiles (but you will need to pass the `-overwrite` flag if the converged nonisothermal simulation already exists - nothing will be overwritten in this case though!). + +## I forgot to specify for which species I want Cloudy output with the `-save_sp` argument. Do I need to run `convergeT_parker.py` again from scratch? + +You can use the `tools.insertden_Cloudy_in()` function to add species to a (converged) Cloudy simulation file and run it again, without having to go through the temperature convergence scheme again. If you want to do this for a grid of Parker wind models, you will have to set up a loop over the correct filepaths yourself. + +## Can I run an atmospheric profile other than an (isothermal) Parker wind? + +You can "trick" the code into running an arbitrary outflow profile by saving your density and velocity profile in the expected file format in the *$SUNBATHER_PROJECT_PATH/parker_profiles/* folder. For example, you can create a simple density and velocity profile in Python: + +``` python +p = tools.Planet('generic_planet') #make sure you add the parameters in planets.txt + +r = np.linspace(1, 10, num=1000) * p.R #in cm +rho = 1e-15 / np.linspace(1, 10, num=1000)**3 #falls with r^3 +v = 5e4 * np.linspace(1, 10, num=1000) #starts at 0.5km/s, increases linearly with r so that Mdot = 4 pi rho v r^2 is constant +mu = np.repeat(np.nan, 1000) #mu is not used by convergeT_parker.py + +print("log(Mdot) =", np.log10(4*np.pi*r[0]**2*rho[0]*v[0])) + +np.savetxt(tools.projectpath+'/parker_profiles/'+p.name+'/geometric/pprof_'+p.name+'_T=0_M=0.000.txt', np.column_stack((r, rho, v, mu)), delimiter='\t') +``` + +You can then solve the temperature structure of this profile with: `python convergeT_parker.py -plname generic_planet -pdir geometric -dir geometric -T 0 -Mdot 0` + +Similarly, you could for example postprocess the density and velocity profile of an _ATES_ simulation (Caldiroli et al. 2021) with _sunbather_ to produce a transmission spectrum. + +## How do I stop the simulation at the Roche radius / choose the maximum radius? + +The `construct_parker.py` module always creates a profile up until 20 $R_p$ and this can only be changed by editing the source code. + +The `convergeT_parker.py` module by default simulates the atmosphere with *Cloudy* up until 8 $R_p$ and this can be changed with the `-altmax` argument. + +The `RT.FinFout()` function by default makes a transit spectrum based on the full *Cloudy* simulation (so up until 8 $R_p$), but you can give an upper boundary in cm with the `cut_at` argument. For example, if you want to include only material up until the planet's Roche radius when making the transit spectrum, it generally doesn't hurt to leave `construct_parker.py` and `convergeT_parker.py` at the default values, and just pass `cut_at=mysimulation.p.Rroche` to `RT.FinFout()` (assuming `mysimulation` is the `tools.Sim` object of your *Cloudy* simulation). diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 0000000..070a9e3 --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,46 @@ +# Glossary +This wiki page is a glossary that provides additional information on various modules/classes/functionalities included in _sunbather_. We also refer to "Hazy", which is the official documentation of _Cloudy_ and can be found in your _$CLOUDY_PATH/docs/_ folder. + + +## The `tools.py` module +This module contains many basic functions and classes that are used by the other _sunbather_ modules, and can also be used when postprocessing/analyzing _sunbather_ output. + +This module is not intended to be run from the command line, but rather imported into other scripts in order to use its functions. + + +## The `RT.py` module +This module contains functions to perform radiative transfer calculations of the planet transmission spectrum. + +This module is not intended to be run from the command line, but rather imported into other scripts in order to use its functions. + + +## The `construct_parker.py` module +This module is used to create Parker wind profiles. The module can make pure H/He profiles, in which case it is basically a wrapper around the [`p-winds` code](https://github.com/ladsantos/p-winds) (dos Santos et al. 2022). The code can however also make Parker wind profiles for an arbitrary composition (e.g. at a given scaled solar metallicity), which is much more computationally expensive, because it then iteratively runs `p-winds` and _Cloudy_. In this mode, _Cloudy_ is used to obtain the mean molecular weight structure of the atmosphere for the given composition, which `p-winds` uses to calculate the density and velocity structure. + +This module is intended to be run from the command line while supplying arguments. Running `python construct_parker.py --help` will give an explanation of each argument. + +Example use: `python construct_parker.py -plname WASP52b -pdir z_10 -T 8000 -Mdot 11.0 -z 10`. This creates a Parker wind profile for the planet WASP52b (must be defined in *planets.txt*) for a temperature of 8000 K, mass-loss rate of 10^11 g s-1 and a 10x solar metallicity composition, and saves the atmospheric structure as a .txt file in *$SUNBATHER_PROJECT_PATH/parker_profiles/WASP52b/z_10/*. + + +## The `convergeT_parker.py` module +This module is used to run Parker wind profiles through _Cloudy_ to (iteratively) solve for a non-isothermal temperature structure. Additionally, the "converged" simulation can then be postprocessed with functionality of the `RT.py` module in order to make transmission spectra. This module is basically a convenience wrapper which sets up the necessary folder structure and input arguments for the `solveT.py` module that actually performs the iterative scheme described in Linssen et al. (2022). + +This module is intended to be run from the command line while supplying arguments. Running `python convergeT_parker.py --help` will give an explanation of each argument. + +Example use: `python convergeT_parker.py -plname HATP11b -pdir fH_0.99 -dir fiducial -T 5000 10000 200 -Mdot 9.0 11.0 0.1 -zelem He=0.1 -cores 4 -save_sp H He Ca+`. This simulates Parker wind models with Cloudy for the planet HATP11b (must be defined in *planets.txt*) for a grid of temperatures between 5000 K and 10000 K in steps of 200 K, mass-loss rates between 10^9 g s-1 and 10^11 g s-1 in steps of 0.1 dex. It looks for the density and velocity structure of these models in the folder *$SUNBATHER_PROJECT_PATH/parker_profiles/HATP11b/fH_0.99/* (so these models have to be created first in that folder using `construct_parker.py`) and saves the _Cloudy_ simulations in the folder *$SUNBATHER_PROJECT_PATH/sims/1D/HATP11b/fiducial/*. It scales the abundance of helium (which is solar by default in _Cloudy_, i.e. ~10% by number) by a factor 0.1 so that it becomes 1% by number. 4 different calculations of the $T$-$\dot{M}$-grid are done in parallel, and the atomic hydrogen, helium and singly ionized calcium output are saved by _Cloudy_, so that afterwards we can use `RT.FinFout()` to make Halpha, metastable helium and Ca II infrared triplet spectra. + + +## The `solveT.py` module +This module contains the iterative scheme described in Linssen et al. (2022) to solve for a non-isothermal temperature structure of a given atmospheric profile. It is called by `convergeT_parker.py`. As long as you're simulating Parker wind profiles (and not some other custom profile), you should be fine using `convergeT_parker.py` instead of this module. + + +## The *\$SUNBATHER_PROJECT_PATH* (or internally: *tools.projectpath*) directory +This is the directory on your machine where all Parker wind profiles and _Cloudy_ simulations are saved. You can choose any location and name you like, as long as it doesn't contain any spaces. The full path to this directory must be set as your `$SUNBATHER_PROJECT_PATH` environmental variable (see installation instructions). The reason _sunbather_ uses a project path is to keep all output from simulations (i.e. user-specific files) separate from the source code. + + +## The _planets.txt_ file +This file stores the bulk parameters of the planets that are simulated. A template of this file is provided in the _sunbather_ base directory, but you must copy it to your _$SUNBATHER_PROJECT_PATH_ in order for it to work. Every time you want to simulate a new planet/star system, you must add a line to this file with its parameters. You can add comments at the end of the line with a # (for example referencing where the values are from). The first column specifies the "name", which is a tag for this system that cannot contain spaces and is used for the `-plname` argument of `construct_parker.py` and `convergeT_parker.py`, as well as for the `tools.Planet` class to access the system parameters in Python. The second column specifies the "full name", which can be any string you like and can be used e.g. when plotting results. The third column is the radius of the planet in Jupiter radii (7.1492e9 cm). The fourth column is the radius of the star in solar radii (6.9634e10 cm). The fifth column is the semi-major axis of the system in AU (1.49597871e13 cm). The sixth column is the mass of the planet in Jupiter masses (1.898e30 g). The seventh column is the mass of the star in solar masses (1.9891e33 g). The eighth column is the transit impact parameter (dimensionless, 0 is across the center of the stellar disk, 1 is grazing the stellar limb). The ninth column is the name of the stellar SED - see "Stellar SED handling" below in this glossary. + + +## Stellar SED handling +When running _sunbather_, the spectral energy distribution (SED) of the host star has to be available to _Cloudy_, which looks for it in its _$CLOUDY_PATH/data/SED/_ folder. Therefore, every SED you want to use has be **copied to that folder, and requires a specific format**: the first column must be wavelengths in units of Å and the second column must be the $\lambda F_{\lambda} = \nu F_{\nu}$ flux **at a distance of 1 AU** in units of erg s-1 cm-2. Additionally, on the first line, after the first flux value, the following keywords must appear: "units angstrom nuFnu". In the */sunbather/stellar_SEDs/* folder, we have provided a few example SEDs in the correct format. Even though _Cloudy_ in principle supports other units, _sunbather_ doesn't, so please stick to the units as described. Normalization of the flux to the planet orbital distance is done automatically by *sunbather* based on the semi-major axis value given in the *planets.txt* file. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..611d013 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,16 @@ +# sunbather documentation + +Welcome to the _sunbather_ docs! On the left side, you\'ll find the +table of contents. + +![Sunbather logo](logo_text.png) + +```{toctree} Table of Contents +:maxdepth: 2 +installation +glossary +faq +api +fit_helium +predict_UV +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..3e00f25 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,29 @@ +# Installing _sunbather_ + +1. Clone _sunbather_ from Github. The code runs entirely in Python. It was developed using Python 3.9.0 and the following packages are prerequisites: `numpy (v1.24.3), pandas (v1.1.4), matplotlib (v3.7.1), scipy (v1.8.0), astropy (v5.3), p-winds (v1.3.4)`. _sunbather_ also succesfully ran with the newest versions (as of Sep. 18, 2023) of these packages. We have however not yet thoroughly tested all of its functionality with these newer versions, so we currently cannot guarantee that it works, but feel free to try! In any case, we recommend making a Python [virtual environment](https://realpython.com/python-virtual-environments-a-primer/) to run _sunbather_ in. +2. Create a directory anywhere on your machine where the code will save all models/simulations/etc. This will be the "project" folder, and you can give it any name you like. This is to keep the output of _sunbather_ separate from the _sunbather_ source code. +3. Set an environmental variable `$CLOUDY_PATH` to your _Cloudy_ installation base directory, and set `$SUNBATHER_PROJECT_PATH` to the "project" folder. We recommend setting these in your _~/.bashrc_ or _~/.zshrc_ file: + ``` + export CLOUDY_PATH="/full/path/to/c23.01/" + export SUNBATHER_PROJECT_PATH="/full/path/to/project/folder/" + ``` +4. Copy the */sunbather/planets.txt* file to your project folder. +5. Copy the stellar spectra from _/sunbather/stellar_SEDs/_ to _$CLOUDY_PATH/data/SED/_ . These include the [MUSCLES](https://archive.stsci.edu/prepds/muscles/) spectra. +6. Test your _sunbather_ installation: run _/sunbather/tests/test.py_, which should print "Success". If the test fails, feel free to open an issue or contact d.c.linssen@uva.nl with your error. + +## Installing _Cloudy_ + +_sunbather_ has been developed and tested with _Cloudy v17.02_ and _v23.01_. Newer versions of _Cloudy_ are likely also compatible with _sunbather_, but this has not been thoroughly tested. Therefore, we currently recommend using _v23.01_. Complete _Cloudy_ download and installation instructions can be found [here](https://gitlab.nublado.org/cloudy/cloudy/-/wikis/home). In short, for most Unix systems, the steps are as follows: + +1. Go to the [v23 download page](https://data.nublado.org/cloudy_releases/c23/) and download the "c23.01.tar.gz" file (or go to the [v17 download page](https://data.nublado.org/cloudy_releases/c17/old/) and download the "c17.02.tar.gz" file). +2. Extract it in a location where you want to install _Cloudy_. +3. `cd` into the _/c23.01/source/_ or _/c17.02/source/_ folder and compile the code by running `make`. +4. Quickly test the _Cloudy_ installation: in the source folder, run `./cloudy.exe`, type "test" and hit return twice. It should print "Cloudy exited OK" at the end. + +If you have trouble installing _Cloudy_, we refer to the download instructions linked above, as well as the _Cloudy_ [help forum](https://cloudyastrophysics.groups.io/g/Main/topics). + + +## Getting started + +1. To get familiar with _sunbather_, we recommend you go through the Jupyter notebooks in the _/sunbather/examples/_ folder, where example use cases (such as creating atmospheric profiles, calculating transmission spectra and fitting observational data) are worked out and explained. +2. For more details on how to use the code, check out the Glossary and FAQ pages on this wiki. We specifically recommend you read the glossary sections "The _planets.txt_ file" and "Stellar SED handling". diff --git a/docs/logo_text.png b/docs/logo_text.png new file mode 120000 index 0000000..7e00e04 --- /dev/null +++ b/docs/logo_text.png @@ -0,0 +1 @@ +../logo/Logo + text.png \ No newline at end of file diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 0000000..3e815de --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,3 @@ +sphinx == 7.1.2 +sphinx-rtd-theme == 3.0.2 +myst_parser == 4.0.0 diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..86716b2 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,133 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --output-file=requirements.txt --strip-extras ../pyproject.toml requirements.in +# +alabaster==0.7.16 + # via sphinx +astropy==6.1.6 + # via + # p-winds + # sunbather (../pyproject.toml) +astropy-iers-data==0.2024.11.18.0.35.2 + # via astropy +babel==2.16.0 + # via sphinx +certifi==2024.8.30 + # via requests +charset-normalizer==3.4.0 + # via requests +contourpy==1.3.1 + # via matplotlib +cycler==0.12.1 + # via matplotlib +docutils==0.20.1 + # via + # myst-parser + # sphinx + # sphinx-rtd-theme +flatstar==0.2.1a0 + # via p-winds +fonttools==4.55.0 + # via matplotlib +idna==3.10 + # via requests +imagesize==1.4.1 + # via sphinx +jinja2==3.1.4 + # via + # myst-parser + # sphinx +kiwisolver==1.4.7 + # via matplotlib +markdown-it-py==3.0.0 + # via + # mdit-py-plugins + # myst-parser +markupsafe==3.0.2 + # via jinja2 +matplotlib==3.9.2 + # via sunbather (../pyproject.toml) +mdit-py-plugins==0.4.2 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +myst-parser==4.0.0 + # via -r requirements.in +numpy==2.1.3 + # via + # astropy + # contourpy + # flatstar + # matplotlib + # p-winds + # pandas + # pyerfa + # scipy + # sunbather (../pyproject.toml) +p-winds==1.4.7 + # via sunbather (../pyproject.toml) +packaging==24.2 + # via + # astropy + # matplotlib + # sphinx +pandas==2.2.3 + # via sunbather (../pyproject.toml) +pillow==11.0.0 + # via + # flatstar + # matplotlib +pyerfa==2.0.1.5 + # via astropy +pygments==2.18.0 + # via sphinx +pyparsing==3.2.0 + # via matplotlib +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2024.2 + # via pandas +pyyaml==6.0.2 + # via + # astropy + # myst-parser +requests==2.32.3 + # via sphinx +scipy==1.13.1 + # via + # p-winds + # sunbather (../pyproject.toml) +six==1.16.0 + # via python-dateutil +snowballstemmer==2.2.0 + # via sphinx +sphinx==7.1.2 + # via + # -r requirements.in + # myst-parser + # sphinx-rtd-theme + # sphinxcontrib-jquery +sphinx-rtd-theme==3.0.2 + # via -r requirements.in +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +tzdata==2024.2 + # via pandas +urllib3==2.2.3 + # via requests diff --git a/examples/fit_helium.ipynb b/examples/fit_helium.ipynb index 34d3fdb..0d9a559 100644 --- a/examples/fit_helium.ipynb +++ b/examples/fit_helium.ipynb @@ -1,22 +1,43 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "41ae9ef8-a389-4246-a783-9eb9f103cfe9", + "metadata": {}, + "source": [ + "# Fit Helium" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c2fa30e-6bfd-47bc-9672-3245cb376f59", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sunbather\n", + "sunbather.tools.get_sunbather_project_path()\n", + "sunbather.firstrun()" + ] + }, { "cell_type": "markdown", "id": "d7370e97", "metadata": {}, "source": [ - "# License\n", - "\n", + "## License\n", "The code in this notebook is free to be used, edited and redistributed by anyone free of charge. Please cite Linssen et al. (2024) when making use of _sunbather_ and/or the code in this notebook." ] }, { "cell_type": "markdown", "id": "90d0f8d2", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ - "# Scientific goal\n", - "\n", + "## Scientific goal\n", "In this notebook, we demonstrate how sunbather can be used to obtain mass-loss rate estimates from spectral observations. Fitting Parker wind models to helium data is a commonly applied method, but it can result in a degeneracy between the free parameters of the Parker wind model: the temperature and the mass-loss rate. Constraints on the mass-loss rate are not very stringent in that case. In this notebook, we follow the approach described in Linssen et al. (2022), to mitigate this. We fit the helium line and combine this with additional constraints on the Parker wind temperature parameter, in order to break the degeneracy and get better constraints on the mass-loss rate. We first do this for a simple case of a spectrum that we generate from random data, which will reproduce the results of Fig. 6 from Linssen et al. (2024). Then, we move on to fitting the observed He 10830 Å signal of TOI-2134 b from Zhang et al. (2023). We will perform fits with both an atmospheric composition that is a hydrogen/helium mixture in the solar ratio, and a 100x solar metallicity atmosphere. This reproduces the results of Fig. 7 from Linssen et al. (2024)." ] }, @@ -25,7 +46,7 @@ "id": "9c3b972d", "metadata": {}, "source": [ - "# Methodology\n", + "## Methodology\n", "\n", "We need to run a grid of Parker wind models with different temperatures ($T_0$) and mass-loss rates ($\\dot{M}$), which can be computationally expensive depending on the size of the grid. Here, we include the commands to run your own grid (as commented out Python lines), but by default this notebook uses the model runs that we have pre-run to save computation time.\n", "\n", @@ -44,120 +65,171 @@ }, { "cell_type": "markdown", - "id": "0da3ff8d", + "id": "3e620f6b-9d14-4796-bef8-adf0ced1ca22", "metadata": {}, "source": [ - "# Preparation\n", + "## Preparation\n", "\n", "To reproduce these results, we assume you have all codes set-up. That is; you have downloaded *sunbather* and installed its dependencies (the Python packages, including *p-winds*). You have installed *Cloudy* and have the path to it stored as the *\\$CLOUDY_PATH* environmental variable. You have created your \"project\" folder, and have the path to it stored as the *\\$SUNBATHER_PROJECT_PATH* environmental variable. These steps are described in more detail in the \"installation\" section of the _sunbather_ wiki.\n", "\n", - "Before *sunbather* can create Parker wind profiles, we need to make sure the parameters of the system are available to the code. The parameters are stored in the *$SUNBATHER_PROJECT_PATH/planets.txt* file. To run the models for the generic hot Neptune mock retrieval, as well as the TOI-2134 b models, you will need to add their parameters.\n", - "\n", - "> **Add the following two lines to _$SUNBATHER_PROJECT_PATH/planets.txt_:**

\n", - "> TOI2134b,TOI-2134 b,0.240,0.709,0.078,0.0287,0.744,0.20,TOI2134.spec #parameters from Zhang et al. (2023)\n", - "
\n", - "> hotNeptune,generic hot Neptune,0.5,1.0,0.05,0.1,1.0,0.,solar.spec\n", - "\n", + "Before *sunbather* can create Parker wind profiles, we need to make sure the parameters of the system are available to the code. The parameters are stored in the *$SUNBATHER_PROJECT_PATH/planets.txt* file. To run the models for the generic hot Neptune mock retrieval, as well as the TOI-2134 b models, you will need to add their parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29bd070a-1891-4325-a53a-7d4467175935", + "metadata": {}, + "outputs": [], + "source": [ + "# Adds two lines to _$SUNBATHER_PROJECT_PATH/planets.txt_:\n", + "import sunbather.tools\n", + "with open(\n", + " f\"{sunbather.tools.get_sunbather_project_path()}/planets.txt\", \"a+\", encoding=\"utf-8\"\n", + ") as planetfile:\n", + " planets = planetfile.read()\n", + " if \"TOI2134b\" not in planets:\n", + " planetfile.write(\n", + " \"TOI2134b,TOI-2134 b,0.240,0.709,0.078,0.0287,0.744,0.20,TOI2134.spec \"\n", + " \"#parameters from Zhang et al. (2023)\\n\"\n", + " )\n", + " if \"hotneptune\" not in planets:\n", + " planetfile.write(\n", + " \"hotNeptune,generic hot Neptune,0.5,1.0,0.05,0.1,1.0,0.,solar.spec\\n\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "0aa89533-96e2-4b80-b884-a64f215815d0", + "metadata": {}, + "source": [ "The last column of the _planets.txt_ file specifies the **name** of the stellar SED that we want to use. The SED with exactly this name must be available to *Cloudy*, so it must be placed in its source folder, specifically: _\\$CLOUDY_PATH/data/SED/_. In the */sunbather/stellar_SEDs/* folder, the solar.spec file is provided, which is combined from Woods et al. (2005) and Rottman (2005). The TOI2134.spec file is also provided, which is downloaded from the online material of Zhang et al. (2023) ( https://iopscience.iop.org/article/10.3847/2041-8213/aced51 ) and put in the format accepted by *sunbather* (see wiki).\n", "> **The only step you need to take here, is make sure these two spectra are in Cloudy's SED folder: \\$CLOUDY_PATH/data/SED/ (so copy them there).**" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "ede7a4e4-8bb3-47b4-b18e-76f97acd3064", + "metadata": {}, + "outputs": [], + "source": [ + "import os.path\n", + "import shutil\n", + "cloudy_sed_folder = f\"{sunbather.tools.get_cloudy_path()}/data/SED\"\n", + "local_sed_folder = f\"{sunbather.tools.get_sunbather_project_path}/stellar_SEDs/\"\n", + "\n", + "for file in [\"TOI2134.spec\", \"solar.spec\"]:\n", + " if not os.path.isfile(f\"{cloudy_sed_folder}/{file}\"):\n", + " shutil.copyfile(\n", + " f\"{local_sed_folder}/{file}\",\n", + " f\"{cloudy_sed_folder}/{file}\"\n", + " )" + ] + }, { "cell_type": "markdown", "id": "84e276fe", "metadata": {}, "source": [ - "# ------------\n", - "\n", - "# Function definitions\n", - "\n", + "## Function definitions\n", "Since this notebook contains quite a lot of functions for performing the various calculations, we have grouped them all together here, so that the rest of the notebook has improved readability. Once the functions are defined, most parts of the analysis are really only one line of code!" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "4d7362e7", "metadata": {}, "outputs": [], "source": [ - "import sys\n", - "sys.path.append('/Users/dion/src/sunbather/src/') #add your path to /sunbather/src/ here\n", - "\n", - "#import sunbather modules\n", - "import tools\n", - "import RT\n", - "\n", - "#import other modules\n", + "import traceback\n", + "import numpy as np\n", "import pandas as pd\n", "from scipy.integrate import trapezoid\n", "from scipy.integrate import cumulative_trapezoid\n", "import matplotlib.pyplot as plt\n", "import matplotlib\n", "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", - "import numpy as np\n", "import scipy.stats as sps\n", - "import traceback\n", "from scipy.optimize import curve_fit\n", - "import spectres #to resample synthetic spectra to the wavelength grid of observations (pip install spectres)\n", + "import spectres # to resample synthetic spectra to the wavelength grid of observations (pip install spectres)\n", "\n", - "#for interactive matplotlib plots\n", - "%matplotlib notebook" + "# import sunbather modules\n", + "import sunbather\n", + "from sunbather import tools\n", + "from sunbather import RT\n", + "\n", + "# for interactive matplotlib plots\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1084514e-cde2-4120-85d8-5075df89e2f1", + "metadata": {}, + "outputs": [], + "source": [ + "projectpath = tools.get_sunbather_project_path()" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "7dd9f6cd", "metadata": {}, "outputs": [], "source": [ - "### Useful functions ###\n", + "# Useful functions\n", + "\n", "\n", "def get_xyz(df):\n", - " '''\n", + " \"\"\"\n", " Takes as input a Pandas DataFrame which has the x-values\n", " as the index, and the y-values as the column names,\n", " and the z-values as the dataframe contents.\n", - " Returns three 1D arrays with the x, y, z values \n", + " Returns three 1D arrays with the x, y, z values\n", " that can e.g. be plotted with matplotlib tricontour.\n", - " '''\n", - " \n", + " \"\"\"\n", + "\n", " stacked_data = df.stack()\n", " stacked_data = stacked_data.dropna()\n", " x_values = stacked_data.index.get_level_values(0).tolist() # x-values\n", " y_values = stacked_data.index.get_level_values(1).tolist() # y-values\n", - " z_values = stacked_data.tolist() # z-values \n", - " \n", + " z_values = stacked_data.tolist() # z-values\n", + "\n", " return x_values, y_values, z_values\n", "\n", "\n", "def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=1000):\n", - " '''\n", + " \"\"\"\n", " Cuts a colormap to a smaller range.\n", " from https://stackoverflow.com/questions/18926031/how-to-extract-a-subset-of-a-colormap-as-a-new-colormap-in-matplotlib\n", - " '''\n", + " \"\"\"\n", "\n", " new_cmap = matplotlib.colors.LinearSegmentedColormap.from_list(\n", - " 'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval),\n", - " cmap(np.linspace(minval, maxval, n)))\n", + " \"trunc({n},{a:.2f},{b:.2f})\".format(n=cmap.name, a=minval, b=maxval),\n", + " cmap(np.linspace(minval, maxval, n)),\n", + " )\n", " return new_cmap\n", "\n", "\n", "def get_array_from_contour(cn, levelnum):\n", - " '''\n", + " \"\"\"\n", " Takes an ax.contour object and returns the 'levelnum'-th contour as an\n", " array of x and y values. You can then extract the min/max values from\n", " such a contour/array.\n", - " '''\n", - " \n", + " \"\"\"\n", + "\n", " lines = []\n", " for line in cn.collections[levelnum].get_paths():\n", " lines.append(line.vertices)\n", "\n", " if lines:\n", - " x = np.concatenate(lines, axis=0)[:,0]\n", - " y = np.concatenate(lines, axis=0)[:,1]\n", + " x = np.concatenate(lines, axis=0)[:, 0]\n", + " y = np.concatenate(lines, axis=0)[:, 1]\n", " else:\n", " print(\"No 1 sigma contour could be found\")\n", " x, y = None, None\n", @@ -165,226 +237,305 @@ " return x, y\n", "\n", "\n", - "### Functions that calculate statistics ###\n", - "\n", - "def calc_chisqs_He10830(Tstrucpath, observed_wavs, observed_ea, observed_sig_ea,\n", - " instrument_R=None,\n", - " T0low=2000, T0up=12000, T0step=100,\n", - " Mdotlow=8, Mdotup=12, Mdotstep=0.05,\n", - " **kwargs):\n", - " '''\n", + "# Functions that calculate statistics\n", + "\n", + "\n", + "def calc_chisqs_He10830(\n", + " Tstrucpath,\n", + " observed_wavs,\n", + " observed_ea,\n", + " observed_sig_ea,\n", + " instrument_R=None,\n", + " T0low=2000,\n", + " T0up=12000,\n", + " T0step=100,\n", + " Mdotlow=8,\n", + " Mdotup=12,\n", + " Mdotstep=0.05,\n", + " **kwargs,\n", + "):\n", + " \"\"\"\n", " Reads in all Parker wind profiles present in the Tstrucpath folder,\n", " calculates the metastable helium line, compares it to the observed\n", " profile and calculates the chi-squared value.\n", " Expects excess absorption and error in units of %.\n", - " '''\n", + " \"\"\"\n", + "\n", + " Mdots = [\n", + " \"%.3f\" % Mdot for Mdot in np.arange(Mdotlow, Mdotup + 1e-5, Mdotstep)\n", + " ] # set up a grid of Mdot\n", + " T0s = [\n", + " \"%i\" % T0 for T0 in np.arange(T0low, T0up + 1e-5, T0step)\n", + " ] # set up a grid of T_0\n", "\n", - " Mdots = [\"%.3f\" % Mdot for Mdot in np.arange(Mdotlow, Mdotup+1e-5, Mdotstep)] #set up a grid of Mdot\n", - " T0s = [\"%i\" % T0 for T0 in np.arange(T0low, T0up+1e-5, T0step)] #set up a grid of T_0\n", - " \n", " chisqs = pd.DataFrame(columns=Mdots, index=T0s, dtype=float)\n", "\n", " for Mdot in Mdots:\n", " for T0 in T0s:\n", " try:\n", - " #read in the converged simulation for this combination of T0 and Mdot by specifying the path\n", - " sim = tools.Sim(Tstrucpath+'parker_'+T0+'_'+Mdot+'/converged')\n", - " #we calculate the model spectrum on a high-resolution grid\n", + " # read in the converged simulation for this combination of T0 and Mdot by specifying the path\n", + " sim = tools.Sim(Tstrucpath + \"parker_\" + T0 + \"_\" + Mdot + \"/converged\")\n", + " # we calculate the model spectrum on a high-resolution grid\n", " highres_wavs = np.linspace(10830, 10836, 100)\n", - " FinFout, lines_found, lines_not_found = RT.FinFout(sim, highres_wavs, 'He', **kwargs)\n", - " #convert to excess absorption in units of %\n", + " FinFout, lines_found, lines_not_found = RT.FinFout(\n", + " sim, highres_wavs, \"He\", **kwargs\n", + " )\n", + " # convert to excess absorption in units of %\n", " model_excess_absorption = (np.max(FinFout) - FinFout) * 100\n", - " #convolve with instrumental resolution, if any was passed\n", + " # convolve with instrumental resolution, if any was passed\n", " if instrument_R is not None:\n", - " model_excess_absorption = RT.convolve_spectrum_R(highres_wavs, model_excess_absorption, instrument_R) #careful - it gets the same variable name\n", - " #resample model to the observed wav points\n", - " model_excess_absorption_ondata = spectres.spectres(observed_wavs, highres_wavs, model_excess_absorption, fill=0., verbose=False)\n", - " #calculate chi squared\n", - " chisq = np.sum(((model_excess_absorption_ondata - observed_ea) / observed_sig_ea)**2)\n", - " #save chisq value in the pandas dataframe\n", + " model_excess_absorption = RT.convolve_spectrum_R(\n", + " highres_wavs, model_excess_absorption, instrument_R\n", + " ) # careful - it gets the same variable name\n", + " # resample model to the observed wav points\n", + " model_excess_absorption_ondata = spectres.spectres(\n", + " observed_wavs,\n", + " highres_wavs,\n", + " model_excess_absorption,\n", + " fill=0.0,\n", + " verbose=False,\n", + " )\n", + " # calculate chi squared\n", + " chisq = np.sum(\n", + " ((model_excess_absorption_ondata - observed_ea) / observed_sig_ea)\n", + " ** 2\n", + " )\n", + " # save chisq value in the pandas dataframe\n", " chisqs.loc[Mdot][T0] = chisq\n", "\n", - " except FileNotFoundError as e: #then this Parker wind model was not calculated\n", + " except (\n", + " FileNotFoundError\n", + " ) as e: # then this Parker wind model was not calculated\n", " pass\n", - " \n", - " except Exception as e: #if something else went wrong\n", + "\n", + " except Exception as e: # if something else went wrong\n", " traceback.print_exc()\n", "\n", " return chisqs\n", "\n", "\n", - "def calc_EWs_He10830(Tstrucpath, \n", - " T0low=2000, T0up=12000, T0step=100,\n", - " Mdotlow=8, Mdotup=12, Mdotstep=0.05,\n", - " **kwargs):\n", - " '''\n", + "def calc_EWs_He10830(\n", + " Tstrucpath,\n", + " T0low=2000,\n", + " T0up=12000,\n", + " T0step=100,\n", + " Mdotlow=8,\n", + " Mdotup=12,\n", + " Mdotstep=0.05,\n", + " **kwargs,\n", + "):\n", + " \"\"\"\n", " Reads in all Parker wind profiles present in the Tstrucpath folder,\n", " calculates the metastable helium line and integrates it to get the EW.\n", - " '''\n", + " \"\"\"\n", + "\n", + " Mdots = [\n", + " \"%.3f\" % Mdot for Mdot in np.arange(Mdotlow, Mdotup + 1e-5, Mdotstep)\n", + " ] # set up a grid of Mdot\n", + " T0s = [\n", + " \"%i\" % T0 for T0 in np.arange(T0low, T0up + 1e-5, T0step)\n", + " ] # set up a grid of T_0\n", "\n", - " Mdots = [\"%.3f\" % Mdot for Mdot in np.arange(Mdotlow, Mdotup+1e-5, Mdotstep)] #set up a grid of Mdot\n", - " T0s = [\"%i\" % T0 for T0 in np.arange(T0low, T0up+1e-5, T0step)] #set up a grid of T_0\n", - " \n", " model_EWs = pd.DataFrame(columns=Mdots, index=T0s, dtype=float)\n", "\n", " for Mdot in Mdots:\n", " for T0 in T0s:\n", " try:\n", - " #read in the converged simulation for this combination of T0 and Mdot by specifying the path\n", - " sim = tools.Sim(Tstrucpath+'parker_'+T0+'_'+Mdot+'/converged')\n", - " #set up the wavelength array in vacuum angstrom units\n", + " # read in the converged simulation for this combination of T0 and Mdot by specifying the path\n", + " sim = tools.Sim(Tstrucpath + \"parker_\" + T0 + \"_\" + Mdot + \"/converged\")\n", + " # set up the wavelength array in vacuum angstrom units\n", " wavs = np.logspace(np.log10(10831), np.log10(10835), num=100)\n", - " #run the radiative transfer (to check if all lines were calculated, print lines_found)\n", - " FinFout, lines_found, lines_not_found = RT.FinFout(sim, wavs, 'He', **kwargs)\n", - " #convert from Fin/Fout to excess absorption - this assumes we reach the continuum somewhere\n", + " # run the radiative transfer (to check if all lines were calculated, print lines_found)\n", + " FinFout, lines_found, lines_not_found = RT.FinFout(\n", + " sim, wavs, \"He\", **kwargs\n", + " )\n", + " # convert from Fin/Fout to excess absorption - this assumes we reach the continuum somewhere\n", " absorption = np.max(FinFout) - FinFout\n", - " #integrate to get EW\n", + " # integrate to get EW\n", " EW = trapezoid(absorption, x=wavs)\n", - " #save EW value in the pandas dataframe\n", + " # save EW value in the pandas dataframe\n", " model_EWs.loc[Mdot][T0] = EW\n", - " \n", - " except FileNotFoundError as e: #then this Parker wind model was not calculated\n", + "\n", + " except (\n", + " FileNotFoundError\n", + " ) as e: # then this Parker wind model was not calculated\n", " pass\n", - " \n", - " except Exception as e: #if something else went wrong\n", + "\n", + " except Exception as e: # if something else went wrong\n", " traceback.print_exc()\n", "\n", " return model_EWs\n", "\n", "\n", "def metaHe_weighted_T(sim):\n", - " '''\n", + " \"\"\"\n", " Calculates the mean temperature and its standard devation of the atmosphere weighted by\n", " the metastable helium number density (Eq. 4 & 5 in Linssen et al. 2022).\n", - " '''\n", - " \n", - " #in Cloudy, not all bins have the same thickness, so we must compensate for that to do a fair weighting\n", - " bin_thickness = np.diff(np.insert(sim.den.depth.values, 0, 0.))\n", - " #the metastable helium state is the second lowest energy level and thus stored in the He[2] column of the density file\n", - " T_He = np.sum(sim.ovr.Te.values * sim.den['He[2]'].values * bin_thickness) / np.sum(sim.den['He[2]'].values * bin_thickness)\n", - " sigmaT = np.sqrt( np.sum(sim.den['He[2]'].values * bin_thickness * (sim.ovr.Te.values - T_He)**2) / np.sum(sim.den['He[2]'].values * bin_thickness) )\n", - " \n", + " \"\"\"\n", + "\n", + " # in Cloudy, not all bins have the same thickness, so we must compensate for that to do a fair weighting\n", + " bin_thickness = np.diff(np.insert(sim.den.depth.values, 0, 0.0))\n", + " # the metastable helium state is the second lowest energy level and thus stored in the He[2] column of the density file\n", + " T_He = np.sum(sim.ovr.Te.values * sim.den[\"He[2]\"].values * bin_thickness) / np.sum(\n", + " sim.den[\"He[2]\"].values * bin_thickness\n", + " )\n", + " sigmaT = np.sqrt(\n", + " np.sum(\n", + " sim.den[\"He[2]\"].values * bin_thickness * (sim.ovr.Te.values - T_He) ** 2\n", + " )\n", + " / np.sum(sim.den[\"He[2]\"].values * bin_thickness)\n", + " )\n", + "\n", " return T_He, sigmaT\n", "\n", "\n", - "def calc_dT_helium(Tstrucpath,\n", - " T0low=2000, T0up=12000, T0step=100,\n", - " Mdotlow=8, Mdotup=12, Mdotstep=0.05):\n", - " '''\n", + "def calc_dT_helium(\n", + " Tstrucpath, T0low=2000, T0up=12000, T0step=100, Mdotlow=8, Mdotup=12, Mdotstep=0.05\n", + "):\n", + " \"\"\"\n", " Reads in all Parker wind profiles present in the Tstrucpath folder,\n", " calculates the mean temperature weighted by the metastable helium density\n", " and its standard deviation. Calculates the temperature difference between\n", - " the isothermal value and the He-weighted value, which can be used as \n", + " the isothermal value and the He-weighted value, which can be used as\n", " a measure of model self-consistency.\n", - " '''\n", - " \n", - " Mdots = [\"%.3f\" % Mdot for Mdot in np.arange(Mdotlow, Mdotup+1e-5, Mdotstep)] #set up a grid of Mdot\n", - " T0s = [\"%i\" % T0 for T0 in np.arange(T0low, T0up+1e-5, T0step)] #set up a grid of T_0\n", + " \"\"\"\n", + "\n", + " Mdots = [\n", + " \"%.3f\" % Mdot for Mdot in np.arange(Mdotlow, Mdotup + 1e-5, Mdotstep)\n", + " ] # set up a grid of Mdot\n", + " T0s = [\n", + " \"%i\" % T0 for T0 in np.arange(T0low, T0up + 1e-5, T0step)\n", + " ] # set up a grid of T_0\n", "\n", - " T_He = pd.DataFrame(columns=Mdots, index=T0s, dtype=float) #stores the weighted T\n", - " sigmaT = T_He.copy() #stores standard deviation of T(r) around T_He\n", - " dT = T_He.copy() #stores T_He - isothermal T\n", + " T_He = pd.DataFrame(columns=Mdots, index=T0s, dtype=float) # stores the weighted T\n", + " sigmaT = T_He.copy() # stores standard deviation of T(r) around T_He\n", + " dT = T_He.copy() # stores T_He - isothermal T\n", "\n", " for Mdot in Mdots:\n", " for T0 in T0s:\n", " try:\n", - " sim = tools.Sim(Tstrucpath+'/parker_'+T0+'_'+Mdot+'/converged') #load the converged simulation\n", - " ClT, ClsigmaT = metaHe_weighted_T(sim) #find the T_He and sigT_He for this simulation\n", + " sim = tools.Sim(\n", + " Tstrucpath + \"/parker_\" + T0 + \"_\" + Mdot + \"/converged\"\n", + " ) # load the converged simulation\n", + " ClT, ClsigmaT = metaHe_weighted_T(\n", + " sim\n", + " ) # find the T_He and sigT_He for this simulation\n", " T_He.loc[Mdot][T0] = ClT\n", " sigmaT.loc[Mdot][T0] = ClsigmaT\n", " dT.loc[Mdot][T0] = ClT - int(T0)\n", "\n", - " except FileNotFoundError: #then this profile was not calculated\n", + " except FileNotFoundError: # then this profile was not calculated\n", " pass\n", - " \n", + "\n", " return dT, sigmaT\n", "\n", "\n", "### Functions that calculate the Bayesian terms ###\n", "\n", + "\n", "def calc_likelihood_resolved(chisq_fit):\n", - " '''\n", + " \"\"\"\n", " Calculates the likelihood of a spectrally resolved fit to data,\n", " based on the chi-squared value.\n", "\n", " See e.g. https://philuttley.github.io/statistical-inference/10-mle_model_fitting/index.html\n", " for how the chi-squared statistic relates to the likelihood.\n", - " '''\n", + " \"\"\"\n", "\n", - " likelihood = np.exp(-chisq_fit.values / 2.)\n", + " likelihood = np.exp(-chisq_fit.values / 2.0)\n", + "\n", + " likelihood = pd.DataFrame(\n", + " columns=chisq_fit.columns.values,\n", + " index=chisq_fit.index.values,\n", + " data=likelihood,\n", + " dtype=float,\n", + " ) # turn into pd.DataFrame\n", "\n", - " likelihood = pd.DataFrame(columns=chisq_fit.columns.values, index=chisq_fit.index.values, \n", - " data=likelihood, dtype=float) #turn into pd.DataFrame\n", - " \n", " return likelihood\n", "\n", "\n", "def calc_likelihood_unresolved(nsig_fit):\n", - " '''\n", + " \"\"\"\n", " Calculates the likelihood of spectrally unresolved data,\n", - " expects a pandas dataframe that stores the number of \n", + " expects a pandas dataframe that stores the number of\n", " errorbars difference between model and data.\n", - " '''\n", + " \"\"\"\n", "\n", " likelihood = sps.norm.pdf(nsig_fit.abs())\n", "\n", - " likelihood = pd.DataFrame(columns=nsig_fit.columns.values, index=nsig_fit.index.values, \n", - " data=likelihood, dtype=float) #turn into pd.DataFrame\n", - " \n", + " likelihood = pd.DataFrame(\n", + " columns=nsig_fit.columns.values,\n", + " index=nsig_fit.index.values,\n", + " data=likelihood,\n", + " dtype=float,\n", + " ) # turn into pd.DataFrame\n", + "\n", " return likelihood\n", "\n", "\n", "def calc_prior(dT, sigmaT):\n", - " '''\n", + " \"\"\"\n", " Calculates a prior based on the model temperature self-consistency.\n", " See Linssen et al. (2022) for details on the choice of prior evaluation.\n", - " '''\n", + " \"\"\"\n", "\n", - " #check that dT and sigmaT are defined on the same T-Mdot grid\n", - " assert np.array_equal(dT.index.values, sigmaT.index.values), \"Different temperature grids.\"\n", - " assert np.array_equal(dT.columns.values, sigmaT.columns.values), \"Different mass-loss rate grids.\"\n", + " # check that dT and sigmaT are defined on the same T-Mdot grid\n", + " assert np.array_equal(\n", + " dT.index.values, sigmaT.index.values\n", + " ), \"Different temperature grids.\"\n", + " assert np.array_equal(\n", + " dT.columns.values, sigmaT.columns.values\n", + " ), \"Different mass-loss rate grids.\"\n", "\n", " T0grid = dT.index.values.astype(float)\n", " Mdotgrid = dT.columns.values.astype(float)\n", "\n", - " #assume prior is a normal distribution around dT=0 with std dev sigmaT\n", + " # assume prior is a normal distribution around dT=0 with std dev sigmaT\n", " number_sigma = dT / sigmaT\n", " prior = sps.norm.pdf(number_sigma)\n", - " #now normalize the prior\n", - " prior[np.isnan(prior)] = 0.\n", + " # now normalize the prior\n", + " prior[np.isnan(prior)] = 0.0\n", " prior_sum = trapezoid(trapezoid(prior, axis=1, x=Mdotgrid), x=T0grid)\n", " prior = prior / prior_sum\n", "\n", - " prior = pd.DataFrame(columns=dT.columns.values, index=dT.index.values, \n", - " data=prior, dtype=float) #turn into pd.DataFrame\n", - " \n", + " prior = pd.DataFrame(\n", + " columns=dT.columns.values, index=dT.index.values, data=prior, dtype=float\n", + " ) # turn into pd.DataFrame\n", + "\n", " return prior\n", "\n", "\n", "def calc_posterior(prior, likelihood):\n", - " '''\n", + " \"\"\"\n", " Combines the prior from the T0-T_He analysis with the likelihoods\n", " from the data fit with Bayes' theorem to calculate the posterior.\n", - " '''\n", + " \"\"\"\n", "\n", " if isinstance(prior, pd.DataFrame):\n", - " #check that the prior and likelihoods are defined on the same T-Mdot grid\n", - " assert np.array_equal(prior.index.values, likelihood.index.values), \"Different temperature grids.\"\n", - " assert np.array_equal(prior.columns.values, likelihood.columns.values), \"Different mass-loss rate grids.\"\n", + " # check that the prior and likelihoods are defined on the same T-Mdot grid\n", + " assert np.array_equal(\n", + " prior.index.values, likelihood.index.values\n", + " ), \"Different temperature grids.\"\n", + " assert np.array_equal(\n", + " prior.columns.values, likelihood.columns.values\n", + " ), \"Different mass-loss rate grids.\"\n", "\n", " T0grid = likelihood.index.values.astype(float)\n", " Mdotgrid = likelihood.columns.values.astype(float)\n", "\n", - " integrand = prior*likelihood\n", - " integrand[np.isnan(integrand)] = 0.\n", + " integrand = prior * likelihood\n", + " integrand[np.isnan(integrand)] = 0.0\n", " evidence = trapezoid(trapezoid(integrand, axis=1, x=Mdotgrid), x=T0grid)\n", " posterior = integrand / evidence\n", "\n", - " posterior = pd.DataFrame(columns=likelihood.columns.values, index=likelihood.index.values, \n", - " data=posterior, dtype=float) #turn into pd.DataFrame\n", + " posterior = pd.DataFrame(\n", + " columns=likelihood.columns.values,\n", + " index=likelihood.index.values,\n", + " data=posterior,\n", + " dtype=float,\n", + " ) # turn into pd.DataFrame\n", "\n", - "\n", - " #calculate marginalized posteriors\n", + " # calculate marginalized posteriors\n", " post_Mdot = trapezoid(posterior.values, axis=0, x=T0grid)\n", " post_T0 = trapezoid(posterior.values, axis=1, x=Mdotgrid)\n", "\n", @@ -392,7 +543,9 @@ " lowsig_Mdot = Mdotgrid[np.argmax(post_Mdot_cum > 0.16)]\n", " mid_Mdot = Mdotgrid[np.argmin(np.abs(post_Mdot_cum - 0.5)) + 1]\n", " upsig_Mdot = Mdotgrid[np.argmax(post_Mdot_cum > 0.84) + 1]\n", - " print(\"Constraints from marginalized posteriors (not necessarily normally distributed!):\")\n", + " print(\n", + " \"Constraints from marginalized posteriors (not necessarily normally distributed!):\"\n", + " )\n", " print(f\"log10(Mdot) = {mid_Mdot} + {upsig_Mdot-mid_Mdot} - {mid_Mdot-lowsig_Mdot}\")\n", "\n", " post_T0_cum = cumulative_trapezoid(post_T0, x=T0grid)\n", @@ -401,45 +554,61 @@ " upsig_T0 = T0grid[np.argmax(post_T0_cum > 0.84) + 1]\n", " print(f\"T0 = {mid_T0} + {upsig_T0-mid_T0} - {mid_T0-lowsig_T0}\")\n", "\n", - "\n", " return posterior\n", "\n", "\n", - "### Functions that plot stuff ###\n", + "# Functions that plot stuff\n", "\n", - "def plot_chisq_fit(chisq_fit,\n", - " dof=None, bounds_T0=None, bounds_Mdot=None,\n", - " title=None, save=None, fig=None, ax=None):\n", - " '''\n", + "\n", + "def plot_chisq_fit(\n", + " chisq_fit,\n", + " dof=None,\n", + " bounds_T0=None,\n", + " bounds_Mdot=None,\n", + " title=None,\n", + " save=None,\n", + " fig=None,\n", + " ax=None,\n", + "):\n", + " \"\"\"\n", " Makes a standard plot of the Parker wind models fitted to the data.\n", " In this case, we fit the resolved line shape, and\n", " the colormap shows the chi squared statistic.\n", - " '''\n", + " \"\"\"\n", "\n", - " pixT = float(chisq_fit.index[1])-float(chisq_fit.index[0]) #T step size\n", - " pixM = float(chisq_fit.columns[1]) - float(chisq_fit.columns[0]) #Mdot step size\n", + " pixT = float(chisq_fit.index[1]) - float(chisq_fit.index[0]) # T step size\n", + " pixM = float(chisq_fit.columns[1]) - float(chisq_fit.columns[0]) # Mdot step size\n", "\n", - " if dof != None: #then we assume you want to plot the reduced chi squared\n", + " if dof != None: # then we assume you want to plot the reduced chi squared\n", " plotted_vals = chisq_fit / dof\n", - " cbar_label = r'Reduced $\\chi ^2$'\n", + " cbar_label = r\"Reduced $\\chi ^2$\"\n", " else:\n", " plotted_vals = chisq_fit\n", - " cbar_label = r'$\\chi ^2$'\n", + " cbar_label = r\"$\\chi ^2$\"\n", "\n", - " \n", " if fig == None and ax == None:\n", " _showplot = True\n", " fig, ax = plt.subplots(1)\n", " else:\n", " _showplot = False\n", "\n", - " im = ax.imshow(plotted_vals.T, cmap='Blues_r', norm=matplotlib.colors.LogNorm(), \n", - " origin='lower', aspect='auto', interpolation='none',\n", - " extent=[float(chisq_fit.index[0])-0.5*pixT, float(chisq_fit.index[-1])+0.5*pixT, \n", - " float(chisq_fit.columns[0])-0.5*pixM, float(chisq_fit.columns[-1])+0.5*pixM])\n", - " ax.set_facecolor('grey')\n", - " ax.set_xlabel(r'$T_0$ [K]')\n", - " ax.set_ylabel(r'log$_{10}$($\\dot{M}$ [g s$^{-1}$])')\n", + " im = ax.imshow(\n", + " plotted_vals.T,\n", + " cmap=\"Blues_r\",\n", + " norm=matplotlib.colors.LogNorm(),\n", + " origin=\"lower\",\n", + " aspect=\"auto\",\n", + " interpolation=\"none\",\n", + " extent=[\n", + " float(chisq_fit.index[0]) - 0.5 * pixT,\n", + " float(chisq_fit.index[-1]) + 0.5 * pixT,\n", + " float(chisq_fit.columns[0]) - 0.5 * pixM,\n", + " float(chisq_fit.columns[-1]) + 0.5 * pixM,\n", + " ],\n", + " )\n", + " ax.set_facecolor(\"grey\")\n", + " ax.set_xlabel(r\"$T_0$ [K]\")\n", + " ax.set_ylabel(r\"log$_{10}$($\\dot{M}$ [g s$^{-1}$])\")\n", " fig.colorbar(im, label=cbar_label)\n", " if title != None:\n", " ax.set_title(title)\n", @@ -448,24 +617,25 @@ " if bounds_Mdot != None:\n", " ax.set_ylim(*bounds_Mdot)\n", " if save != None:\n", - " plt.savefig(save, bbox_inches='tight', dpi=300)\n", + " plt.savefig(save, bbox_inches=\"tight\", dpi=300)\n", " if _showplot:\n", " plt.show()\n", "\n", "\n", - "def plot_nsig_fit(nsig_fit, bounds_T0=None, bounds_Mdot=None,\n", - " title=None, save=None, fig=None, ax=None):\n", - " '''\n", + "def plot_nsig_fit(\n", + " nsig_fit, bounds_T0=None, bounds_Mdot=None, title=None, save=None, fig=None, ax=None\n", + "):\n", + " \"\"\"\n", " Makes a standard plot of the Parker wind models fitted to the data.\n", " In this case, we fit the EW of the model to the EW of the data, and\n", " the colormap simply indicates how many data errorbars difference there is.\n", - " '''\n", - " \n", - " cmap = plt.get_cmap('Blues_r')\n", + " \"\"\"\n", + "\n", + " cmap = plt.get_cmap(\"Blues_r\")\n", " normalize = matplotlib.colors.Normalize(vmin=0, vmax=5)\n", "\n", - " pixT = float(nsig_fit.index[1]) - float(nsig_fit.index[0]) #T step size\n", - " pixM = float(nsig_fit.columns[1]) - float(nsig_fit.columns[0]) #Mdot step size\n", + " pixT = float(nsig_fit.index[1]) - float(nsig_fit.index[0]) # T step size\n", + " pixM = float(nsig_fit.columns[1]) - float(nsig_fit.columns[0]) # Mdot step size\n", "\n", " if fig == None and ax == None:\n", " _showplot = True\n", @@ -473,13 +643,24 @@ " else:\n", " _showplot = False\n", "\n", - " im = ax.imshow(nsig_fit.T.abs(), cmap=cmap, norm=normalize, origin='lower', aspect='auto', interpolation='none',\n", - " extent=[float(nsig_fit.index[0])-0.5*pixT, float(nsig_fit.index[-1])+0.5*pixT, \n", - " float(nsig_fit.columns[0])-0.5*pixM, float(nsig_fit.columns[-1])+0.5*pixM])\n", - " ax.set_facecolor('grey')\n", - " ax.set_xlabel(r'$T_0$ [K]')\n", - " ax.set_ylabel(r'log$_{10}$($\\dot{M}$ [g s$^{-1}$])')\n", - " fig.colorbar(im, label=r'$\\Delta\\sigma$')\n", + " im = ax.imshow(\n", + " nsig_fit.T.abs(),\n", + " cmap=cmap,\n", + " norm=normalize,\n", + " origin=\"lower\",\n", + " aspect=\"auto\",\n", + " interpolation=\"none\",\n", + " extent=[\n", + " float(nsig_fit.index[0]) - 0.5 * pixT,\n", + " float(nsig_fit.index[-1]) + 0.5 * pixT,\n", + " float(nsig_fit.columns[0]) - 0.5 * pixM,\n", + " float(nsig_fit.columns[-1]) + 0.5 * pixM,\n", + " ],\n", + " )\n", + " ax.set_facecolor(\"grey\")\n", + " ax.set_xlabel(r\"$T_0$ [K]\")\n", + " ax.set_ylabel(r\"log$_{10}$($\\dot{M}$ [g s$^{-1}$])\")\n", + " fig.colorbar(im, label=r\"$\\Delta\\sigma$\")\n", " if title != None:\n", " ax.set_title(title)\n", " if bounds_T0 != None:\n", @@ -487,45 +668,62 @@ " if bounds_Mdot != None:\n", " ax.set_ylim(*bounds_Mdot)\n", " if save != None:\n", - " plt.savefig(save, bbox_inches='tight', dpi=300)\n", + " plt.savefig(save, bbox_inches=\"tight\", dpi=300)\n", " if _showplot:\n", " plt.show()\n", "\n", "\n", - "def plot_selfcons(dT, sigmaT,\n", - " bounds_T0=None, bounds_Mdot=None,\n", - " title=None, fig=None, ax=None):\n", - " '''\n", + "def plot_selfcons(\n", + " dT, sigmaT, bounds_T0=None, bounds_Mdot=None, title=None, fig=None, ax=None\n", + "):\n", + " \"\"\"\n", " Makes a standard plot of the self-consistency of the Parker wind parameter space.\n", " Self-consistent models are white, while blue/red models indicate models for which\n", " Cloudy indicates that the temperature in the helium line-forming region is\n", " cooler/hotter than that assumed in creating the isothermal profiles, respectively.\n", - " The dotted lines indicate 1 standard deviation discrepancy. \n", - " '''\n", - " \n", - " cmap = truncate_colormap(plt.get_cmap('seismic'), 0.2, 0.8)\n", + " The dotted lines indicate 1 standard deviation discrepancy.\n", + " \"\"\"\n", + "\n", + " cmap = truncate_colormap(plt.get_cmap(\"seismic\"), 0.2, 0.8)\n", " normalize = matplotlib.colors.Normalize(vmin=-4000, vmax=4000)\n", "\n", - " pixT = float(dT.index[1])-float(dT.index[0]) #T step size\n", - " pixM = float(dT.columns[1]) - float(dT.columns[0]) #Mdot step size\n", + " pixT = float(dT.index[1]) - float(dT.index[0]) # T step size\n", + " pixM = float(dT.columns[1]) - float(dT.columns[0]) # Mdot step size\n", "\n", - " \n", " if fig == None and ax == None:\n", " _showplot = True\n", " fig, ax = plt.subplots(1)\n", " else:\n", " _showplot = False\n", "\n", - " im = ax.imshow(dT.T, cmap=cmap, norm=normalize, origin='lower', aspect='auto', interpolation='none',\n", - " extent=[float(dT.index[0])-0.5*pixT, float(dT.index[-1])+0.5*pixT, \n", - " float(dT.columns[0])-0.5*pixM, float(dT.columns[-1])+0.5*pixM])\n", - " #plot the 1 sigma line (i.e. dT = sigmaT)\n", - " sig1lines = ax.contour(dT.index.astype(float), dT.columns.astype(float), (dT / sigmaT).T, \n", - " levels=[-1,1], zorder=1, colors='black', linestyles='dotted')\n", - " ax.set_facecolor('grey')\n", - " ax.set_xlabel(r'$T_0$ [K]')\n", - " ax.set_ylabel(r'log$_{10}$($\\dot{M}$ [g s$^{-1}$])')\n", - " fig.colorbar(im, label=r'$T_{He} - T_0$ [K]')\n", + " im = ax.imshow(\n", + " dT.T,\n", + " cmap=cmap,\n", + " norm=normalize,\n", + " origin=\"lower\",\n", + " aspect=\"auto\",\n", + " interpolation=\"none\",\n", + " extent=[\n", + " float(dT.index[0]) - 0.5 * pixT,\n", + " float(dT.index[-1]) + 0.5 * pixT,\n", + " float(dT.columns[0]) - 0.5 * pixM,\n", + " float(dT.columns[-1]) + 0.5 * pixM,\n", + " ],\n", + " )\n", + " # plot the 1 sigma line (i.e. dT = sigmaT)\n", + " sig1lines = ax.contour(\n", + " dT.index.astype(float),\n", + " dT.columns.astype(float),\n", + " (dT / sigmaT).T,\n", + " levels=[-1, 1],\n", + " zorder=1,\n", + " colors=\"black\",\n", + " linestyles=\"dotted\",\n", + " )\n", + " ax.set_facecolor(\"grey\")\n", + " ax.set_xlabel(r\"$T_0$ [K]\")\n", + " ax.set_ylabel(r\"log$_{10}$($\\dot{M}$ [g s$^{-1}$])\")\n", + " fig.colorbar(im, label=r\"$T_{He} - T_0$ [K]\")\n", " if title != None:\n", " ax.set_title(title)\n", " if bounds_T0 != None:\n", @@ -536,22 +734,22 @@ " plt.show()\n", "\n", "\n", - "def plot_posterior(posterior,\n", - " bounds_T0=None, bounds_Mdot=None,\n", - " title=None, save=None, log=False):\n", - " '''\n", + "def plot_posterior(\n", + " posterior, bounds_T0=None, bounds_Mdot=None, title=None, save=None, log=False\n", + "):\n", + " \"\"\"\n", " Plots the posterior distribution and the marginalized distributions.\n", " Prints the 1 sigma confidence intervals, at the precision of the T0/Mdot-grid.\n", - " '''\n", + " \"\"\"\n", "\n", " T0grid = posterior.index.values.astype(float)\n", " Mdotgrid = posterior.columns.values.astype(float)\n", " post = posterior.values\n", - " \n", - " pixT = np.diff(T0grid)[0] #T step size\n", - " pixM = np.diff(Mdotgrid)[0] #Mdot step size\n", "\n", - " #calculate marginalized posteriors\n", + " pixT = np.diff(T0grid)[0] # T step size\n", + " pixM = np.diff(Mdotgrid)[0] # Mdot step size\n", + "\n", + " # calculate marginalized posteriors\n", " post_Mdot = trapezoid(post, axis=0, x=T0grid)\n", " post_T0 = trapezoid(post, axis=1, x=Mdotgrid)\n", "\n", @@ -565,40 +763,57 @@ " mid_T0 = T0grid[np.argmin(np.abs(post_T0_cum - 0.5)) + 1]\n", " upsig_T0 = T0grid[np.argmax(post_T0_cum > 0.84) + 1]\n", "\n", - "\n", " fig = plt.figure(figsize=(5, 5))\n", - " gs = fig.add_gridspec(2, 2, width_ratios=(3, 1), height_ratios=(1, 3),\n", - " left=0.1, right=0.9, bottom=0.1, top=0.9,\n", - " wspace=0.05, hspace=0.05)\n", + " gs = fig.add_gridspec(\n", + " 2,\n", + " 2,\n", + " width_ratios=(3, 1),\n", + " height_ratios=(1, 3),\n", + " left=0.1,\n", + " right=0.9,\n", + " bottom=0.1,\n", + " top=0.9,\n", + " wspace=0.05,\n", + " hspace=0.05,\n", + " )\n", " ax = fig.add_subplot(gs[1, 0])\n", " ax_T0 = fig.add_subplot(gs[0, 0])\n", " ax_Mdot = fig.add_subplot(gs[1, 1])\n", "\n", - " im = ax.imshow(post.T, cmap=plt.get_cmap('Greys'), origin='lower', aspect='auto', interpolation='none',\n", - " extent=[T0grid[0]-0.5*pixT, T0grid[-1]+0.5*pixT, \n", - " Mdotgrid[0]-0.5*pixM, Mdotgrid[-1]+0.5*pixM])\n", + " im = ax.imshow(\n", + " post.T,\n", + " cmap=plt.get_cmap(\"Greys\"),\n", + " origin=\"lower\",\n", + " aspect=\"auto\",\n", + " interpolation=\"none\",\n", + " extent=[\n", + " T0grid[0] - 0.5 * pixT,\n", + " T0grid[-1] + 0.5 * pixT,\n", + " Mdotgrid[0] - 0.5 * pixM,\n", + " Mdotgrid[-1] + 0.5 * pixM,\n", + " ],\n", + " )\n", " if log:\n", " im.set_norm(matplotlib.colors.LogNorm())\n", - " ax_T0.plot(T0grid, post_T0, color='k')\n", - " ax_Mdot.plot(post_Mdot, Mdotgrid, color='k')\n", - "\n", + " ax_T0.plot(T0grid, post_T0, color=\"k\")\n", + " ax_Mdot.plot(post_Mdot, Mdotgrid, color=\"k\")\n", "\n", - " ax_T0.axvline(mid_T0, color='blue', linewidth=0.7)\n", - " ax_T0.axvline(lowsig_T0, color='blue', linewidth=0.7, linestyle='dotted')\n", - " ax_T0.axvline(upsig_T0, color='blue', linewidth=0.7, linestyle='dotted')\n", - " ax_Mdot.axhline(mid_Mdot, color='blue', linewidth=0.7)\n", - " ax_Mdot.axhline(lowsig_Mdot, color='blue', linewidth=0.7, linestyle='dotted')\n", - " ax_Mdot.axhline(upsig_Mdot, color='blue', linewidth=0.7, linestyle='dotted')\n", + " ax_T0.axvline(mid_T0, color=\"blue\", linewidth=0.7)\n", + " ax_T0.axvline(lowsig_T0, color=\"blue\", linewidth=0.7, linestyle=\"dotted\")\n", + " ax_T0.axvline(upsig_T0, color=\"blue\", linewidth=0.7, linestyle=\"dotted\")\n", + " ax_Mdot.axhline(mid_Mdot, color=\"blue\", linewidth=0.7)\n", + " ax_Mdot.axhline(lowsig_Mdot, color=\"blue\", linewidth=0.7, linestyle=\"dotted\")\n", + " ax_Mdot.axhline(upsig_Mdot, color=\"blue\", linewidth=0.7, linestyle=\"dotted\")\n", "\n", " ax_T0.set_xticks([])\n", " ax_T0.set_yticks([])\n", " ax_Mdot.set_xticks([])\n", " ax_Mdot.set_yticks([])\n", "\n", - " ax.set_xlim(T0grid[0]-0.5*pixT, T0grid[-1]+0.5*pixT)\n", - " ax_T0.set_xlim(T0grid[0]-0.5*pixT, T0grid[-1]+0.5*pixT)\n", - " ax.set_ylim(Mdotgrid[0]-0.5*pixM, Mdotgrid[-1]+0.5*pixM)\n", - " ax_Mdot.set_ylim(Mdotgrid[0]-0.5*pixM, Mdotgrid[-1]+0.5*pixM)\n", + " ax.set_xlim(T0grid[0] - 0.5 * pixT, T0grid[-1] + 0.5 * pixT)\n", + " ax_T0.set_xlim(T0grid[0] - 0.5 * pixT, T0grid[-1] + 0.5 * pixT)\n", + " ax.set_ylim(Mdotgrid[0] - 0.5 * pixM, Mdotgrid[-1] + 0.5 * pixM)\n", + " ax_Mdot.set_ylim(Mdotgrid[0] - 0.5 * pixM, Mdotgrid[-1] + 0.5 * pixM)\n", " if bounds_T0 != None:\n", " ax_T0.set_xlim(*bounds_T0)\n", " ax.set_xlim(*bounds_T0)\n", @@ -606,113 +821,153 @@ " ax_Mdot.set_ylim(*bounds_Mdot)\n", " ax.set_ylim(*bounds_Mdot)\n", "\n", - " ax.set_xlabel(r'$T_0$ [K]')\n", - " ax.set_ylabel(r'log$_{10}$($\\dot{M}$ [g s$^{-1}$])')\n", + " ax.set_xlabel(r\"$T_0$ [K]\")\n", + " ax.set_ylabel(r\"log$_{10}$($\\dot{M}$ [g s$^{-1}$])\")\n", " if title != None:\n", " ax_T0.set_title(title)\n", "\n", " if save != None:\n", - " plt.savefig(save, bbox_inches='tight', dpi=300)\n", + " plt.savefig(save, bbox_inches=\"tight\", dpi=300)\n", " plt.show()\n", "\n", "\n", - "def plot_joint_constraint_resolved(dT, sigmaT, chisq_fit, posterior, \n", - " bounds_T0=None, bounds_Mdot=None, \n", - " title=None, save=None, fig=None, ax=None, \n", - " post_uptosigma=1, cmap_T=None,\n", - " cmap_fit=None, show_colorbar=True):\n", - " '''\n", - " Makes a standard plot of the posterior distribution, \n", + "def plot_joint_constraint_resolved(\n", + " dT,\n", + " sigmaT,\n", + " chisq_fit,\n", + " posterior,\n", + " bounds_T0=None,\n", + " bounds_Mdot=None,\n", + " title=None,\n", + " save=None,\n", + " fig=None,\n", + " ax=None,\n", + " post_uptosigma=1,\n", + " cmap_T=None,\n", + " cmap_fit=None,\n", + " show_colorbar=True,\n", + "):\n", + " \"\"\"\n", + " Makes a standard plot of the posterior distribution,\n", " on top of the prior (self-consistency) and likelihood (data fit) constraints.\n", " Also prints out the 1sigma bounds on T and Mdot.\n", "\n", " chisq_fit must be given, but dT, sigmaT and posterior can be passed as None\n", " if they should not be plotted.\n", - " '''\n", + " \"\"\"\n", "\n", - " assert post_uptosigma <=3, \"Maximum value of post_uptosigma is 3\"\n", + " assert post_uptosigma <= 3, \"Maximum value of post_uptosigma is 3\"\n", "\n", " if cmap_T is None:\n", - " cmap_T = truncate_colormap(plt.get_cmap('autumn'), 0.35, 0.65)\n", + " cmap_T = truncate_colormap(plt.get_cmap(\"autumn\"), 0.35, 0.65)\n", " if cmap_fit is None:\n", - " cmap_fit = truncate_colormap(plt.get_cmap('winter_r'), 0.35, 0.65)\n", - " \n", + " cmap_fit = truncate_colormap(plt.get_cmap(\"winter_r\"), 0.35, 0.65)\n", "\n", " if fig == None and ax == None:\n", " showplot = True\n", " fig, ax = plt.subplots(1)\n", " else:\n", " showplot = False\n", - " \n", - " #### Plot the model temperature discrepancy (i.e. prior)\n", + "\n", + " # Plot the model temperature discrepancy (i.e. prior)\n", " if dT is not None and sigmaT is not None:\n", " nsig_T = dT / sigmaT\n", "\n", " x, y, z = get_xyz(nsig_T)\n", "\n", - " ax.tricontour(x, y, z, cmap=cmap_T, levels=[0], zorder=0) #plots the orange line\n", - " prior_contour = ax.tricontourf(x, y, np.abs(z), cmap=cmap_T, levels=[0,1,2], alpha=0.5, zorder=0) #plots the orange contours\n", - " \n", - " \n", - " #### Plot the fit constraints (i.e. likelihood):\n", + " ax.tricontour(\n", + " x, y, z, cmap=cmap_T, levels=[0], zorder=0\n", + " ) # plots the orange line\n", + " prior_contour = ax.tricontourf(\n", + " x, y, np.abs(z), cmap=cmap_T, levels=[0, 1, 2], alpha=0.5, zorder=0\n", + " ) # plots the orange contours\n", + "\n", + " # Plot the fit constraints (i.e. likelihood):\n", " likelihood = calc_likelihood_resolved(chisq_fit)\n", - " likelihood = likelihood.fillna(0.)\n", + " likelihood = likelihood.fillna(0.0)\n", " highest_likelihood = likelihood.max().max()\n", - " sigma1_likelihood = highest_likelihood * np.exp(-1/2)\n", - " sigma2_likelihood = highest_likelihood * np.exp(-4/2)\n", - " sigma3_likelihood = highest_likelihood * np.exp(-9/2)\n", - " \n", + " sigma1_likelihood = highest_likelihood * np.exp(-1 / 2)\n", + " sigma2_likelihood = highest_likelihood * np.exp(-4 / 2)\n", + " sigma3_likelihood = highest_likelihood * np.exp(-9 / 2)\n", + "\n", " x, y, z = get_xyz(likelihood)\n", - " \n", - " likelihood_contour = ax.tricontourf(x, y, z, cmap=cmap_fit, levels=[sigma2_likelihood, sigma1_likelihood, highest_likelihood], alpha=0.5, zorder=1) #plots the blue contours\n", - " \n", - " \n", - " #### Plot the posterior\n", + "\n", + " likelihood_contour = ax.tricontourf(\n", + " x,\n", + " y,\n", + " z,\n", + " cmap=cmap_fit,\n", + " levels=[sigma2_likelihood, sigma1_likelihood, highest_likelihood],\n", + " alpha=0.5,\n", + " zorder=1,\n", + " ) # plots the blue contours\n", + "\n", + " # Plot the posterior\n", " if posterior is not None and post_uptosigma > 0:\n", - " #calculate the posterior values that corresponds to the 1,2,3 sigma contours\n", + " # calculate the posterior values that corresponds to the 1,2,3 sigma contours\n", " posterior_sorted = np.sort(posterior.values.flatten())[::-1]\n", " posterior_sum = np.sum(posterior_sorted)\n", " posterior_cumsum = np.cumsum(posterior_sorted)\n", " p1sigma = posterior_sorted[posterior_cumsum > 0.3935 * posterior_sum][0]\n", " p2sigma = posterior_sorted[posterior_cumsum > 0.8647 * posterior_sum][0]\n", " p3sigma = posterior_sorted[posterior_cumsum > 0.9889 * posterior_sum][0]\n", - " post_levels = [p3sigma, p2sigma, p1sigma][3-post_uptosigma:]\n", - " post_linestyles = ['dotted', 'dashed', 'solid'][3-post_uptosigma:]\n", + " post_levels = [p3sigma, p2sigma, p1sigma][3 - post_uptosigma :]\n", + " post_linestyles = [\"dotted\", \"dashed\", \"solid\"][3 - post_uptosigma :]\n", "\n", " x, y, z = get_xyz(posterior)\n", - " \n", - " #plot the joint posterior 1/2/3 sigma lines:\n", - " cn_post = ax.tricontour(x, y, z, levels=post_levels, linestyles=post_linestyles, colors='black', zorder=2)\n", - " \n", - " #print the posterior 1sigma credible intervals:\n", - " pT0, pMdot = get_array_from_contour(cn_post, -1) #get last level (=1 sigma) from posterior\n", + "\n", + " # plot the joint posterior 1/2/3 sigma lines:\n", + " cn_post = ax.tricontour(\n", + " x,\n", + " y,\n", + " z,\n", + " levels=post_levels,\n", + " linestyles=post_linestyles,\n", + " colors=\"black\",\n", + " zorder=2,\n", + " )\n", + "\n", + " # print the posterior 1sigma credible intervals:\n", + " pT0, pMdot = get_array_from_contour(\n", + " cn_post, -1\n", + " ) # get last level (=1 sigma) from posterior\n", " if pT0 is not None and pMdot is not None:\n", - " bestT0_index, bestMdot_index = np.unravel_index(np.nanargmax(posterior), posterior.shape)\n", - " bestT0, bestMdot = float(posterior.index[bestT0_index]), float(posterior.columns[bestMdot_index]) #T, Mdot of the maximum of the posterior\n", - " max_pMdot, min_pMdot, max_pT0, min_pT0 = np.max(pMdot), np.min(pMdot), np.max(pT0), np.min(pT0) #\n", - " print(f\"1 sigma constraints:\\nlog10(Mdot) = {bestMdot} + {max_pMdot-bestMdot} - {bestMdot-min_pMdot}\")\n", + " bestT0_index, bestMdot_index = np.unravel_index(\n", + " np.nanargmax(posterior), posterior.shape\n", + " )\n", + " bestT0, bestMdot = float(posterior.index[bestT0_index]), float(\n", + " posterior.columns[bestMdot_index]\n", + " ) # T, Mdot of the maximum of the posterior\n", + " max_pMdot, min_pMdot, max_pT0, min_pT0 = (\n", + " np.max(pMdot),\n", + " np.min(pMdot),\n", + " np.max(pT0),\n", + " np.min(pT0),\n", + " ) #\n", + " print(\n", + " f\"1 sigma constraints:\\nlog10(Mdot) = {bestMdot} + {max_pMdot-bestMdot} - {bestMdot-min_pMdot}\"\n", + " )\n", " print(f\"T0 = {bestT0} + {max_pT0-bestT0} - {bestT0-min_pT0}\")\n", - " \n", - " \n", + "\n", " if show_colorbar:\n", " divider = make_axes_locatable(ax)\n", - " \n", + "\n", " if dT is not None and sigmaT is not None:\n", - " #plot the prior colorbar\n", + " # plot the prior colorbar\n", " cax1 = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", " cbar1 = fig.colorbar(prior_contour, cax=cax1)\n", - " cax1.yaxis.set_major_locator(plt.NullLocator()) #turn the ticks for cbar1 off\n", - " \n", - " #plot the likelihood colorbar \n", + " cax1.yaxis.set_major_locator(\n", + " plt.NullLocator()\n", + " ) # turn the ticks for cbar1 off\n", + "\n", + " # plot the likelihood colorbar\n", " cax2 = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", " cbar2 = fig.colorbar(likelihood_contour, cax=cax2)\n", - " cax2.set_yticklabels([r'2$\\sigma$', r'1$\\sigma$', r'0$\\sigma$'])\n", - " cax2.invert_yaxis() #because the p-values and sigma values are in opposite direction\n", + " cax2.set_yticklabels([r\"2$\\sigma$\", r\"1$\\sigma$\", r\"0$\\sigma$\"])\n", + " cax2.invert_yaxis() # because the p-values and sigma values are in opposite direction\n", "\n", - " \n", - " \n", - " ax.set_xlabel(r'$T_0$ [K]')\n", - " ax.set_ylabel(r'log$_{10}$($\\dot{M}$ [g s$^{-1}$])')\n", + " ax.set_xlabel(r\"$T_0$ [K]\")\n", + " ax.set_ylabel(r\"log$_{10}$($\\dot{M}$ [g s$^{-1}$])\")\n", " if bounds_T0:\n", " ax.set_xlim(*bounds_T0)\n", " if bounds_Mdot:\n", @@ -721,97 +976,133 @@ " if title != None:\n", " ax.set_title(title)\n", " if save != None:\n", - " plt.savefig(save, bbox_inches='tight', dpi=300)\n", + " plt.savefig(save, bbox_inches=\"tight\", dpi=300)\n", " if showplot:\n", " plt.show()\n", "\n", "\n", - "def plot_joint_constraint_unresolved(dT, sigmaT, nsig_fit, posterior, \n", - " bounds_T0=None, bounds_Mdot=None, \n", - " title=None, save=None, \n", - " fig=None, ax=None, cmap_T=None,\n", - " cmap_fit=None, post_uptosigma=1,\n", - " show_colorbar=True):\n", - " '''\n", - " Makes a plot of the 1sigma posterior contour \n", + "def plot_joint_constraint_unresolved(\n", + " dT,\n", + " sigmaT,\n", + " nsig_fit,\n", + " posterior,\n", + " bounds_T0=None,\n", + " bounds_Mdot=None,\n", + " title=None,\n", + " save=None,\n", + " fig=None,\n", + " ax=None,\n", + " cmap_T=None,\n", + " cmap_fit=None,\n", + " post_uptosigma=1,\n", + " show_colorbar=True,\n", + "):\n", + " \"\"\"\n", + " Makes a plot of the 1sigma posterior contour\n", " on top of the prior (self-consistency) and likelihood (data fit) constraints.\n", " Also prints out the 1sigma bounds on T and Mdot, which are calculated\n", " based on the matplotlib contour, and thus involve interpolation to\n", " higher precision than the data itself.\n", - " '''\n", + " \"\"\"\n", "\n", - " assert post_uptosigma <=3, \"Maximum value of post_uptosigma is 3\"\n", + " assert post_uptosigma <= 3, \"Maximum value of post_uptosigma is 3\"\n", "\n", " if cmap_T is None:\n", - " cmap_T = truncate_colormap(plt.get_cmap('autumn'), 0.35, 0.65)\n", + " cmap_T = truncate_colormap(plt.get_cmap(\"autumn\"), 0.35, 0.65)\n", " if cmap_fit is None:\n", - " cmap_fit = truncate_colormap(plt.get_cmap('winter'), 0.35, 0.65)\n", + " cmap_fit = truncate_colormap(plt.get_cmap(\"winter\"), 0.35, 0.65)\n", "\n", - " \n", " if fig == None and ax == None:\n", " _showplot = True\n", " fig, ax = plt.subplots(1)\n", " else:\n", " _showplot = False\n", - " \n", - " #### Plot the model temperature discrepancy (i.e. prior)\n", + "\n", + " # Plot the model temperature discrepancy (i.e. prior)\n", " if dT is not None and sigmaT is not None:\n", " nsig_T = dT / sigmaT\n", "\n", " x, y, z = get_xyz(nsig_T)\n", "\n", - " ax.tricontour(x, y, z, cmap=cmap_T, levels=[0], zorder=0) #plots the orange line\n", - " prior_contour = ax.tricontourf(x, y, np.abs(z), cmap=cmap_T, levels=[0,1,2], alpha=0.5, zorder=0) #plots the orange contours\n", - " \n", - " #### Plot the fit constraints (i.e. likelihood):\n", + " ax.tricontour(\n", + " x, y, z, cmap=cmap_T, levels=[0], zorder=0\n", + " ) # plots the orange line\n", + " prior_contour = ax.tricontourf(\n", + " x, y, np.abs(z), cmap=cmap_T, levels=[0, 1, 2], alpha=0.5, zorder=0\n", + " ) # plots the orange contours\n", + "\n", + " # Plot the fit constraints (i.e. likelihood):\n", " x, y, z = get_xyz(nsig_fit)\n", - " ax.tricontour(x, y, z, cmap=cmap_fit, levels=[0], zorder=1) #plots the blue line\n", - " likelihood_contour = ax.tricontourf(x, y, np.abs(z), cmap=cmap_fit, levels=[0,1,2], alpha=0.5, zorder=1) #plots the blue contours\n", - " \n", - " #### Plot the posterior\n", + " ax.tricontour(x, y, z, cmap=cmap_fit, levels=[0], zorder=1) # plots the blue line\n", + " likelihood_contour = ax.tricontourf(\n", + " x, y, np.abs(z), cmap=cmap_fit, levels=[0, 1, 2], alpha=0.5, zorder=1\n", + " ) # plots the blue contours\n", + "\n", + " # Plot the posterior\n", " if posterior is not None and post_uptosigma > 0:\n", - " #calculate the posterior values that corresponds to the 1,2,3 sigma contours\n", + " # calculate the posterior values that corresponds to the 1,2,3 sigma contours\n", " posterior_sorted = np.sort(posterior.values.flatten())[::-1]\n", " posterior_sum = np.sum(posterior_sorted)\n", " posterior_cumsum = np.cumsum(posterior_sorted)\n", " p1sigma = posterior_sorted[posterior_cumsum > 0.3935 * posterior_sum][0]\n", " p2sigma = posterior_sorted[posterior_cumsum > 0.8647 * posterior_sum][0]\n", " p3sigma = posterior_sorted[posterior_cumsum > 0.9889 * posterior_sum][0]\n", - " post_levels = [p3sigma, p2sigma, p1sigma][3-post_uptosigma:]\n", - " post_linestyles = ['dotted', 'dashed', 'solid'][3-post_uptosigma:]\n", + " post_levels = [p3sigma, p2sigma, p1sigma][3 - post_uptosigma :]\n", + " post_linestyles = [\"dotted\", \"dashed\", \"solid\"][3 - post_uptosigma :]\n", "\n", " x, y, z = get_xyz(posterior)\n", "\n", - " #plot the joint posterior 1/2/3 sigma lines:\n", - " cn_post = ax.tricontour(x, y, z, levels=post_levels, linestyles=post_linestyles, colors='black', zorder=2)\n", - " \n", - " #print the posterior 1sigma credible intervals:\n", - " pT0, pMdot = get_array_from_contour(cn_post, -1) #get last level (=1 sigma) from posterior\n", + " # plot the joint posterior 1/2/3 sigma lines:\n", + " cn_post = ax.tricontour(\n", + " x,\n", + " y,\n", + " z,\n", + " levels=post_levels,\n", + " linestyles=post_linestyles,\n", + " colors=\"black\",\n", + " zorder=2,\n", + " )\n", + "\n", + " # print the posterior 1sigma credible intervals:\n", + " pT0, pMdot = get_array_from_contour(\n", + " cn_post, -1\n", + " ) # get last level (=1 sigma) from posterior\n", " if pT0 is not None and pMdot is not None:\n", - " bestT0_index, bestMdot_index = np.unravel_index(np.nanargmax(posterior), posterior.shape)\n", - " bestT0, bestMdot = float(posterior.index[bestT0_index]), float(posterior.columns[bestMdot_index]) #T, Mdot of the maximum of the posterior\n", - " max_pMdot, min_pMdot, max_pT0, min_pT0 = np.max(pMdot), np.min(pMdot), np.max(pT0), np.min(pT0) #\n", - " print(f\"1 sigma constraints:\\nlog10(Mdot) = {bestMdot} + {max_pMdot-bestMdot} - {bestMdot-min_pMdot}\")\n", + " bestT0_index, bestMdot_index = np.unravel_index(\n", + " np.nanargmax(posterior), posterior.shape\n", + " )\n", + " bestT0, bestMdot = float(posterior.index[bestT0_index]), float(\n", + " posterior.columns[bestMdot_index]\n", + " ) # T, Mdot of the maximum of the posterior\n", + " max_pMdot, min_pMdot, max_pT0, min_pT0 = (\n", + " np.max(pMdot),\n", + " np.min(pMdot),\n", + " np.max(pT0),\n", + " np.min(pT0),\n", + " ) #\n", + " print(\n", + " f\"1 sigma constraints:\\nlog10(Mdot) = {bestMdot} + {max_pMdot-bestMdot} - {bestMdot-min_pMdot}\"\n", + " )\n", " print(f\"T0 = {bestT0} + {max_pT0-bestT0} - {bestT0-min_pT0}\")\n", - " \n", - " \n", + "\n", " if show_colorbar:\n", " divider = make_axes_locatable(ax)\n", - " \n", + "\n", " if dT is not None and sigmaT is not None:\n", - " #plot the prior colorbar\n", + " # plot the prior colorbar\n", " cax1 = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", " cbar1 = fig.colorbar(prior_contour, cax=cax1)\n", - " cax1.yaxis.set_major_locator(plt.NullLocator()) #turn the ticks for cbar1 off\n", - " \n", - " #plot the likelihood colorbar \n", + " cax1.yaxis.set_major_locator(\n", + " plt.NullLocator()\n", + " ) # turn the ticks for cbar1 off\n", + "\n", + " # plot the likelihood colorbar\n", " cax2 = divider.append_axes(\"right\", size=\"5%\", pad=0.05)\n", " cbar2 = fig.colorbar(likelihood_contour, cax=cax2)\n", - " cax2.set_yticklabels([r'0$\\sigma$', r'1$\\sigma$', r'2$\\sigma$'])\n", - " \n", - " \n", - " ax.set_xlabel(r'$T_0$ [K]')\n", - " ax.set_ylabel(r'log$_{10}$($\\dot{M}$ [g s$^{-1}$])')\n", + " cax2.set_yticklabels([r\"0$\\sigma$\", r\"1$\\sigma$\", r\"2$\\sigma$\"])\n", + "\n", + " ax.set_xlabel(r\"$T_0$ [K]\")\n", + " ax.set_ylabel(r\"log$_{10}$($\\dot{M}$ [g s$^{-1}$])\")\n", " if bounds_T0:\n", " ax.set_xlim(*bounds_T0)\n", " if bounds_Mdot:\n", @@ -820,7 +1111,7 @@ " if title != None:\n", " ax.set_title(title)\n", " if save != None:\n", - " plt.savefig(save, bbox_inches='tight', dpi=300)\n", + " plt.savefig(save, bbox_inches=\"tight\", dpi=300)\n", " if _showplot:\n", " plt.show()" ] @@ -830,38 +1121,84 @@ "id": "aa8609df", "metadata": {}, "source": [ - "# --------------\n", - "\n", - "# Retrieval of a mock spectrum of a hot Neptune" + "## Retrieval of a mock spectrum of a hot Neptune" ] }, { "cell_type": "markdown", - "id": "6964e61b", + "id": "dea2e2a7-78c6-4974-b3e9-4028834d87fd", "metadata": {}, "source": [ "### Step 1: Create a grid of Parker wind profiles with p-winds (skip or run a rougher grid if you use our pre-run models)\n", "\n", - "This step can be done by calling `construct_parker.py` from the command-line with the proper arguments. Running `python construct_parker.py --help` will give an overview of the available arguments. In our case, we will run *p-winds* with a solar composition (so including metals at the solar abundances). This is done by passing `-z 1`. We will run a grid of models that span temperatures $T_0$ from 4000 to 7000 K in steps of 100 K, and log10-mass-loss rates $\\dot{M}$ from 10.5 to 11.5 in steps of 0.05 dex. To run a grid of temperatures, we need to pass the lowest temperature, the highest temperature, and the step size to the `-T` argument, and similarly for the log-10 mass-loss rate `-Mdot`. Since in the future, we might want to explore different compositions, `construct_parker.py` always expects you to give a folder name `-pdir` where we want to store our Parker profiles. We reccommend using a descriptive name, so in this case we will go with *z_1* and the path where our profiles will be saved is then *$SUNBATHER_PROJECT_PATH/parker_profiles/hotNeptune/z_1/*. Finally, tidal gravity is included by default, but we will turn it off here in order to reproduce the results from Linssen et al. (2024), by passing the `-no_tidal` flag.\n", - "\n", - "> **The full command to create a grid of solar composition Parker wind models is**:

\n", - "> `python construct_parker.py -plname hotNeptune -pdir z_1 -T 4000 7000 100 -Mdot 10.5 11.5 0.05 -z 1 -no_tidal`\n", - "\n", + "This step can be done by calling `construct_parker.py` from the command-line with the proper arguments. Running `python construct_parker.py --help` will give an overview of the available arguments. In our case, we will run *p-winds* with a solar composition (so including metals at the solar abundances). This is done by passing `-z 1`. We will run a grid of models that span temperatures $T_0$ from 4000 to 7000 K in steps of 100 K, and log10-mass-loss rates $\\dot{M}$ from 10.5 to 11.5 in steps of 0.05 dex. To run a grid of temperatures, we need to pass the lowest temperature, the highest temperature, and the step size to the `-T` argument, and similarly for the log-10 mass-loss rate `-Mdot`. Since in the future, we might want to explore different compositions, `construct_parker.py` always expects you to give a folder name `-pdir` where we want to store our Parker profiles. We reccommend using a descriptive name, so in this case we will go with *z_1* and the path where our profiles will be saved is then *$SUNBATHER_PROJECT_PATH/parker_profiles/hotNeptune/z_1/*. Finally, tidal gravity is included by default, but we will turn it off here in order to reproduce the results from Linssen et al. (2024), by passing the `-no_tidal` flag." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c61a5f8-b946-4220-859d-3bee7000a0cf", + "metadata": {}, + "outputs": [], + "source": [ + "# The full command to create a grid of solar composition Parker wind models is:\n", + "from sunbather import construct_parker\n", + "temp = np.arange(4000, 7001, 100)\n", + "mdot = np.arange(10.5, 11.51, 0.05)\n", + "zdict = tools.get_zdict(z=1)\n", + "\n", + "construct_parker.run_models(\n", + " plname=\"hotNeptune\",\n", + " pdir=\"z_1\",\n", + " temp_list=temp,\n", + " mdot_list=mdot,\n", + " zdict=zdict,\n", + " no_tidal=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "630591f8-bbbd-4ab7-89de-58ed6e9872d1", + "metadata": {}, + "source": [ "Additionally, with the `-cores` flag you can specify the number of parallel CPUs used. For this set of parameters, this command should take on the order of a few minutes. In the *$SUNBATHER_PROJECT_PATH/parker_profiles/hotNeptune/z_1/* folder, there should now be many different .txt files with the isothermal Parker wind structures, feel free to inspect them!" ] }, { "cell_type": "markdown", - "id": "e73b1280", + "id": "4d05d58c-6821-498e-9f63-df7a6cdd0523", "metadata": {}, "source": [ "### Step 2: Run the Parker wind profiles through Cloudy (skip or run a rougher grid if you use our pre-run models)\n", "\n", - "This step can be done by calling `convergeT_parker.py` from the command-line with the proper arguments (`--help` will explain these). The $T_0$ and $\\dot{M}$ grid-commands are the same as in Step 1. We also again need to specify a folder name where we want to save our *Cloudy* simulations (which is a different directory from where the isothermal Parker wind profiles were stored). In this case we will indicate that we used _Cloudy_ with a solar composition/metallicity, i.e. a metallicity of z=1: `-dir z_1` and so our simulations will be saved in _$SUNBATHER_PROJECT_PATH/sims/1D/hotNeptune/z_0/_. We now again need to specify the folder where we want to read the Parker wind profiles from: `-pdir fH_0.9`. Since the *Cloudy* simulations are more time-consuming, you can easily run the different simulations in parallel by using the `-cores` flag. We then need to choose the atmospheric composition in _Cloudy_. We go with the default solar composition so we do not have to provide a command-line argument for it. The last thing we need to think about, is for which atomic/ionic species we want to save *Cloudy's* output. The default behavior is to save everything that's available, but this generally results in large file sizes (~5 MB per model, if metals are included). In our particular case, we want to fit helium observations, so we are fine with just saving the densities of the different atomic helium energy levels: `-save_sp He`.\n", - "\n", - "> **The full command to run our grid of hydrogen/helium Parker wind models through *Cloudy* is:**:

\n", - "> `python convergeT_parker.py -plname hotNeptune -dir z_1 -pdir fH_0.9 -T 4000 7000 100 -Mdot 10.5 11.5 0.05 -save_sp He`\n", - "\n", + "This step can be done by calling `convergeT_parker.py` from the command-line with the proper arguments (`--help` will explain these). The $T_0$ and $\\dot{M}$ grid-commands are the same as in Step 1. We also again need to specify a folder name where we want to save our *Cloudy* simulations (which is a different directory from where the isothermal Parker wind profiles were stored). In this case we will indicate that we used _Cloudy_ with a solar composition/metallicity, i.e. a metallicity of z=1: `-dir z_1` and so our simulations will be saved in _$SUNBATHER_PROJECT_PATH/sims/1D/hotNeptune/z_0/_. We now again need to specify the folder where we want to read the Parker wind profiles from: `-pdir fH_0.9`. Since the *Cloudy* simulations are more time-consuming, you can easily run the different simulations in parallel by using the `-cores` flag. We then need to choose the atmospheric composition in _Cloudy_. We go with the default solar composition so we do not have to provide a command-line argument for it. The last thing we need to think about, is for which atomic/ionic species we want to save *Cloudy's* output. The default behavior is to save everything that's available, but this generally results in large file sizes (~5 MB per model, if metals are included). In our particular case, we want to fit helium observations, so we are fine with just saving the densities of the different atomic helium energy levels: `-save_sp He`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbdc7986-6d55-4e35-aa04-5948e0d717f0", + "metadata": {}, + "outputs": [], + "source": [ + "# The full command to run our grid of hydrogen/helium Parker wind models through *Cloudy* is:\n", + "import sunbather.convergeT_parker\n", + "sunbather.convergeT_parker.main(\n", + " plname=\"hotNeptune\",\n", + " dir=\"z_1\",\n", + " pdir=\"fH_0.9\",\n", + " T=\"4000 7000 100\",\n", + " Mdot=\"10.5 11.5 0.05\",\n", + " save_sp=\"He\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1f26281f-bcf9-4738-8af8-b6b829658b47", + "metadata": {}, + "source": [ "Additionally, with the `-cores` flag you can specify the number of parallel CPUs used. For this set of parameters, this command should take many hours (roughly 10 minutes per model, and we are running roughy 30x20 models), and it is adviced to run it on a compute cluster. In the _$SUNBATHER_PROJECT_PATH/sims/1D/hotNeptune/fH_0.9/_ folder, there should now be many different sub-folders, with the output of the _Cloudy_ simulations, feel free to inspect the files! The _converged.png_ file shows the converged temperature structure, and the other _converged.*_ files are the _Cloudy_ output files." ] }, @@ -891,1017 +1228,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "5c08a50d", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The true EW is 0.014318327135537906. We have drawn an observed EW = 0.013534265740520248 +- 0.0017897908919422383 (which is a 7.561925698388814 sigma detection)\n" - ] - } - ], + "outputs": [], "source": [ - "#set random seed for consistent results\n", + "# set random seed for consistent results\n", "np.random.seed(0)\n", "\"\"\"\n", "#if due to a different numpy version, your generated mock spectrum is different,\n", @@ -1909,40 +1241,44 @@ "#which has stored np.column_stack((wavs, observed_excess_absorption, observed_excess_absorption_errorbar))\n", "\"\"\"\n", "\n", - "#Let's make the spectrum of the generic hot Neptune\n", - "hotNep = tools.Sim(tools.projectpath+\"/sims/1D/hotNeptune/z_1/parker_5100_10.950/converged\")\n", + "# Let's make the spectrum of the generic hot Neptune\n", + "hotNep = tools.Sim(\n", + " projectpath + \"/sims/1D/hotNeptune/z_1/parker_5100_10.950/converged\"\n", + ")\n", "\n", "wavs = RT.constantR_wavs(10830, 10836, 80000)\n", - "true_spec, _, _ = RT.FinFout(hotNep, wavs, 'He')\n", + "true_spec, _, _ = RT.FinFout(hotNep, wavs, \"He\")\n", "\n", - "#draw errors from normal distribution\n", - "observed_errorbar = np.ones_like(wavs) * 0.0025 #just pick a number\n", + "# draw errors from normal distribution\n", + "observed_errorbar = np.ones_like(wavs) * 0.0025 # just pick a number\n", "observed_spec = np.random.normal(loc=true_spec, scale=observed_errorbar)\n", "\n", - "#convert into excess absorption in % which is what the calc_chisqs_He10830() function expects\n", - "#we pretend we don't know the continuum level and take it as the average away from the line (last 15 points)\n", + "# convert into excess absorption in % which is what the calc_chisqs_He10830() function expects\n", + "# we pretend we don't know the continuum level and take it as the average away from the line (last 15 points)\n", "observed_excess_absorption = 100 * (np.mean(observed_spec[-15:]) - observed_spec)\n", "observed_excess_absorption_errorbar = 100 * observed_errorbar\n", "\n", - "highres_wavs = np.linspace(10830, 10836, 1000) #for smooth plotting\n", - "highres_true_spec, _, _ = RT.FinFout(hotNep, highres_wavs, 'He')\n", + "highres_wavs = np.linspace(10830, 10836, 1000) # for smooth plotting\n", + "highres_true_spec, _, _ = RT.FinFout(hotNep, highres_wavs, \"He\")\n", "\n", "\n", "fig, ax = plt.subplots(1)\n", "ax.plot(highres_wavs, highres_true_spec)\n", - "ax.errorbar(wavs, observed_spec, yerr=observed_errorbar, fmt='o')\n", + "ax.errorbar(wavs, observed_spec, yerr=observed_errorbar, fmt=\"o\")\n", "ax.set_xlabel(\"Wavelength [Å]\")\n", "ax.set_ylabel(r\"$F_{in}$/$F_{out}$\")\n", "plt.show()\n", "\n", "\n", - "#integrate the difference with the continuum to obtain the equivalent width\n", - "true_EW = trapezoid(np.max(true_spec) - true_spec, x=wavs) #units: Å\n", - "#let's choose the errorbar as 1/8 of the EW so that we will get around a 8 sigma detection\n", - "observed_EW_errorbar = true_EW / 8.\n", + "# integrate the difference with the continuum to obtain the equivalent width\n", + "true_EW = trapezoid(np.max(true_spec) - true_spec, x=wavs) # units: Å\n", + "# let's choose the errorbar as 1/8 of the EW so that we will get around a 8 sigma detection\n", + "observed_EW_errorbar = true_EW / 8.0\n", "observed_EW = np.random.normal(loc=true_EW, scale=observed_EW_errorbar)\n", - "print(f\"The true EW is {true_EW}. We have drawn an observed EW = {observed_EW} +- {observed_EW_errorbar} \" \\\n", - " f\"(which is a {observed_EW/observed_EW_errorbar} sigma detection)\")\n" + "print(\n", + " f\"The true EW is {true_EW}. We have drawn an observed EW = {observed_EW} +- {observed_EW_errorbar} \"\n", + " f\"(which is a {observed_EW/observed_EW_errorbar} sigma detection)\"\n", + ")" ] }, { @@ -1965,27 +1301,27 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "afb89ddc", "metadata": {}, "outputs": [], "source": [ - "#chisqs = calc_chisqs_He10830(tools.projectpath+'/sims/1D/hotNeptune/z_1/', \n", + "# chisqs = calc_chisqs_He10830(tools.projectpath+'/sims/1D/hotNeptune/z_1/',\n", "# wavs, observed_excess_absorption, observed_excess_absorption_errorbar)\n", - "#chisqs.to_csv('materials/hotNeptune_chisqs.csv') #this overwrites the supplied file" + "# chisqs.to_csv('materials/hotNeptune_chisqs.csv') #this overwrites the supplied file" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "98436f6e", "metadata": {}, "outputs": [], "source": [ - "#read in the chi-squared values from file\n", - "chisqs = pd.read_csv('materials/hotNeptune_chisqs.csv', index_col=0, dtype=float)\n", + "# read in the chi-squared values from file\n", + "chisqs = pd.read_csv(\"materials/hotNeptune_chisqs.csv\", index_col=0, dtype=float)\n", "\n", - "#calculate the likelihood (an actual p-value, contrary to the chi-squared statistic)\n", + "# calculate the likelihood (an actual p-value, contrary to the chi-squared statistic)\n", "likelihood_resolved = calc_likelihood_resolved(chisqs)" ] }, @@ -1999,1010 +1335,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "133461ec", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "plot_chisq_fit(chisqs, bounds_T0=(4000,7000), bounds_Mdot=(10.5, 11.5), title='Resolved line fit')" + "plot_chisq_fit(\n", + " chisqs, bounds_T0=(4000, 7000), bounds_Mdot=(10.5, 11.5), title=\"Resolved line fit\"\n", + ")" ] }, { @@ -3023,28 +1363,28 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "c86ecca2", "metadata": {}, "outputs": [], "source": [ - "#EWs = calc_EWs_He10830(tools.projectpath+'/sims/1D/hotNeptune/z_1/')\n", - "#EWs.to_csv('materials/hotNeptune_EWs.csv') #this overwrites the supplied file" + "# EWs = calc_EWs_He10830(tools.projectpath+'/sims/1D/hotNeptune/z_1/')\n", + "# EWs.to_csv('materials/hotNeptune_EWs.csv') #this overwrites the supplied file" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "bc6f2960", "metadata": {}, "outputs": [], "source": [ - "#read in the EW values from file\n", - "EWs = pd.read_csv('materials/hotNeptune_EWs.csv', index_col=0, dtype=float)\n", + "# read in the EW values from file\n", + "EWs = pd.read_csv(\"materials/hotNeptune_EWs.csv\", index_col=0, dtype=float)\n", "\n", - "#calculate the number of errorbars discrepancy between model and data\n", - "#this is what the calc_likelihood_unresolved() function expects\n", - "nsig_fit = (EWs - observed_EW)/observed_EW_errorbar\n", + "# calculate the number of errorbars discrepancy between model and data\n", + "# this is what the calc_likelihood_unresolved() function expects\n", + "nsig_fit = (EWs - observed_EW) / observed_EW_errorbar\n", "\n", "likelihood_unresolved = calc_likelihood_unresolved(nsig_fit)" ] @@ -3059,1010 +1399,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "0b5ded03", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "plot_nsig_fit(nsig_fit, bounds_T0=(4000,7000), bounds_Mdot=(10.5,11.5), title='Equivalent width fit')" + "plot_nsig_fit(\n", + " nsig_fit,\n", + " bounds_T0=(4000, 7000),\n", + " bounds_Mdot=(10.5, 11.5),\n", + " title=\"Equivalent width fit\",\n", + ")" ] }, { @@ -4085,29 +1432,29 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "a6d078e4", "metadata": {}, "outputs": [], "source": [ - "#dT, sigmaT = calc_dT_helium(tools.projectpath+'/sims/1D/hotNeptune/z_1/')\n", - "#dT.to_csv('materials/hotNeptune_dT.csv', float_format='%.3e') #these overwrite the supplied files\n", - "#sigmaT.to_csv('materials/hotNeptune_sigmaT.csv', float_format='%.3e')" + "# dT, sigmaT = calc_dT_helium(tools.projectpath+'/sims/1D/hotNeptune/z_1/')\n", + "# dT.to_csv('materials/hotNeptune_dT.csv', float_format='%.3e') #these overwrite the supplied files\n", + "# sigmaT.to_csv('materials/hotNeptune_sigmaT.csv', float_format='%.3e')" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "14a91e2d", "metadata": {}, "outputs": [], "source": [ - "#read in the dT and sigmaT values from file\n", - "dT = pd.read_csv('materials/hotNeptune_dT.csv', index_col=0, dtype=float)\n", - "sigmaT = pd.read_csv('materials/hotNeptune_sigmaT.csv', index_col=0, dtype=float) \n", + "# read in the dT and sigmaT values from file\n", + "dT = pd.read_csv(\"materials/hotNeptune_dT.csv\", index_col=0, dtype=float)\n", + "sigmaT = pd.read_csv(\"materials/hotNeptune_sigmaT.csv\", index_col=0, dtype=float)\n", "\n", - "#now we can calculate the prior based on the model self-consistency\n", - "prior = calc_prior(dT, sigmaT)" + "# now we can calculate the prior based on the model self-consistency\n", + "prior = calc_prior(dT, sigmaT)" ] }, { @@ -4120,1010 +1467,18 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "67f04b54", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "plot_selfcons(dT, sigmaT, bounds_T0=(4000,7000), bounds_Mdot=(10.5,11.5), title='Model self-consistency')" + "plot_selfcons(\n", + " dT,\n", + " sigmaT,\n", + " bounds_T0=(4000, 7000),\n", + " bounds_Mdot=(10.5, 11.5),\n", + " title=\"Model self-consistency\",\n", + ")" ] }, { @@ -5144,30 +1499,17 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "d2f8579b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Constraints from marginalized posteriors (not necessarily normally distributed!):\n", - "log10(Mdot) = 10.9 + 0.09999999999999964 - 0.09999999999999964\n", - "T0 = 4800.0 + 600.0 - 500.0\n", - "Constraints from marginalized posteriors (not necessarily normally distributed!):\n", - "log10(Mdot) = 10.9 + 0.09999999999999964 - 0.09999999999999964\n", - "T0 = 4900.0 + 400.0 - 500.0\n" - ] - } - ], + "outputs": [], "source": [ - "#for the resolved line fit, we calculate the posterior with a flat prior\n", - "#we do this only so that calc_posterior() prints the constrained T0 and Mdot based on\n", - "#the likelihood alone, which we can then quote\n", - "posterior_resolved = calc_posterior(1., likelihood_resolved)\n", + "# for the resolved line fit, we calculate the posterior with a flat prior\n", + "# we do this only so that calc_posterior() prints the constrained T0 and Mdot based on\n", + "# the likelihood alone, which we can then quote\n", + "posterior_resolved = calc_posterior(1.0, likelihood_resolved)\n", "\n", - "#for the EW fit, we calculate the posterior with the prior based on model self-consistency\n", + "# for the EW fit, we calculate the posterior with the prior based on model self-consistency\n", "posterior_unresolved = calc_posterior(prior, likelihood_unresolved)" ] }, @@ -5181,2009 +1523,24 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "44e9ab5c", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "plot_posterior(posterior_resolved, bounds_T0=(4000,7000), bounds_Mdot=(10.5,11.5), title='Resolved fit posterior')\n", - "\n", - "plot_posterior(posterior_unresolved, bounds_T0=(4000,7000), bounds_Mdot=(10.5,11.5), title='Equivalent width fit posterior')" + "plot_posterior(\n", + " posterior_resolved,\n", + " bounds_T0=(4000, 7000),\n", + " bounds_Mdot=(10.5, 11.5),\n", + " title=\"Resolved fit posterior\",\n", + ")\n", + "\n", + "plot_posterior(\n", + " posterior_unresolved,\n", + " bounds_T0=(4000, 7000),\n", + " bounds_Mdot=(10.5, 11.5),\n", + " title=\"Equivalent width fit posterior\",\n", + ")" ] }, { @@ -7198,1073 +1555,199 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "950d3c60", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1 sigma constraints:\n", - "log10(Mdot) = 10.9 + 0.06793855530304249 - 0.05619954871618482\n", - "T0 = 5000.0 + 227.31345196987695 - 442.4995430827812\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ - "with plt.rc_context({\"font.family\": \"serif\", \"mathtext.fontset\": \"dejavuserif\"}): #identical to paper\n", + "with plt.rc_context(\n", + " {\"font.family\": \"serif\", \"mathtext.fontset\": \"dejavuserif\"}\n", + "): # identical to paper\n", "\n", - " fig, axes = plt.subplots(ncols=1, nrows=3, figsize=(4.5,12))\n", + " fig, axes = plt.subplots(ncols=1, nrows=3, figsize=(4.5, 12))\n", "\n", " #######\n", "\n", - " #re-calculate the best-fit spectrum\n", - " bestfitsim = tools.Sim(tools.projectpath+\"/sims/1D/hotNeptune/z_1/parker_4800_10.900/converged\")\n", - " bestspec, _, _ = RT.FinFout(bestfitsim, highres_wavs, 'He')\n", - " \n", - " axes[0].plot(highres_wavs, highres_true_spec, color='blueviolet', label='True model', lw=2, zorder=-20)\n", - " axes[0].errorbar(wavs, observed_spec, yerr=observed_errorbar, fmt='o', color='black', label='Mock observation', lw=2, zorder=-10)\n", - " axes[0].plot(highres_wavs, bestspec, color='dodgerblue', label='Retrieved model', linestyle='dashed', lw=2, zorder=-5)\n", - " axes[0].text(0.96,0.04, r'$\\bf{a)}$', ha='right', va='bottom', transform=axes[0].transAxes)\n", + " # re-calculate the best-fit spectrum\n", + " bestfitsim = tools.Sim(\n", + " tools.projectpath + \"/sims/1D/hotNeptune/z_1/parker_4800_10.900/converged\"\n", + " )\n", + " bestspec, _, _ = RT.FinFout(bestfitsim, highres_wavs, \"He\")\n", + "\n", + " axes[0].plot(\n", + " highres_wavs,\n", + " highres_true_spec,\n", + " color=\"blueviolet\",\n", + " label=\"True model\",\n", + " lw=2,\n", + " zorder=-20,\n", + " )\n", + " axes[0].errorbar(\n", + " wavs,\n", + " observed_spec,\n", + " yerr=observed_errorbar,\n", + " fmt=\"o\",\n", + " color=\"black\",\n", + " label=\"Mock observation\",\n", + " lw=2,\n", + " zorder=-10,\n", + " )\n", + " axes[0].plot(\n", + " highres_wavs,\n", + " bestspec,\n", + " color=\"dodgerblue\",\n", + " label=\"Retrieved model\",\n", + " linestyle=\"dashed\",\n", + " lw=2,\n", + " zorder=-5,\n", + " )\n", + " axes[0].text(\n", + " 0.96, 0.04, r\"$\\bf{a)}$\", ha=\"right\", va=\"bottom\", transform=axes[0].transAxes\n", + " )\n", " axes[0].set_xlabel(\"Wavelength [Å]\")\n", " axes[0].set_ylabel(r\"$F_{in}$/$F_{out}$\")\n", - " axes[0].legend(loc=\"lower left\", edgecolor='none', facecolor='none', framealpha=0)\n", - " axes[0].xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, _: '{:g}'.format(x)))\n", + " axes[0].legend(loc=\"lower left\", edgecolor=\"none\", facecolor=\"none\", framealpha=0)\n", + " axes[0].xaxis.set_major_formatter(\n", + " matplotlib.ticker.FuncFormatter(lambda x, _: \"{:g}\".format(x))\n", + " )\n", "\n", " #######\n", "\n", - " plot_joint_constraint_resolved(None, None, chisqs, None, bounds_T0=(4000,7000), \n", - " bounds_Mdot = (10.5, 11.5), fig=fig, ax=axes[1],\n", - " show_colorbar=False)\n", - " axes[1].scatter(5100, 10.95, marker='x', color='blueviolet', s=100, zorder=100) #the true model\n", - " axes[1].text(4870, 11, 'True model', color='blueviolet', ha='left', va='bottom')\n", - " axes[1].text(4400,10.75, 'Fit likelihood', ha='left', va='top', color=plt.get_cmap('winter_r')(0.65))\n", - " axes[1].text(0.96,0.96, r'$\\bf{b)}$ Resolved line fit', ha='right', va='top', transform=axes[1].transAxes)\n", - " axes[1].text(4900, 10.887, r'1$\\sigma$', va='center', ha='center', color=plt.get_cmap('winter_r')(0.55), fontsize=9)\n", - " axes[1].text(4120, 10.765, r'2$\\sigma$', va='center', ha='center', color=plt.get_cmap('winter_r')(0.45), fontsize=9)\n", - " axes[1].set_yticks(np.arange(10.6,11.41, 0.1), minor=True)\n", - " \n", + " plot_joint_constraint_resolved(\n", + " None,\n", + " None,\n", + " chisqs,\n", + " None,\n", + " bounds_T0=(4000, 7000),\n", + " bounds_Mdot=(10.5, 11.5),\n", + " fig=fig,\n", + " ax=axes[1],\n", + " show_colorbar=False,\n", + " )\n", + " axes[1].scatter(\n", + " 5100, 10.95, marker=\"x\", color=\"blueviolet\", s=100, zorder=100\n", + " ) # the true model\n", + " axes[1].text(4870, 11, \"True model\", color=\"blueviolet\", ha=\"left\", va=\"bottom\")\n", + " axes[1].text(\n", + " 4400,\n", + " 10.75,\n", + " \"Fit likelihood\",\n", + " ha=\"left\",\n", + " va=\"top\",\n", + " color=plt.get_cmap(\"winter_r\")(0.65),\n", + " )\n", + " axes[1].text(\n", + " 0.96,\n", + " 0.96,\n", + " r\"$\\bf{b)}$ Resolved line fit\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " transform=axes[1].transAxes,\n", + " )\n", + " axes[1].text(\n", + " 4900,\n", + " 10.887,\n", + " r\"1$\\sigma$\",\n", + " va=\"center\",\n", + " ha=\"center\",\n", + " color=plt.get_cmap(\"winter_r\")(0.55),\n", + " fontsize=9,\n", + " )\n", + " axes[1].text(\n", + " 4120,\n", + " 10.765,\n", + " r\"2$\\sigma$\",\n", + " va=\"center\",\n", + " ha=\"center\",\n", + " color=plt.get_cmap(\"winter_r\")(0.45),\n", + " fontsize=9,\n", + " )\n", + " axes[1].set_yticks(np.arange(10.6, 11.41, 0.1), minor=True)\n", + "\n", " #######\n", - " \n", - " plot_joint_constraint_unresolved(dT, sigmaT, nsig_fit, posterior_unresolved, bounds_T0=(4000,7000), \n", - " bounds_Mdot = (10.5, 11.5), fig=fig, ax=axes[2], post_uptosigma=2,\n", - " show_colorbar=False)\n", - " axes[2].scatter(5100, 10.95, marker='x', color='blueviolet', s=100, zorder=100) #the true model\n", - " axes[2].text(4870, 11.05, 'True model', color='blueviolet', ha='left', va='bottom')\n", - " axes[2].text(4550,11.4, 'Prior', ha='left', va='top', color=plt.get_cmap('autumn')(0.35))\n", - " axes[2].text(6100,10.86, 'Fit likelihood', ha='left', va='top', color=plt.get_cmap('winter_r')(0.65))\n", - " axes[2].text(4500,10.73, r'Posterior', ha='left', va='top', color='black')\n", - " axes[2].text(6300, 10.972, r'1$\\sigma$', va='center', ha='center', color=plt.get_cmap('winter_r')(0.55), fontsize=9)\n", - " axes[2].text(6300, 10.913, r'2$\\sigma$', va='center', ha='center', color=plt.get_cmap('winter_r')(0.45), fontsize=9)\n", - " axes[2].text(4680, 11.3, r'1$\\sigma$', va='center', ha='center', color=plt.get_cmap('autumn')(0.45), fontsize=9)\n", - " axes[2].text(5000, 11.3, r'2$\\sigma$', va='center', ha='center', color=plt.get_cmap('autumn')(0.55), fontsize=9)\n", - " axes[2].text(4850, 10.866, r'1$\\sigma$', va='center', ha='center', color='black', fontsize=9)\n", - " axes[2].text(4500, 10.8, r'2$\\sigma$', va='center', ha='center', color='black', fontsize=9)\n", - " axes[2].text(0.96,0.96, r'$\\bf{c)}$ Equivalent width fit', ha='right', va='top', transform=axes[2].transAxes)\n", - " axes[2].set_yticks(np.arange(10.6,11.41, 0.1), minor=True)\n", - " \n", + "\n", + " plot_joint_constraint_unresolved(\n", + " dT,\n", + " sigmaT,\n", + " nsig_fit,\n", + " posterior_unresolved,\n", + " bounds_T0=(4000, 7000),\n", + " bounds_Mdot=(10.5, 11.5),\n", + " fig=fig,\n", + " ax=axes[2],\n", + " post_uptosigma=2,\n", + " show_colorbar=False,\n", + " )\n", + " axes[2].scatter(\n", + " 5100, 10.95, marker=\"x\", color=\"blueviolet\", s=100, zorder=100\n", + " ) # the true model\n", + " axes[2].text(4870, 11.05, \"True model\", color=\"blueviolet\", ha=\"left\", va=\"bottom\")\n", + " axes[2].text(\n", + " 4550, 11.4, \"Prior\", ha=\"left\", va=\"top\", color=plt.get_cmap(\"autumn\")(0.35)\n", + " )\n", + " axes[2].text(\n", + " 6100,\n", + " 10.86,\n", + " \"Fit likelihood\",\n", + " ha=\"left\",\n", + " va=\"top\",\n", + " color=plt.get_cmap(\"winter_r\")(0.65),\n", + " )\n", + " axes[2].text(4500, 10.73, r\"Posterior\", ha=\"left\", va=\"top\", color=\"black\")\n", + " axes[2].text(\n", + " 6300,\n", + " 10.972,\n", + " r\"1$\\sigma$\",\n", + " va=\"center\",\n", + " ha=\"center\",\n", + " color=plt.get_cmap(\"winter_r\")(0.55),\n", + " fontsize=9,\n", + " )\n", + " axes[2].text(\n", + " 6300,\n", + " 10.913,\n", + " r\"2$\\sigma$\",\n", + " va=\"center\",\n", + " ha=\"center\",\n", + " color=plt.get_cmap(\"winter_r\")(0.45),\n", + " fontsize=9,\n", + " )\n", + " axes[2].text(\n", + " 4680,\n", + " 11.3,\n", + " r\"1$\\sigma$\",\n", + " va=\"center\",\n", + " ha=\"center\",\n", + " color=plt.get_cmap(\"autumn\")(0.45),\n", + " fontsize=9,\n", + " )\n", + " axes[2].text(\n", + " 5000,\n", + " 11.3,\n", + " r\"2$\\sigma$\",\n", + " va=\"center\",\n", + " ha=\"center\",\n", + " color=plt.get_cmap(\"autumn\")(0.55),\n", + " fontsize=9,\n", + " )\n", + " axes[2].text(\n", + " 4850, 10.866, r\"1$\\sigma$\", va=\"center\", ha=\"center\", color=\"black\", fontsize=9\n", + " )\n", + " axes[2].text(\n", + " 4500, 10.8, r\"2$\\sigma$\", va=\"center\", ha=\"center\", color=\"black\", fontsize=9\n", + " )\n", + " axes[2].text(\n", + " 0.96,\n", + " 0.96,\n", + " r\"$\\bf{c)}$ Equivalent width fit\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " transform=axes[2].transAxes,\n", + " )\n", + " axes[2].set_yticks(np.arange(10.6, 11.41, 0.1), minor=True)\n", + "\n", " #######\n", - " \n", + "\n", " plt.show()" ] }, @@ -8283,9 +1766,7 @@ "id": "a10c1b00", "metadata": {}, "source": [ - "# -------------------\n", - "\n", - "# Retrieval of the observed helium spectrum of TOI-2134 b\n", + "## Retrieval of the observed helium spectrum of TOI-2134 b\n", "\n", "Having seen how a typical analysis is performed, we now move on to interpreting the helium spectrum of a real planet: TOI-2134 b. We perform the analysis for two different atmospheric compositions. The steps in principle are the same as before, so we do not repeat much of the explanations. As we are working with spectrally resolved data, we use a flat prior here. However, we also calculate the model self-consistency like we did before, simply to demonstrate that there is interesting physics going on here, since the self-consistent temperature is very different from that indicated by the data fit. So in this case we do not convert that self-consistency measure into a prior." ] @@ -8338,1055 +1819,70 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "4937b2d6", "metadata": {}, "outputs": [], "source": [ "def gaussian(x, amplitude, mean, std_dev):\n", - " return amplitude * np.exp(-((x - mean) / std_dev) ** 2 / 2)" + " return amplitude * np.exp(-(((x - mean) / std_dev) ** 2) / 2)" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "4459f3c1", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "There is a 0.1791344919038238 angstrom shift of the line from rest-frame, which we now shift back.\n" - ] - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "observed_spec_toi2134 = pd.read_table('materials/dbf2.txt', skiprows=13, \n", - " names=['wav', 'ea', 'sig_ea'], delim_whitespace=True)\n", + "observed_spec_toi2134 = pd.read_table(\n", + " \"materials/dbf2.txt\",\n", + " skiprows=13,\n", + " names=[\"wav\", \"ea\", \"sig_ea\"],\n", + " delim_whitespace=True,\n", + ")\n", "observed_spec_toi2134.dropna(inplace=True)\n", "\n", - "#fit a Gaussian to the peak of the data to obtain the line center\n", - "params, covariance = curve_fit(gaussian,\n", - " observed_spec_toi2134.wav[(observed_spec_toi2134.wav > 10832.5) & (observed_spec_toi2134.wav < 10833.8)],\n", - " observed_spec_toi2134.ea[(observed_spec_toi2134.wav > 10832.5) & (observed_spec_toi2134.wav < 10833.8)],\n", - " p0=[0.4, 10833., 0.5])\n", + "# fit a Gaussian to the peak of the data to obtain the line center\n", + "params, covariance = curve_fit(\n", + " gaussian,\n", + " observed_spec_toi2134.wav[\n", + " (observed_spec_toi2134.wav > 10832.5) & (observed_spec_toi2134.wav < 10833.8)\n", + " ],\n", + " observed_spec_toi2134.ea[\n", + " (observed_spec_toi2134.wav > 10832.5) & (observed_spec_toi2134.wav < 10833.8)\n", + " ],\n", + " p0=[0.4, 10833.0, 0.5],\n", + ")\n", "offset = params[1] - 10833.25\n", - "print(\"There is a\", offset, \"angstrom shift of the line from rest-frame, which we now shift back.\")\n", + "print(\n", + " \"There is a\",\n", + " offset,\n", + " \"angstrom shift of the line from rest-frame, which we now shift back.\",\n", + ")\n", "observed_spec_toi2134.wav = observed_spec_toi2134.wav - offset\n", "\n", "\n", - "#we will not fit the whole dataset, but only the part between 10831 and 10836 angstroms\n", - "obs_wav_toi2134 = observed_spec_toi2134.wav[(observed_spec_toi2134.wav > 10831) & (observed_spec_toi2134.wav < 10836)].values\n", - "obs_ea_toi2134 = observed_spec_toi2134.ea[(observed_spec_toi2134.wav > 10831) & (observed_spec_toi2134.wav < 10836)].values\n", - "obs_sig_ea_toi2134 = observed_spec_toi2134.sig_ea[(observed_spec_toi2134.wav > 10831) & (observed_spec_toi2134.wav < 10836)].values\n", + "# we will not fit the whole dataset, but only the part between 10831 and 10836 angstroms\n", + "obs_wav_toi2134 = observed_spec_toi2134.wav[\n", + " (observed_spec_toi2134.wav > 10831) & (observed_spec_toi2134.wav < 10836)\n", + "].values\n", + "obs_ea_toi2134 = observed_spec_toi2134.ea[\n", + " (observed_spec_toi2134.wav > 10831) & (observed_spec_toi2134.wav < 10836)\n", + "].values\n", + "obs_sig_ea_toi2134 = observed_spec_toi2134.sig_ea[\n", + " (observed_spec_toi2134.wav > 10831) & (observed_spec_toi2134.wav < 10836)\n", + "].values\n", "\n", "\n", "fig, ax = plt.subplots(1)\n", "ax.errorbar(obs_wav_toi2134, obs_ea_toi2134, yerr=obs_sig_ea_toi2134)\n", - "ax.axvline(10832.057472, color='k', linestyle='dotted')\n", - "ax.axvline(10833.216751, color='k', linestyle='dotted')\n", - "ax.axvline(10833.306444, color='k', linestyle='dotted')\n", - "ax.set_xlabel('Wavelength [Å]')\n", - "ax.set_ylabel('Excess absorption [%]')\n", - "ax.set_title('TOI-2134 b observations by Zhang et al. 2023 (shifted)')\n", + "ax.axvline(10832.057472, color=\"k\", linestyle=\"dotted\")\n", + "ax.axvline(10833.216751, color=\"k\", linestyle=\"dotted\")\n", + "ax.axvline(10833.306444, color=\"k\", linestyle=\"dotted\")\n", + "ax.set_xlabel(\"Wavelength [Å]\")\n", + "ax.set_ylabel(\"Excess absorption [%]\")\n", + "ax.set_title(\"TOI-2134 b observations by Zhang et al. 2023 (shifted)\")\n", "plt.show()" ] }, @@ -9400,35 +1896,39 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "2a3b9cf2", "metadata": {}, "outputs": [], "source": [ - "#chisqs_z0 = calc_chisqs_He10830(tools.projectpath+'/sims/1D/TOI2134b/z_0/', \n", - "# obs_wav_toi2134, obs_ea_toi2134, obs_sig_ea_toi2134, \n", + "# chisqs_z0 = calc_chisqs_He10830(tools.projectpath+'/sims/1D/TOI2134b/z_0/',\n", + "# obs_wav_toi2134, obs_ea_toi2134, obs_sig_ea_toi2134,\n", "# instrument_R=32000, Mdotstep=0.02)\n", - "#chisqs_z0.to_csv('materials/TOI2134b_chisqs_fit_z0.csv') #overwrites the supplied file\n", + "# chisqs_z0.to_csv('materials/TOI2134b_chisqs_fit_z0.csv') #overwrites the supplied file\n", "#\n", - "#chisqs_z100 = calc_chisqs_He10830(tools.projectpath+'/sims/1D/TOI2134b/z_100/',\n", + "# chisqs_z100 = calc_chisqs_He10830(tools.projectpath+'/sims/1D/TOI2134b/z_100/',\n", "# obs_wav_toi2134, obs_ea_toi2134, obs_sig_ea_toi2134,\n", "# instrument_R=32000, Mdotstep=0.02)\n", - "#chisqs_z100.to_csv('materials/TOI2134b_chisqs_fit_z100.csv') #overwrites the supplied file" + "# chisqs_z100.to_csv('materials/TOI2134b_chisqs_fit_z100.csv') #overwrites the supplied file" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "0ed5d4f3", "metadata": {}, "outputs": [], "source": [ - "#read the chi-squared values from file\n", - "chisqs_z0 = pd.read_csv('materials/TOI2134b_chisqs_fit_z0.csv', index_col=0, dtype=float)\n", + "# read the chi-squared values from file\n", + "chisqs_z0 = pd.read_csv(\n", + " \"materials/TOI2134b_chisqs_fit_z0.csv\", index_col=0, dtype=float\n", + ")\n", "\n", - "chisqs_z100 = pd.read_csv('materials/TOI2134b_chisqs_fit_z100.csv', index_col=0, dtype=float)\n", + "chisqs_z100 = pd.read_csv(\n", + " \"materials/TOI2134b_chisqs_fit_z100.csv\", index_col=0, dtype=float\n", + ")\n", "\n", - "#from chi-squared, we can calculate the likelihood\n", + "# from chi-squared, we can calculate the likelihood\n", "likelihood_z0 = calc_likelihood_resolved(chisqs_z0)\n", "\n", "likelihood_z100 = calc_likelihood_resolved(chisqs_z100)" @@ -9444,1010 +1944,17 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "73648232", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "plot_chisq_fit(chisqs_z0, bounds_T0=(2000,8000), bounds_Mdot=(8.5,10), title=\"TOI-2134 b, pure H/He\")" + "plot_chisq_fit(\n", + " chisqs_z0,\n", + " bounds_T0=(2000, 8000),\n", + " bounds_Mdot=(8.5, 10),\n", + " title=\"TOI-2134 b, pure H/He\",\n", + ")" ] }, { @@ -10480,33 +1987,35 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "60e3c65c", "metadata": {}, "outputs": [], "source": [ - "#dT_z0, sigmaT_z0 = calc_dT_helium(tools.projectpath+'/sims/1D/TOI2134b/z_0', Mdotstep=0.02)\n", - "#dT_z0.to_csv('materials/TOI2134b_dT_z0.csv', float_format='%.3e') #overwrites the supplied files\n", - "#sigmaT_z0.to_csv('materials/TOI2134b_sigmaT_z0.csv', float_format='%.3e')\n", + "# dT_z0, sigmaT_z0 = calc_dT_helium(tools.projectpath+'/sims/1D/TOI2134b/z_0', Mdotstep=0.02)\n", + "# dT_z0.to_csv('materials/TOI2134b_dT_z0.csv', float_format='%.3e') #overwrites the supplied files\n", + "# sigmaT_z0.to_csv('materials/TOI2134b_sigmaT_z0.csv', float_format='%.3e')\n", "\n", - "#dT_z100, sigmaT_z100 = calc_dT_helium(tools.projectpath+'/sims/1D/TOI2134b/z_100', Mdotstep=0.02)\n", - "#dT_z100.to_csv('materials/TOI2134b_dT_z100.csv', float_format='%.3e') #overwrites the supplied files\n", - "#sigmaT_z100.to_csv('materials/TOI2134b_sigmaT_z100.csv', float_format='%.3e')" + "# dT_z100, sigmaT_z100 = calc_dT_helium(tools.projectpath+'/sims/1D/TOI2134b/z_100', Mdotstep=0.02)\n", + "# dT_z100.to_csv('materials/TOI2134b_dT_z100.csv', float_format='%.3e') #overwrites the supplied files\n", + "# sigmaT_z100.to_csv('materials/TOI2134b_sigmaT_z100.csv', float_format='%.3e')" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "1087d9c1", "metadata": {}, "outputs": [], "source": [ - "#load the dT and sigmaT values from file\n", - "dT_z0 = pd.read_csv('materials/TOI2134b_dT_z0.csv', index_col=0, dtype=float)\n", - "sigmaT_z0 = pd.read_csv('materials/TOI2134b_sigmaT_z0.csv', index_col=0, dtype=float) \n", - "\n", - "dT_z100 = pd.read_csv('materials/TOI2134b_dT_z100.csv', index_col=0, dtype=float)\n", - "sigmaT_z100 = pd.read_csv('materials/TOI2134b_sigmaT_z100.csv', index_col=0, dtype=float)" + "# load the dT and sigmaT values from file\n", + "dT_z0 = pd.read_csv(\"materials/TOI2134b_dT_z0.csv\", index_col=0, dtype=float)\n", + "sigmaT_z0 = pd.read_csv(\"materials/TOI2134b_sigmaT_z0.csv\", index_col=0, dtype=float)\n", + "\n", + "dT_z100 = pd.read_csv(\"materials/TOI2134b_dT_z100.csv\", index_col=0, dtype=float)\n", + "sigmaT_z100 = pd.read_csv(\n", + " \"materials/TOI2134b_sigmaT_z100.csv\", index_col=0, dtype=float\n", + ")" ] }, { @@ -10519,30 +2028,17 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "18bfd116", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Constraints from marginalized posteriors (not necessarily normally distributed!):\n", - "log10(Mdot) = 9.28 + 0.0600000000000005 - 0.03999999999999915\n", - "T0 = 5000.0 + 400.0 - 400.0\n", - "Constraints from marginalized posteriors (not necessarily normally distributed!):\n", - "log10(Mdot) = 9.68 + 0.040000000000000924 - 0.03999999999999915\n", - "T0 = 9700.0 + 700.0 - 700.0\n" - ] - } - ], + "outputs": [], "source": [ - "#we calculate the posterior with a flat prior\n", - "#we do this only so that calc_posterior() prints the constrained T0 and Mdot based on\n", - "#the likelihood alone, which we can then quote\n", - "posterior_z0 = calc_posterior(1., likelihood_z0)\n", + "# we calculate the posterior with a flat prior\n", + "# we do this only so that calc_posterior() prints the constrained T0 and Mdot based on\n", + "# the likelihood alone, which we can then quote\n", + "posterior_z0 = calc_posterior(1.0, likelihood_z0)\n", "\n", - "posterior_z100 = calc_posterior(1., likelihood_z100)" + "posterior_z100 = calc_posterior(1.0, likelihood_z100)" ] }, { @@ -10555,30 +2051,38 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "230fec93", "metadata": {}, "outputs": [], "source": [ - "sim_z0_5000 = tools.Sim(tools.projectpath+'/sims/1D/TOI2134b/z_0/parker_5000_9.280/converged') #best-fit\n", - "sim_z0_2600 = tools.Sim(tools.projectpath+'/sims/1D/TOI2134b/z_0/parker_2600_8.800/converged') #self-consistent T\n", - "sim_z100_9700 = tools.Sim(tools.projectpath+'/sims/1D/TOI2134b/z_100/parker_9700_9.680/converged') #best-fit\n", - "sim_z100_4000 = tools.Sim(tools.projectpath+'/sims/1D/TOI2134b/z_100/parker_4000_9.100/converged') #self-consistent T\n", - "\n", - "#calculate spectra\n", + "sim_z0_5000 = tools.Sim(\n", + " tools.projectpath + \"/sims/1D/TOI2134b/z_0/parker_5000_9.280/converged\"\n", + ") # best-fit\n", + "sim_z0_2600 = tools.Sim(\n", + " tools.projectpath + \"/sims/1D/TOI2134b/z_0/parker_2600_8.800/converged\"\n", + ") # self-consistent T\n", + "sim_z100_9700 = tools.Sim(\n", + " tools.projectpath + \"/sims/1D/TOI2134b/z_100/parker_9700_9.680/converged\"\n", + ") # best-fit\n", + "sim_z100_4000 = tools.Sim(\n", + " tools.projectpath + \"/sims/1D/TOI2134b/z_100/parker_4000_9.100/converged\"\n", + ") # self-consistent T\n", + "\n", + "# calculate spectra\n", "wavsHe = np.linspace(10831, 10836, 200)\n", - "spec_z0_5000, _, _ = RT.FinFout(sim_z0_5000, wavsHe, 'He')\n", - "spec_z0_2600, _, _ = RT.FinFout(sim_z0_2600, wavsHe, 'He')\n", - "spec_z100_9700, _, _ = RT.FinFout(sim_z100_9700, wavsHe, 'He')\n", - "spec_z100_4000, _, _ = RT.FinFout(sim_z100_4000, wavsHe, 'He')\n", + "spec_z0_5000, _, _ = RT.FinFout(sim_z0_5000, wavsHe, \"He\")\n", + "spec_z0_2600, _, _ = RT.FinFout(sim_z0_2600, wavsHe, \"He\")\n", + "spec_z100_9700, _, _ = RT.FinFout(sim_z100_9700, wavsHe, \"He\")\n", + "spec_z100_4000, _, _ = RT.FinFout(sim_z100_4000, wavsHe, \"He\")\n", "\n", - "#convert Fin/Fout to excess absorption in %\n", + "# convert Fin/Fout to excess absorption in %\n", "ea_z0_5000 = (np.max(spec_z0_5000) - spec_z0_5000) * 100\n", "ea_z0_2600 = (np.max(spec_z0_2600) - spec_z0_2600) * 100\n", "ea_z100_9700 = (np.max(spec_z100_9700) - spec_z100_9700) * 100\n", "ea_z100_4000 = (np.max(spec_z100_4000) - spec_z100_4000) * 100\n", "\n", - "#convolve to instrument resolution\n", + "# convolve to instrument resolution\n", "ea_z0_5000 = RT.convolve_spectrum_R(wavsHe, ea_z0_5000, 32000)\n", "ea_z0_2600 = RT.convolve_spectrum_R(wavsHe, ea_z0_2600, 32000)\n", "ea_z100_9700 = RT.convolve_spectrum_R(wavsHe, ea_z100_9700, 32000)\n", @@ -10587,1049 +2091,138 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "edb4de21", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute('tabindex', '0');\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;' +\n", - " 'z-index: 2;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'pointer-events: none;' +\n", - " 'position: relative;' +\n", - " 'z-index: 0;'\n", - " );\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box;' +\n", - " 'left: 0;' +\n", - " 'pointer-events: none;' +\n", - " 'position: absolute;' +\n", - " 'top: 0;' +\n", - " 'z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " /* This rescales the canvas back to display pixels, so that it\n", - " * appears correct on HiDPI screens. */\n", - " canvas.style.width = width + 'px';\n", - " canvas.style.height = height + 'px';\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " /* User Agent sniffing is bad, but WebKit is busted:\n", - " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", - " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", - " * The worst that happens here is that they get an extra browser\n", - " * selection when dragging, if this check fails to catch them.\n", - " */\n", - " var UA = navigator.userAgent;\n", - " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", - " if(isWebKit) {\n", - " return function (event) {\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We\n", - " * want to control all of the cursor setting manually through\n", - " * the 'cursor' event from matplotlib */\n", - " event.preventDefault()\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " } else {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " canvas_div.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " canvas_div.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " canvas_div.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.canvas_div.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "function getModifiers(event) {\n", - " var mods = [];\n", - " if (event.ctrlKey) {\n", - " mods.push('ctrl');\n", - " }\n", - " if (event.altKey) {\n", - " mods.push('alt');\n", - " }\n", - " if (event.shiftKey) {\n", - " mods.push('shift');\n", - " }\n", - " if (event.metaKey) {\n", - " mods.push('meta');\n", - " }\n", - " return mods;\n", - "}\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " // from https://stackoverflow.com/q/1114465\n", - " var boundingRect = this.canvas.getBoundingClientRect();\n", - " var x = (event.clientX - boundingRect.left) * this.ratio;\n", - " var y = (event.clientY - boundingRect.top) * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " modifiers: getModifiers(event),\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "with plt.rc_context({\"font.family\": \"serif\", \"mathtext.fontset\": \"dejavuserif\"}):\n", - " fig, axes = plt.subplots(ncols=1, nrows=3, figsize=(4.5,12))\n", + " fig, axes = plt.subplots(ncols=1, nrows=3, figsize=(4.5, 12))\n", "\n", " ##########\n", - " \n", - " plot_joint_constraint_resolved(dT_z0, sigmaT_z0, chisqs_z0, None, \n", - " bounds_T0=(2000,12000), bounds_Mdot = (8.5, 10), \n", - " fig=fig, ax=axes[0], show_colorbar=False)\n", - " \n", - " axes[0].text(0.96,0.96, r'$\\bf{a)}$'+' 91% H, 9% He', ha='right', va='top', transform=axes[0].transAxes)\n", - " axes[0].text(3600,9.7, 'Temperature self-consistency', ha='left', va='top', color=plt.get_cmap('autumn')(0.35))\n", - " axes[0].text(5400,9.24, 'Fit likelihood', ha='left', va='top', color=plt.get_cmap('winter_r')(0.65))\n", + "\n", + " plot_joint_constraint_resolved(\n", + " dT_z0,\n", + " sigmaT_z0,\n", + " chisqs_z0,\n", + " None,\n", + " bounds_T0=(2000, 12000),\n", + " bounds_Mdot=(8.5, 10),\n", + " fig=fig,\n", + " ax=axes[0],\n", + " show_colorbar=False,\n", + " )\n", + "\n", + " axes[0].text(\n", + " 0.96,\n", + " 0.96,\n", + " r\"$\\bf{a)}$\" + \" 91% H, 9% He\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " transform=axes[0].transAxes,\n", + " )\n", + " axes[0].text(\n", + " 3600,\n", + " 9.7,\n", + " \"Temperature self-consistency\",\n", + " ha=\"left\",\n", + " va=\"top\",\n", + " color=plt.get_cmap(\"autumn\")(0.35),\n", + " )\n", + " axes[0].text(\n", + " 5400,\n", + " 9.24,\n", + " \"Fit likelihood\",\n", + " ha=\"left\",\n", + " va=\"top\",\n", + " color=plt.get_cmap(\"winter_r\")(0.65),\n", + " )\n", "\n", " ##########\n", - " \n", - " plot_joint_constraint_resolved(dT_z100, sigmaT_z100, chisqs_z100, None, \n", - " bounds_T0=(2000,12000), bounds_Mdot = (8.5, 10),\n", - " fig=fig, ax=axes[1], show_colorbar=False)\n", "\n", - " axes[1].text(0.96,0.96, r'$\\bf{b)}$'+' 100x solar metallicity', ha='right', va='top', transform=axes[1].transAxes)\n", - " axes[1].text(5100,8.7, 'Temperature self-consistency', ha='left', va='top', color=plt.get_cmap('autumn')(0.35))\n", - " axes[1].text(9000,9.58, 'Fit likelihood', ha='left', va='top', color=plt.get_cmap('winter_r')(0.65))\n", + " plot_joint_constraint_resolved(\n", + " dT_z100,\n", + " sigmaT_z100,\n", + " chisqs_z100,\n", + " None,\n", + " bounds_T0=(2000, 12000),\n", + " bounds_Mdot=(8.5, 10),\n", + " fig=fig,\n", + " ax=axes[1],\n", + " show_colorbar=False,\n", + " )\n", + "\n", + " axes[1].text(\n", + " 0.96,\n", + " 0.96,\n", + " r\"$\\bf{b)}$\" + \" 100x solar metallicity\",\n", + " ha=\"right\",\n", + " va=\"top\",\n", + " transform=axes[1].transAxes,\n", + " )\n", + " axes[1].text(\n", + " 5100,\n", + " 8.7,\n", + " \"Temperature self-consistency\",\n", + " ha=\"left\",\n", + " va=\"top\",\n", + " color=plt.get_cmap(\"autumn\")(0.35),\n", + " )\n", + " axes[1].text(\n", + " 9000,\n", + " 9.58,\n", + " \"Fit likelihood\",\n", + " ha=\"left\",\n", + " va=\"top\",\n", + " color=plt.get_cmap(\"winter_r\")(0.65),\n", + " )\n", "\n", " ##########\n", "\n", - " axes[2].errorbar(obs_wav_toi2134, obs_ea_toi2134, yerr=obs_sig_ea_toi2134, \n", - " fmt='o', color='black', zorder=-100, lw=2)\n", - " axes[2].plot(wavsHe, ea_z0_5000, label=r'H/He, $T_0$=5000', color='dodgerblue', lw=2)\n", - " axes[2].plot(wavsHe, ea_z100_9700, label=r'100x, $T_0$=9700', color='crimson', lw=2)\n", - " axes[2].plot(wavsHe, ea_z0_2600, label=r'H/He, $T_0$=2600', color='dodgerblue', linestyle='dashed', lw=2)\n", - " axes[2].plot(wavsHe, ea_z100_4000, label=r'100x, $T_0$=4000', color='crimson', linestyle='dashed', lw=2)\n", - " axes[2].text(0.96,0.04, r'$\\bf{c)}$', ha='right', va='bottom', transform=axes[2].transAxes)\n", - " axes[2].xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, _: '{:g}'.format(x)))\n", - " axes[2].set_xlabel('Wavelength [Å]')\n", - " axes[2].set_ylabel('Excess absorption [%]')\n", - " axes[2].legend(loc=\"upper right\", edgecolor='none', facecolor='none', framealpha=0, ncols=2)\n", + " axes[2].errorbar(\n", + " obs_wav_toi2134,\n", + " obs_ea_toi2134,\n", + " yerr=obs_sig_ea_toi2134,\n", + " fmt=\"o\",\n", + " color=\"black\",\n", + " zorder=-100,\n", + " lw=2,\n", + " )\n", + " axes[2].plot(\n", + " wavsHe, ea_z0_5000, label=r\"H/He, $T_0$=5000\", color=\"dodgerblue\", lw=2\n", + " )\n", + " axes[2].plot(wavsHe, ea_z100_9700, label=r\"100x, $T_0$=9700\", color=\"crimson\", lw=2)\n", + " axes[2].plot(\n", + " wavsHe,\n", + " ea_z0_2600,\n", + " label=r\"H/He, $T_0$=2600\",\n", + " color=\"dodgerblue\",\n", + " linestyle=\"dashed\",\n", + " lw=2,\n", + " )\n", + " axes[2].plot(\n", + " wavsHe,\n", + " ea_z100_4000,\n", + " label=r\"100x, $T_0$=4000\",\n", + " color=\"crimson\",\n", + " linestyle=\"dashed\",\n", + " lw=2,\n", + " )\n", + " axes[2].text(\n", + " 0.96, 0.04, r\"$\\bf{c)}$\", ha=\"right\", va=\"bottom\", transform=axes[2].transAxes\n", + " )\n", + " axes[2].xaxis.set_major_formatter(\n", + " matplotlib.ticker.FuncFormatter(lambda x, _: \"{:g}\".format(x))\n", + " )\n", + " axes[2].set_xlabel(\"Wavelength [Å]\")\n", + " axes[2].set_ylabel(\"Excess absorption [%]\")\n", + " axes[2].legend(\n", + " loc=\"upper right\", edgecolor=\"none\", facecolor=\"none\", framealpha=0, ncols=2\n", + " )\n", " axes[2].set_ylim(-0.15, 0.55)\n", "\n", " ##########\n", - " \n", + "\n", " plt.show()" ] }, @@ -11640,6 +2233,14 @@ "source": [ "We see that the temperature obtained from the line fit is very different from the self-consistency constraint. In Linssen et al. (2024), we discuss the temperature discrepancy between the line fit and the self-consistency in more detail." ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a340366-3cc8-4764-8773-2222a540ec7d", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -11658,7 +2259,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/examples/predict_UV.ipynb b/examples/predict_UV.ipynb index 61af898..47ea8e9 100644 --- a/examples/predict_UV.ipynb +++ b/examples/predict_UV.ipynb @@ -2,10 +2,35 @@ "cells": [ { "cell_type": "markdown", - "id": "f1846bd0", + "id": "28e6aaa4-3704-474d-a257-62ef4d0c052f", + "metadata": {}, + "source": [ + "# Example: Predict UV" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af8b8894-0d32-4158-ac8d-87fad3c7ad93", "metadata": {}, + "outputs": [], + "source": [ + "import sunbather\n", + "sunbather.firstrun(quiet=True)" + ] + }, + { + "cell_type": "markdown", + "id": "f1846bd0", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ - "# License\n", + "## License\n", "\n", "The code in this notebook is free to be used, edited and redistributed by anyone free of charge. Please cite Linssen et al. (in prep) when making use of _sunbather_ and/or the code in this notebook." ] @@ -15,7 +40,7 @@ "id": "d3b01a8a", "metadata": {}, "source": [ - "# Example goal\n", + "## Example goal\n", "\n", "In this example notebook, we predict the NUV spectrum of WASP-52 b to see if the signatures of escaping metal species are strong enough to be potentially observable with HST/STIS. In the *fit_helium.ipynb* example notebook, we constrained the mass-loss rate and thermospheric temperature of the planet based on metastable helium observations (the examples can be done in any order). Here, we use those constrained values and make NUV predictions. Since we do not know the (upper) atmospheric metallicity of WASP-52 b, we will explore three different models; one assuming solar metallicity, one assuming 10x solar metallicity, and one assuming 10x solar metallicity but a 100x solar magnesium abundance. " ] @@ -25,7 +50,7 @@ "id": "8603e0ac", "metadata": {}, "source": [ - "# Example layout\n", + "## Example layout\n", "\n", "The analysis consists of three main steps:\n", "\n", @@ -41,35 +66,82 @@ "id": "52a4caf3", "metadata": {}, "source": [ - "# Preparation\n", + "## Preparation\n", "\n", "For this exercise, we assume you have all codes set-up. That is; you have downloaded *sunbather* and installed its dependencies (the Python packages, including *p-winds*). You have installed *Cloudy v17.02* and have the path to it stored as your _\\$CLOUDY_PATH_ environmental variable. You have created your \"project\" folder, and have the path to it stored as your _\\$SUNBATHER_PROJECT_PATH_ environmental variable. You have copied the _planets.txt_ file to the project path. These steps are described in more detail in the \"installation\" section of the _sunbather_ wiki.\n", "\n", - "Before *sunbather* can create Parker wind profiles, we need to make sure the parameters of the system are available to the code. The parameters are stored in the _\\$SUNBATHER_PROJECT_PATH/planets.txt_ file, and the parameters of the WASP-52 b system have already been added. If you want to model additional planets, you can simply add lines in the _planets.txt_ file with their parameters, there is no need to replace previous planet parameters. The last column of the _planets.txt_ file specifies the **name** of the stellar SED that we want to use. The SED with exactly this name must be available to *Cloudy*, so it must be placed in its source folder, specifically: _\\$CLOUDY_PATH/data/SED/_. In the _/sunbather/stellar_SEDs/_ folder, we have provided the SED that we are going to use for WASP-52 b. This is the MUSCLES spectrum (France et al. 2016; Youngblood et al. 2016; Loyd et al. 2016) of eps Eri, which is a similar spectral type to WASP-52. The code is very specific about the format of the spectrum, so we refer to the wiki on how to prep your stellar SED for *sunbather*. \n", - "> **The only step you need to take here, is make sure the eps_Eri_binned.spec is in Cloudy's SED folder: $CLOUDY_PATH/data/SED/ (so copy/move it there).**" + "Before *sunbather* can create Parker wind profiles, we need to make sure the parameters of the system are available to the code. The parameters are stored in the _\\$SUNBATHER_PROJECT_PATH/planets.txt_ file, and the parameters of the WASP-52 b system have already been added. If you want to model additional planets, you can simply add lines in the _planets.txt_ file with their parameters, there is no need to replace previous planet parameters. The last column of the _planets.txt_ file specifies the **name** of the stellar SED that we want to use. The SED with exactly this name must be available to *Cloudy*, so it must be placed in its source folder, specifically: _\\$CLOUDY_PATH/data/SED/_. In the _/sunbather/stellar_SEDs/_ folder, we have provided the SED that we are going to use for WASP-52 b. This is the MUSCLES spectrum (France et al. 2016; Youngblood et al. 2016; Loyd et al. 2016) of eps Eri, which is a similar spectral type to WASP-52. The code is very specific about the format of the spectrum, so we refer to the wiki on how to prep your stellar SED for *sunbather*. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f3a65c4-8c80-4dc2-9da8-de8bb27e564c", + "metadata": {}, + "outputs": [], + "source": [ + "# The only step you need to take here, is to ensure the eps_Eri_binned.spec file\n", + "# is in Cloudy's SED folder:\n", + "import os.path\n", + "cloudypath = sunbather.tools.get_cloudy_path()\n", + "os.path.exists(f\"{cloudypath}/data/SED/eps_Eri_binned.spec\")" ] }, { "cell_type": "markdown", - "id": "0f1fd603", + "id": "b6b85abb-2118-4b17-8be6-eafe91bad824", "metadata": {}, "source": [ - "# Step 1: Create Parker wind profiles with p-winds/Cloudy\n", - "\n", - "This step can be done by calling `construct_parker.py` from the command-line with the proper arguments. Running `python construct_parker.py --help` will give an overview of the available arguments. In this module, the atmospheric composition/metallicity only affects the structure through the mean molecular weight $\\mu$. At solar metallicity, the metal content is low enough to not significantly affect $\\mu$, so we will make the Parker wind profile with *p-winds* standalone, assuming a pure H/He composition of 90/10. At 10x solar metallicity, $\\mu$ may be significantly affected by the metal content, so we will make the Parker wind profile with a hybrid *p-winds*/_Cloudy_ calculation. In Step 2 of this example, we will use this profile also for the run with an enhanced magnesium abundance, since $\\mu$ (and hence the structure we calculate here in step 1) will not change much when increasing the abundance of just one metal species.\n", - "\n", - "To make the profile with a 90% hydrogen, 10% helium composition, we pass `-fH 0.9`. We will only make a profile for the constrained temperature of 9200 K and mass-loss rate of $\\dot{M}=10^{11.3}$ g/s, so we pass `-T 9200` and `-Mdot 11.3`. Since we explore different compositions, `construct_parker.py` always expects you to give a folder name `-pdir` where we want to store our Parker profiles. We reccommend using a descriptive name, so in this case we will go with *fH_0.9* and the path where our profiles will be saved is then _$SUNBATHER_PROJECT_PATH/parker_profiles/WASP52b/fH_0.9/_.\n", + "## Step 1: Create Parker wind profiles with p-winds/Cloudy\n", "\n", - "> **The full command to create our first Parker wind model thus becomes - go ahead and run it**:

\n", - "> `python construct_parker.py -plname WASP52b -pdir fH_0.9 -T 9200 -Mdot 11.3 -fH 0.9`\n", + "This step can be done by calling `sunbather.construct_parker.run` with the proper keyword arguments. Calling `help(sunbather.construct_parker.run)` will give an overview of the available arguments. In this module, the atmospheric composition/metallicity only affects the structure through the mean molecular weight $\\mu$. At solar metallicity, the metal content is low enough to not significantly affect $\\mu$, so we will make the Parker wind profile with *p-winds* standalone, assuming a pure H/He composition of 90/10. At 10x solar metallicity, $\\mu$ may be significantly affected by the metal content, so we will make the Parker wind profile with a hybrid *p-winds*/_Cloudy_ calculation. In Step 2 of this example, we will use this profile also for the run with an enhanced magnesium abundance, since $\\mu$ (and hence the structure we calculate here in step 1) will not change much when increasing the abundance of just one metal species.\n", "\n", + "To make the profile with a 90% hydrogen, 10% helium composition, we pass `fraction_hydrogen=0.9`. We will only make a profile for the constrained temperature of 9200 K and mass-loss rate of $\\dot{M}=10^{11.3}$ g/s, so we pass `temp=9200` and `mdot=11.3`. Since we explore different compositions, `construct_parker` always expects you to give a folder name `pdir` where we want to store our Parker profiles. We reccommend using a descriptive name, so in this case we will go with *fH_0.9* and the path where our profiles will be saved is then _$SUNBATHER_PROJECT_PATH/parker_profiles/WASP52b/fH_0.9/_." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76969515-c0a5-432e-af53-eee9d48934b5", + "metadata": {}, + "outputs": [], + "source": [ + "# The full command to create our first Parker wind model thus becomes - go ahead and run it:\n", + "import sunbather.construct_parker\n", + "sunbather.construct_parker.run(\n", + " plname=\"WASP52b\", pdir=\"fH_0.9\", temp=9200, mdot=11.3, fraction_hydrogen=0.9, overwrite=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8e94c6ec-3826-48fd-b910-bd13c1f8571d", + "metadata": {}, + "source": [ "For this set of parameters, this command should take on the order of 1 second (but it depends on your machine).\n", "\n", - "To make the 10x solar metallicity profile, instead of the `-fH` argument, we use `-z 10`. We don't want this profile to overwrite the file we just saved for H/He only, so we provide a different `-pdir`. \n", - "\n", - "> **The full command to create our second Parker wind model thus becomes - go ahead and run it**:

\n", - "> `python construct_parker.py -plname WASP52b -pdir z_10 -T 9200 -Mdot 11.3 -z 10`\n", - "\n", + "To make the 10x solar metallicity profile, instead of the `-fH` argument, we use `-z 10`. We don't want this profile to overwrite the file we just saved for H/He only, so we provide a different `-pdir`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2bab02aa-13cf-4485-a6fd-47aca1cdef46", + "metadata": {}, + "outputs": [], + "source": [ + "# The full command to create our second Parker wind model thus becomes - go ahead and run it:\n", + "zdict = sunbather.tools.get_zdict(z=10)\n", + "sunbather.construct_parker.run(\n", + " plname=\"WASP52b\", pdir=\"z_10\", temp=9200, mdot=11.3, zdict=zdict, overwrite=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2530de2e-176f-4015-a50e-37767a62e074", + "metadata": {}, + "source": [ "For this set of parameters, this command should take on the order of 3 minutes (but it depends on your machine). If it exits with the error that the _Cloudy_ simulation went wrong (because of negative calcium abundance), try again while passing `-zelem Ca=0`.\n", "\n", "In the _$SUNBATHER_PROJECT_PATH/parker_profiles/WASP52b/_ folder, there should now be 2 subfolders, each containing a .txt file with the isothermal Parker wind structure, feel free to inspect them!" @@ -77,24 +149,77 @@ }, { "cell_type": "markdown", - "id": "74bf94f4", + "id": "e7f792ef-e15b-4374-8672-454fd471f168", "metadata": {}, "source": [ - "# Step 2: Run the Parker wind profiles through Cloudy\n", - "\n", - "This step can be done by calling `convergeT_parker.py` from the command-line with the proper arguments The $T_0$ and $\\dot{M}$ commands are the same as in Step 1 of this example. We need to specify a folder name where we want to save our *Cloudy* simulations. For the solar composition we will use `-dir z_1`. We also need to specify the folder where we want to read the Parker wind profiles from, so `-pdir fH_0.9`. The last thing we need to think about, is for which atomic/ionic species we want to save *Cloudy's* output. Since many different metal species absorb in the UV, we will save everything that's available, which is the default behavior of the `-save_sp` flag (so we do not need to specify it here), but does result in a rather large file size of ~5 MB.\n", - "\n", - "> **The command to run our solar composition Parker wind model through *Cloudy* thus becomes - go ahead and run it**:

\n", - "> `python convergeT_parker.py -plname WASP52b -dir z_1 -pdir fH_0.9 -T 9200 -Mdot 11.3`\n", - "\n", - "For the 10x solar metallicity model, we need to make sure we specify `-pdir z_10` to read the Parker wind profile from the correct folder. We will save the _Cloudy_ runs in `-dir z_10`, and we also need to tell _Cloudy_ to actually use a 10x solar metallicity with `-z 10`. \n", - "> **The command to run our 10x solar metallicity Parker wind model through *Cloudy* thus becomes - go ahead and run it**:

\n", - "> `python convergeT_parker.py -plname WASP52b -dir z_10 -pdir z_10 -T 9200 -Mdot 11.3 -z 10`\n", - "\n", - "Finally for the model with 10x solar metallicity, but 100x solar magnesium abundance, we choose a different output folder `-dir z_10_Mg10`, but we can use the same `-pdir` as before (see explanation under Step 1 of this example). Since we already have an overall scaling factor of 10 for all the metals, we have to pass `-zelem Mg=10` to scale magnesium by another factor of 10 to get a 100x solar abundance.\n", - "> **The command to run our 10x solar metallicity with enhanced magnesium Parker wind model through *Cloudy* thus becomes - go ahead and run it**:

\n", - "> `python convergeT_parker.py -plname WASP52b -dir z_10_Mg10 -pdir z_10 -T 9200 -Mdot 11.3 -z 10 -zelem Mg=10`\n", + "## Step 2: Run the Parker wind profiles through Cloudy\n", "\n", + "This step can be done by calling `convergeT_parker.py` from the command-line with the proper arguments The $T_0$ and $\\dot{M}$ commands are the same as in Step 1 of this example. We need to specify a folder name where we want to save our *Cloudy* simulations. For the solar composition we will use `-dir z_1`. We also need to specify the folder where we want to read the Parker wind profiles from, so `-pdir fH_0.9`. The last thing we need to think about, is for which atomic/ionic species we want to save *Cloudy's* output. Since many different metal species absorb in the UV, we will save everything that's available, which is the default behavior of the `-save_sp` flag (so we do not need to specify it here), but does result in a rather large file size of ~5 MB." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4785b7d6-0386-4abf-88e1-30bc2c95e53e", + "metadata": {}, + "outputs": [], + "source": [ + "# The command to run our solar composition Parker wind model through *Cloudy* thus becomes\n", + "# - go ahead and run it:\n", + "import sunbather.convergeT_parker\n", + "sunbather.convergeT_parker.run(\n", + " plname=\"WASP52b\", workingdir=\"z_1\", pdir=\"fH_0.9\", temp=9200, mdot=11.3, overwrite=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9a58d9f3-0018-48fb-af55-c59415a53ec2", + "metadata": {}, + "source": [ + "For the 10x solar metallicity model, we need to make sure we specify `-pdir z_10` to read the Parker wind profile from the correct folder. We will save the _Cloudy_ runs in `-dir z_10`, and we also need to tell _Cloudy_ to actually use a 10x solar metallicity with `-z 10`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aba44b46-fd01-4726-8253-fd171bbfaeb0", + "metadata": {}, + "outputs": [], + "source": [ + "# **The command to run our 10x solar metallicity Parker wind model through *Cloudy* thus becomes - go ahead and run it**:

\n", + "zdict = sunbather.tools.get_zdict(z=10)\n", + "sunbather.convergeT_parker.run(\n", + " plname=\"WASP52b\", workingdir=\"z_10\", pdir=\"z_10\", temp=9200, mdot=11.3, zdict=zdict,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1aec36da-a707-4f61-879e-6469ad98f0f2", + "metadata": {}, + "source": [ + "Finally for the model with 10x solar metallicity, but 100x solar magnesium abundance, we choose a different output folder `-workingdir z_10_Mg10`, but we can use the same `-pdir` as before (see explanation under Step 1 of this example). Since we already have an overall scaling factor of 10 for all the metals, we have to pass `-zelem Mg=10` to scale magnesium by another factor of 10 to get a 100x solar abundance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea96e73a-aa59-4cb8-9977-2753e6c0a5df", + "metadata": {}, + "outputs": [], + "source": [ + "# **The command to run our 10x solar metallicity with enhanced magnesium Parker wind model through *Cloudy* thus becomes - go ahead and run it**:

\n", + "sunbather.convergeT_parker.run(\n", + " plname=\"WASP52b\", workingdir=\"z_10_Mg10\", pdir=\"z_10\", temp=9200, mdot=11.3, z=10, zelem={\"Mg\": 10},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d63ec328-f8f6-4ca6-8b59-b5abb22749d3", + "metadata": {}, + "source": [ "For these parameters, all commands together should take on the order of 35 minutes (but it depends on your machine). In the *$SUNBATHER_PROJECT_PATH/sims/1D/WASP52b/fH_0.9/* and */.../z_10/* and */.../z_10_Mg10/* folders, there should now be a sub-folder (or more if you also did the _fit_helium.ipynb_ example notebook), with the output of the _Cloudy_ simulation, feel free to inspect the files! The _converged.png_ file shows the converged temperature structure, and the other _converged.*_ files are the _Cloudy_ output files." ] }, @@ -103,7 +228,7 @@ "id": "84fa611b", "metadata": {}, "source": [ - "# Step 3: Make NUV transit spectra\n", + "## Step 3: Make NUV transit spectra\n", "\n", "To make transit spectra, we can make use of the `FinFout()` function in the `RT.py` module. This code does not run from the command line, as you may want to process/plot/save the resulting transit spectrum in different ways.\n", "\n", @@ -112,19 +237,27 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "c6622346", "metadata": {}, "outputs": [], "source": [ - "import sys\n", - "sys.path.append('/Users/dion/src/sunbather/src/') #add your path to /sunbather/src/ here\n", - "\n", - "import tools\n", - "import RT\n", - "\n", "import matplotlib.pyplot as plt\n", - "import numpy as np" + "import numpy as np\n", + "\n", + "from sunbather import tools\n", + "from sunbather import RT" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f9f8e7f-1d2e-4141-a877-fee93db5168d", + "metadata": {}, + "outputs": [], + "source": [ + "# Get the project path, and keep this constant during the example\n", + "projectpath = tools.get_sunbather_project_path()" ] }, { @@ -137,59 +270,28 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "e434fa77", + "execution_count": null, + "id": "2e1e0593-e082-455d-b69e-77f659b671fc", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Ti is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Ti is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Ti+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Ti+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species V is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all V is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species V+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all V+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species V+2 is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all V+2 is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Cr is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Cr is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Co is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Co is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Cu+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Cu+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Zn is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Zn is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "wavs = RT.constantR_wavs(2000,3000,100000) #set up wavelength grid at spectral resolution of 100,000\n", - "W52b_solar = tools.Sim(tools.projectpath+'/sims/1D/WASP52b/z_1/parker_9200_11.300/converged') #load simulation\n", - "#we can quickly get a list of all species as follows:\n", - "all_species = tools.get_specieslist(max_ion=2) #this includes all species up to doubly ionized - higher is not neccessary here\n", - "transit_spectrum_solar, _, _ = RT.FinFout(W52b_solar, wavs, all_species) #do RT\n", + "wavs = RT.constantR_wavs(\n", + " 2000, 3000, 100000\n", + ") # set up wavelength grid at spectral resolution of 100,000\n", + "W52b_solar = tools.Sim(\n", + " f\"{projectpath}/sims/1D/WASP52b/z_1/parker_9200_11.300/converged\"\n", + ") # load simulation\n", + "# we can quickly get a list of all species as follows:\n", + "all_species = tools.get_specieslist(\n", + " max_ion=2\n", + ") # this includes all species up to doubly ionized - higher is not neccessary here\n", + "transit_spectrum_solar, _, _ = RT.FinFout(W52b_solar, wavs, all_species) # do RT\n", "\n", - "fig, ax = plt.subplots(1, figsize=(12,7))\n", + "fig, ax = plt.subplots(1, figsize=(12, 7))\n", "ax.plot(wavs, transit_spectrum_solar)\n", - "ax.set_xlabel('Wavelength [Å]')\n", - "ax.set_ylabel(r'$F_{in} / F_{out}$')\n", - "ax.set_title(r'WASP-52 b with T=9200 and $\\dot{M}=10^{11.3}$, solar composition')\n", + "ax.set_xlabel(\"Wavelength [Å]\")\n", + "ax.set_ylabel(r\"$F_{in} / F_{out}$\")\n", + "ax.set_title(r\"WASP-52 b with T=9200 and $\\dot{M}=10^{11.3}$, solar composition\")\n", "plt.show()" ] }, @@ -203,58 +305,21 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "d25a07ca", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Ti is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Ti is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Ti+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Ti+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species V is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all V is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species V+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all V+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species V+2 is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all V+2 is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Cr is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Cr is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Mn+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Mn+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Co is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Co is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Cu+ is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Cu+ is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n", - "/Users/dion/src/sunbather/src/RT.py:508: UserWarning: Your requested species Zn is not resolved into multiple energy levels by Cloudy. I will make the spectrum assuming all Zn is in the ground-state.\n", - " warnings.warn(f\"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. \" + \\\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "W52b_z_10 = tools.Sim(tools.projectpath+'/sims/1D/WASP52b/z_10/parker_9200_11.300/converged') #load simulation\n", - "transit_spectrum_z_10, _, _ = RT.FinFout(W52b_z_10, wavs, all_species) #do RT\n", + "W52b_z_10 = tools.Sim(\n", + " projectpath + \"/sims/1D/WASP52b/z_10/parker_9200_11.300/converged\"\n", + ") # load simulation\n", + "transit_spectrum_z_10, _, _ = RT.FinFout(W52b_z_10, wavs, all_species) # do RT\n", "\n", - "fig, ax = plt.subplots(1, figsize=(12,7))\n", + "fig, ax = plt.subplots(1, figsize=(12, 7))\n", "ax.plot(wavs, transit_spectrum_z_10)\n", - "ax.set_xlabel('Wavelength [Å]')\n", - "ax.set_ylabel(r'$F_{in} / F_{out}$')\n", - "ax.set_title(r'WASP-52 b with T=9200 and $\\dot{M}=10^{11.3}$, 10x solar metallicity')\n", + "ax.set_xlabel(\"Wavelength [Å]\")\n", + "ax.set_ylabel(r\"$F_{in} / F_{out}$\")\n", + "ax.set_title(r\"WASP-52 b with T=9200 and $\\dot{M}=10^{11.3}$, 10x solar metallicity\")\n", "plt.show()" ] }, @@ -270,37 +335,28 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "4c2128fc", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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mcOhhx66fZWqw29/+1uOPPJI0uk0t912W9Wx7373u+y///7sv//+fPe73y2nP/rooxx22GHst99+XHrppcSCShu7ACYw2oAxmTEA/GnjnwCY2DGxlcXZ5dmZ0ObQ/PDmmzZtKocUeeaZZ7avMlthxowZXH/99bz73e+uSl+7di1XXnklDz/8MI888ghXXnkl69atA+Ciiy7iO9/5Trkev/jFL5pWHqP9sYl7bcDkzskAPLv22ap9A770yJfK7dIsDpp4EP90zD81lHd7Q5tD88Obn3XWWdxyyy1cdtll3HTTTZx77rnccMMNAPT29nL++efz1FNPceCBB7JixQrmzZvH3Llzt1nOmTNnAgyq1913381JJ53ExImu43LSSSfxi1/8ghNPPJGNGzdy3HHHAXDeeefx4x//mNNOO22b9zKSgQmMNmBS5yQEYdHaReV9o/XUhjaHnQs+uKPhzd/xjndwwQUXcNlll/HTn/6U73//+2WBcc011zBhwgQWLVrEU089xZw5c8rnnX322SxevHjQ9T7xiU9w3nnnbXc5ly9fzrRp0walG7sOJjDagEwqw8SOiazpX8PY7FhyQa7VRWobGtUEhoPa0ObQmvDmkyZNYsKECdx8880cfPDBdHVVfFy///3v+Yd/+AcADj30UA4//PDysUbiXhnG9mACo03YrWs31vSvMXNUm1AvtDm0Lrz52WefzUc+8hGuv/76hvKXztkRDWPq1Kncd999VeU88cQTmTp1KsuWLduh8hvJwARGm1ASFCYwWs9Qoc1h+MKbH3TQQTz77NC+mjPPPJOVK1dyyimnsGLFinL68ccfz6233sob3/hGFi1axJNPVpaz3VEN45RTTuEzn/lM2dH9y1/+ki984QtMnDiRnp4eHnroIY499li+973vcckll+zQPYzRiY2SahOmdE0BYHKHCYxWszOhzWH7w5uvXr16m8NTx44dyz/90z+RzWar0i+++GJWrVrF7Nmz+ed//mcOOeQQxo0b11A558+fz7Rp0/jBD37Ahz70IQ455BAAJk6cyGc/+1mOPvpojj76aK644oqyA/yaa67hAx/4APvttx/77ruvObx3MSSp46jnzp2rCxYsaHUxGubhlQ9z+3O3c/6h5zN70tBmjV2BZ555hoMPPrjVxRgxfvazn7FkyZK6IdG3RRRFFAoFOjo6ePHFF3nz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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "wavs2 = np.linspace(2795, 2805, num=500) #set up a wav-grid around the Mg II doublet\n", - "W52b_z_10_Mg10 = tools.Sim(tools.projectpath+'/sims/1D/WASP52b/z_10_Mg10/parker_9200_11.300/converged') #load simulation\n", - "transit_spectrum_z_10_Mg10, _, _ = RT.FinFout(W52b_z_10_Mg10, wavs2, 'Mg+', width_fac=20.) #do RT\n", + "wavs2 = np.linspace(2795, 2805, num=500) # set up a wav-grid around the Mg II doublet\n", + "W52b_z_10_Mg10 = tools.Sim(\n", + " projectpath + \"/sims/1D/WASP52b/z_10_Mg10/parker_9200_11.300/converged\"\n", + ") # load simulation\n", + "transit_spectrum_z_10_Mg10, _, _ = RT.FinFout(\n", + " W52b_z_10_Mg10, wavs2, \"Mg+\", width_fac=20.0\n", + ") # do RT\n", "\n", "fig, ax = plt.subplots(1)\n", - "ax.plot(wavs, transit_spectrum_solar, label='Solar')\n", - "ax.plot(wavs, transit_spectrum_z_10, label='Z=10')\n", - "ax.plot(wavs2, transit_spectrum_z_10_Mg10, label='Z=10, Mg=100')\n", + "ax.plot(wavs, transit_spectrum_solar, label=\"Solar\")\n", + "ax.plot(wavs, transit_spectrum_z_10, label=\"Z=10\")\n", + "ax.plot(wavs2, transit_spectrum_z_10_Mg10, label=\"Z=10, Mg=100\")\n", "ax.set_xlim(wavs2[0], wavs2[-1])\n", - "ax.legend(loc='best')\n", - "ax.set_title(r'WASP-52 b Mg II doublet at different metallicities')\n", - "ax.set_xlabel('Wavelength [Å]')\n", - "ax.set_ylabel(r'$F_{in} / F_{out}$')\n", + "ax.legend(loc=\"best\")\n", + "ax.set_title(r\"WASP-52 b Mg II doublet at different metallicities\")\n", + "ax.set_xlabel(\"Wavelength [Å]\")\n", + "ax.set_ylabel(r\"$F_{in} / F_{out}$\")\n", "plt.show()" ] }, @@ -317,7 +373,7 @@ "id": "8385ea04", "metadata": {}, "source": [ - "### References\n", + "## References\n", "\n", "France, K., Loyd, R. O. P., Youngblood, A., et al. 2016, ApJ, 820, 89\n", "\n", @@ -343,7 +399,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5eb17de --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools >= 75.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "sunbather" +version = "1.0.0a1" +# dynamic = ["version"] +dependencies = [ + "numpy >= 1.24.3, <3", + "pandas >= 1.1.4, <3", + "matplotlib >= 3.7.1, <4", + "scipy >= 1.9.0, <1.14", + "astropy >= 5.0, <7", + "p-winds >= 1.3.4, <2", +] +requires-python = ">= 3.9" +authors = [ + {name = "Dion Linssen", email = "d.c.linssen@uva.nl"}, + {name = "Antonija Oklopčić", email = "a.oklopcic@uva.nl"}, +] +description = "sunbather" +readme = {file = "README.md", content-type = "text/markdown"} +license = {file = "LICENSE"} +keywords = ["astrophysics"] + +[project.urls] +Issues = "https://github.com/antonpannekoek/sunbather/issues" +Repository = "https://github.com/antonpannekoek/sunbather" + +[tool.pylint] +max-line-length = 88 diff --git a/src/construct_parker.py b/src/construct_parker.py deleted file mode 100644 index cd77028..0000000 --- a/src/construct_parker.py +++ /dev/null @@ -1,689 +0,0 @@ -#sunbather imports -import tools - -#other imports -import numpy as np -import os -import time -from shutil import copyfile -import matplotlib.pyplot as plt -import astropy.units as u -from p_winds import tools as pw_tools -from p_winds import parker as pw_parker -from p_winds import hydrogen as pw_hydrogen -from scipy.integrate import simpson, trapz -from scipy.interpolate import interp1d -import argparse -import multiprocessing -import traceback -import warnings - - -def cloudy_spec_to_pwinds(SEDfilename, dist_SED, dist_planet): - """ - Reads a spectrum file in the format that we give it to Cloudy, namely - angstroms and monochromatic flux (i.e., nu*F_nu or lambda*F_lambda) units. - and converts it to a spectrum dictionary that p-winds uses. - This is basically an equivalent of the p_winds.parker.make_spectrum_from_file() function. - - Parameters - ---------- - SEDfilename : str - Full path + filename of the SED file. SED file must be in the sunbather/Cloudy - standard units, namely wavelengths in Å and lambda*F_lambda flux units. - dist_SED : numeric - Distance from the source at which the SED is defined (typically 1 AU). - Must have the same units as dist_planet. - dist_planet : numeric - Distance from the source to which the SED must be scaled - (typically semi-major axis - total atmospheric height). Must have the - same units as dist_SED. - - Returns - ------- - spectrum : dict - SED at the planet distance in the dictionary format that p-winds expects. - """ - - with open(SEDfilename, 'r') as f: - for line in f: - if not line.startswith('#'): #skip through the comments at the top - assert ('angstrom' in line) or ('Angstrom' in line) #verify the units - assert 'nuFnu' in line #verify the units - first_spec_point = np.array(line.split(' ')[:2]).astype(float) - break - rest_data = np.genfromtxt(f, skip_header=1) - - SED = np.concatenate(([first_spec_point], rest_data)) #rejoin with the first spectrum point that we read separately - - flux = SED[:,1] / SED[:,0] #from nuFnu = wavFwav to Fwav in erg s-1 cm-2 A-1 - flux = flux * (dist_SED / dist_planet)**2 #scale to planet distance - - assert SED[1,0] > SED[0,0] #check ascending wavelengths - - #make a dictionary like p_winds expects it - spectrum = {'wavelength':SED[:,0], - 'flux_lambda':flux, - 'wavelength_unit':u.angstrom, - 'flux_unit':u.erg / u.s / u.cm ** 2 / u.angstrom, - 'SEDname':SEDfilename.split('/')[-1][:-5]} #SEDname added by me (without extension) - - return spectrum - - -def calc_neutral_mu(zdict): - """Calculates the mean particle mass assuming a completely neutral (i.e., atomic) - gas, for a given composition (specified through elemental scale factors that - can be converted into abundances). - - Parameters - ---------- - zdict : dict - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - - Returns - ------- - neutral_mu : float - Mean particle mass in units of amu. - """ - - abundances = tools.get_abundances(zdict) - neutral_mu = tools.calc_mu(1., 0., abundances=abundances) #set ne=0 so completely neutral - - return neutral_mu - - -def save_plain_parker_profile(planet, Mdot, T, spectrum, h_fraction=0.9, - pdir='fH_0.9', overwrite=False, no_tidal=False, altmax=20): - """ - Uses the p-winds code (dos Santos et al. 2022). - Runs p-winds and saves a 'pprof' txt file with the r, rho, v, mu structure. - This function uses p-winds standalone and can thus only calculate H/He atmospheres. - Most of this code is taken from the p-winds tutorial found via the github: - https://colab.research.google.com/drive/1mTh6_YEgCRl6DAKqnmRp2XMOW8CTCvm7?usp=sharing - - Sometimes when the solver cannot find a solution, you may want to change - initial_f_ion to 0.5 or 1.0. - - Parameters - ---------- - planet : tools.Planet - Planet parameters. - Mdot : str or numeric - log of the mass-loss rate in units of g s-1. - T : str or numeric - Temperature in units of K. - spectrum : dict - SED at the planet distance in the dictionary format that p-winds expects. - Can be made with cloudy_spec_to_pwinds(). - h_fraction : float, optional - Hydrogen abundance expressed as a fraction of the total, by default 0.9 - pdir : str, optional - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. By default 'fH_0.9'. - overwrite : bool, optional - Whether to overwrite existing models, by default False. - notidal : bool, optional - Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et al. (2024). - See also Appendix D of Vissapragada et al. (2022) for the p-winds implementation. - Default is False, i.e. tidal gravity incluced. - altmax : int, optional - Maximum altitude of the profile in units of the planet radius. By default 20. - """ - - Mdot = float(Mdot) - T = int(T) - - save_name = tools.projectpath+'/parker_profiles/'+planet.name+'/'+pdir+'/pprof_'+planet.name+'_T='+str(T)+'_M='+ \ - "%.3f" %Mdot +".txt" - if os.path.exists(save_name) and not overwrite: - print("Parker profile already exists and overwrite = False:", planet.name, pdir, "%.3f" %Mdot, T) - return #this quits the function but if we're running a grid, it doesn't quit the whole Python code - - R_pl = planet.R / tools.RJ #convert from cm to Rjup - M_pl = planet.M / tools.MJ #convert from g to Mjup - - m_dot = 10 ** Mdot # Total atmospheric escape rate in g / s - r = np.logspace(0, np.log10(altmax), 1000) # Radial distance profile in unit of planetary radii - - # A few assumptions about the planet's atmosphere - he_fraction = 1 - h_fraction # He number fraction - he_h_fraction = he_fraction / h_fraction - mean_f_ion = 0.0 # Mean ionization fraction (will be self-consistently calculated later) - mu_0 = (1 + 4 * he_h_fraction) / (1 + he_h_fraction + mean_f_ion) - # mu_0 is the constant mean molecular weight (assumed for now, will be updated later) - - initial_f_ion = 0. - f_r, mu_bar = pw_hydrogen.ion_fraction(r, R_pl, T, h_fraction, - m_dot, M_pl, mu_0, - spectrum_at_planet=spectrum, exact_phi=True, - initial_f_ion=initial_f_ion, relax_solution=True, - return_mu=True, atol=1e-8, rtol=1e-5) - - vs = pw_parker.sound_speed(T, mu_bar) # Speed of sound (km/s, assumed to be constant) - if no_tidal: - rs = pw_parker.radius_sonic_point(M_pl, vs) # Radius at the sonic point (jupiterRad) - rhos = pw_parker.density_sonic_point(m_dot, rs, vs) # Density at the sonic point (g/cm^3) - r_array = r * R_pl / rs - v_array, rho_array = pw_parker.structure(r_array) - else: - Mstar = planet.Mstar / tools.Msun #convert from g to Msun - a = planet.a / tools.AU #convert from cm to AU - rs = pw_parker.radius_sonic_point_tidal(M_pl, vs, Mstar, a) #radius at the sonic point (jupiterRad) - rhos = pw_parker.density_sonic_point(m_dot, rs, vs) # Density at the sonic point (g/cm^3) - r_array = r * R_pl / rs - v_array, rho_array = pw_parker.structure_tidal(r_array, vs, rs, M_pl, Mstar, a) - mu_array = ((1-h_fraction)*4.0 + h_fraction)/(h_fraction*(1+f_r)+(1-h_fraction)) #this assumes no Helium ionization - - save_array = np.column_stack((r*planet.R, rho_array*rhos, v_array*vs*1e5, mu_array)) - np.savetxt(save_name, save_array, delimiter='\t', header=f"hydrogen fraction: {h_fraction:.3f}\nalt rho v mu") - print("Parker wind profile done:", save_name) - - launch_velocity = v_array[0] #velocity at Rp in units of sonic speed - - if launch_velocity > 1: - warnings.warn(f"This Parker wind profile is supersonic already at Rp: {save_name}") - - -def save_temp_parker_profile(planet, Mdot, T, zdict, pdir, - mu_bar, mu_struc=None, no_tidal=False, altmax=20): - """ - Uses the p-winds code (dos Santos et al. 2022) - Runs p_winds and saves a 'pprof' txt file with the r, rho, v, mu structure. - The difference with save_plain_parker_profile() is that this function can - be given a mu_bar value (e.g. from what Cloudy reports) and calculate a - Parker wind profile based on that. - Most of this code is taken from the tutorial found via the github: - https://colab.research.google.com/drive/1mTh6_YEgCRl6DAKqnmRp2XMOW8CTCvm7?usp=sharing - - Parameters - ---------- - planet : tools.Planet - Object storing the planet parameters. - Mdot : str or numeric - log of the mass-loss rate in units of g s-1. - T : str or numeric - Temperature in units of K. - zdict : dict - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - mu_bar : float - Weighted mean of the mean particle mass. Based on Eq. A.3 of Lampon et al. (2020). - mu_struc : numpy.ndarray, optional - Mean particle mass profile, must be provided if mu_bar is None. - Typically, this is a mu(r)-profile as given by Cloudy. By default None. - no_tidal : bool, optional - Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et al. (2024). - See also Appendix D of Vissapragada et al. (2022) for the p-winds implementation. - Default is False, i.e. tidal gravity included. - altmax : int, optional - Maximum altitude of the profile in units of the planet radius. By default 20. - - Returns - ------- - save_name : str - Full path + filename of the saved Parker wind profile file. - launch_velocity : float - Velocity at the planet radius in units of the sonic speed. If it is larger than 1, - the wind is "launched" already supersonic, and hence the assumption of a transonic - wind is not valid anymore. - """ - - Mdot = float(Mdot) - T = int(T) - - R_pl = planet.R / tools.RJ #convert from cm to Rjup - M_pl = planet.M / tools.MJ #convert from g to Mjup - - m_dot = 10 ** Mdot # Total atmospheric escape rate in g / s - r = np.logspace(0, np.log10(altmax), 1000) # Radial distance profile in unit of planetary radii - - assert np.abs(mu_struc[0,0] - 1.) < 0.03 and np.abs(mu_struc[-1,0] - altmax) < 0.0001, "Looks like Cloudy didn't simulate to 1Rp: "+str(mu_struc[0,0]) #ensure safe extrapolation - mu_array = interp1d(mu_struc[:,0], mu_struc[:,1], fill_value='extrapolate')(r) - - vs = pw_parker.sound_speed(T, mu_bar) # Speed of sound (km/s, assumed to be constant) - if no_tidal: - rs = pw_parker.radius_sonic_point(M_pl, vs) # Radius at the sonic point (jupiterRad) - rhos = pw_parker.density_sonic_point(m_dot, rs, vs) # Density at the sonic point (g/cm^3) - r_array = r * R_pl / rs - v_array, rho_array = pw_parker.structure(r_array) - else: - Mstar = planet.Mstar / tools.Msun #convert from g to Msun - a = planet.a / tools.AU #convert from cm to AU - rs = pw_parker.radius_sonic_point_tidal(M_pl, vs, Mstar, a) #radius at the sonic point (jupiterRad) - rhos = pw_parker.density_sonic_point(m_dot, rs, vs) # Density at the sonic point (g/cm^3) - r_array = r * R_pl / rs - v_array, rho_array = pw_parker.structure_tidal(r_array, vs, rs, M_pl, Mstar, a) - - save_array = np.column_stack((r*planet.R, rho_array*rhos, v_array*vs*1e5, mu_array)) - save_name = tools.projectpath+'/parker_profiles/'+planet.name+'/'+pdir+'/temp/pprof_'+planet.name+'_T='+str(T)+'_M='+"%.3f" %Mdot +".txt" - zdictstr = "abundance scale factors relative to solar:" - for sp in zdict.keys(): - zdictstr += " "+sp+"="+"%.1f" %zdict[sp] - np.savetxt(save_name, save_array, delimiter='\t', header=zdictstr+"\nalt rho v mu") - - launch_velocity = v_array[0] #velocity at Rp in units of sonic speed - - return save_name, launch_velocity - - -def run_parker_with_cloudy(filename, T, planet, zdict): - """Runs an isothermal Parker wind profile through Cloudy, using the isothermal temperature profile. - - Parameters - ---------- - filename : str - Full path + filename of the isothermal Parker wind profile. - Typically $SUNBATHER_PROJECT_PATH/parker_profiles/*planetname*/*pdir*/*filename* - T : numeric - Isothermal temperature value. - planet : tools.Planet - Object storing the planet parameters. - zdict : dict - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - - Returns - ------- - simname : str - Full path + name of the Cloudy simulation file without file extension. - pprof : pandas.DataFrame - Radial density, velocity and mean particle mass profiles of the isothermal Parker wind profile. - """ - - pprof = tools.read_parker('', '', '', '', filename=filename) - - altmax = pprof.alt.iloc[-1] / planet.R #maximum altitude of the profile in units of Rp - alt = pprof.alt.values - hden = tools.rho_to_hden(pprof.rho.values, abundances=tools.get_abundances(zdict)) - dlaw = tools.alt_array_to_Cloudy(alt, hden, altmax, planet.R, 1000, log=True) - - nuFnu_1AU_linear, Ryd = tools.get_SED_norm_1AU(planet.SEDname) - nuFnu_a_log = np.log10(nuFnu_1AU_linear / ((planet.a - altmax*planet.R)/tools.AU)**2) - - simname = filename.split('.txt')[0] - tools.write_Cloudy_in(simname, title='Simulation of '+filename, overwrite=True, - flux_scaling=[nuFnu_a_log, Ryd], SED=planet.SEDname, - dlaw=dlaw, double_tau=True, cosmic_rays=True, zdict=zdict, constantT=T, outfiles=['.ovr']) - - tools.run_Cloudy(simname) - - return simname, pprof - - -def calc_mu_bar(sim): - """ - Calculates the weighted mean of the radial mean particle mass profile, - according to Eq. A.3 of Lampon et al. (2020). Code adapted from - p_winds.parker.average_molecular_weight(). - - Parameters - ---------- - sim : tools.Sim - Cloudy simulation output object. - - Returns - ------- - mu_bar : float - Weighted mean of the mean particle mass. - """ - - # Converting units - m_planet = sim.p.M / 1000. #planet mass in kg - r = sim.ovr.alt.values[::-1] / 100. # Radius profile in m - v_r = sim.ovr.v.values[::-1] / 100. # Velocity profile in unit of m / s - temperature = sim.ovr.Te.values[0] # (Isothermal) temperature in units of K - - # Physical constants - k_b = 1.380649e-23 # Boltzmann's constant in J / K - grav = 6.6743e-11 # Gravitational constant in m ** 3 / kg / s ** 2 - - # Mean molecular weight in function of radial distance r - mu_r = sim.ovr.mu.values[::-1] - - # Eq. A.3 of Lampón et al. 2020 is a combination of several integrals, which - # we calculate here - int_1 = simpson(mu_r / r ** 2, r) - int_2 = simpson(mu_r * v_r, v_r) - int_3 = trapz(mu_r, 1 / mu_r) - int_4 = simpson(1 / r ** 2, r) - int_5 = simpson(v_r, v_r) - int_6 = 1 / mu_r[-1] - 1 / mu_r[0] - term_1 = grav * m_planet * int_1 + int_2 + k_b * temperature * int_3 - term_2 = grav * m_planet * int_4 + int_5 + k_b * temperature * int_6 - mu_bar = term_1 / term_2 - - return mu_bar - - -def save_cloudy_parker_profile(planet, Mdot, T, zdict, pdir, - convergence=0.01, maxit=7, cleantemp=False, - overwrite=False, verbose=False, - no_tidal=False, altmax=20): - """ - Calculates an isothermal Parker wind profile with any composition by iteratively - running the p-winds code (dos Santos et al. 2022) and Cloudy (Ferland et al. 1998; 2017, - Chatziokos et al. 2023). This function works iteratively as follows: - p_winds calculates a density profile, Cloudy calculates the mean particle mass profile, - we calculate the associated mu_bar value, which is passed to p-winds to calculate a new - density profile, until mu_bar has converged to a stable value. - Saves a 'pprof' txt file with the r, rho, v, mu structure. - - Parameters - ---------- - planet : tools.Planet - Object storing the planet parameters. - Mdot : str or numeric - log of the mass-loss rate in units of g s-1. - T : str or numeric - Temperature in units of K. - zdict : dict - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - convergence : float, optional - Convergence threshold expressed as the relative change in mu_bar between iterations, by default 0.01 - maxit : int, optional - Maximum number of iterations, by default 7 - cleantemp : bool, optional - Whether to remove the temporary files in the /temp folder. These files store - the intermediate profiles during the iterative process to find mu_bar. By default False. - overwrite : bool, optional - Whether to overwrite existing models, by default False. - verbose : bool, optional - Whether to print diagnostics about the convergence of mu_bar, by default False - no_tidal : bool, optional - Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et al. (2024). - See also Appendix D of Vissapragada et al. (2022) for the p-winds implementation. - Default is False, i.e. tidal gravity included. - altmax : int, optional - Maximum altitude of the profile in units of the planet radius. By default 20. - """ - - save_name = tools.projectpath+'/parker_profiles/'+planet.name+'/'+pdir+'/pprof_'+planet.name+'_T='+str(T)+'_M='+ \ - "%.3f" %Mdot +".txt" - if os.path.exists(save_name) and not overwrite: - print("Parker profile already exists and overwrite = False:", planet.name, pdir, "%.3f" %Mdot, T) - return #this quits the function but if we're running a grid, it doesn't quit the whole Python code - - tools.verbose_print("Making initial parker profile while assuming a completely neutral mu_bar...", verbose=verbose) - neutral_mu_bar = calc_neutral_mu(zdict) - neutral_mu_struc = np.array([[1., neutral_mu_bar], [altmax, neutral_mu_bar]]) #set up an array with constant mu(r) at the neutral value - filename, launch_velocity = save_temp_parker_profile(planet, Mdot, T, zdict, pdir, - neutral_mu_bar, mu_struc=neutral_mu_struc, no_tidal=no_tidal, altmax=altmax) - tools.verbose_print(f"Saved temp parker profile with neutral mu_bar: {neutral_mu_bar}" , verbose=verbose) - previous_mu_bar = neutral_mu_bar - - for itno in range(maxit): - tools.verbose_print(f"Iteration number: {itno+1}", verbose=verbose) - - tools.verbose_print("Running parker profile through Cloudy...", verbose=verbose) - simname, pprof = run_parker_with_cloudy(filename, T, planet, zdict) - tools.verbose_print("Cloudy run done.", verbose=verbose) - - sim = tools.Sim(simname, altmax=altmax, planet=planet) - sim.addv(pprof.alt, pprof.v) #add the velocity structure to the sim, so that calc_mu_bar() works. - - mu_bar = calc_mu_bar(sim) - tools.verbose_print(f"Making new parker profile with p-winds based on Cloudy's reported mu_bar: {mu_bar}", verbose=verbose) - mu_struc = np.column_stack((sim.ovr.alt.values[::-1]/planet.R, sim.ovr.mu[::-1].values)) #pass Cloudy's mu structure to save in the pprof - filename, launch_velocity = save_temp_parker_profile(planet, Mdot, T, zdict, pdir, - mu_bar, mu_struc=mu_struc, no_tidal=no_tidal, altmax=altmax) - tools.verbose_print("Saved temp parker profile.", verbose=verbose) - - if np.abs(mu_bar - previous_mu_bar)/previous_mu_bar < convergence: - print("mu_bar converged:", save_name) - if launch_velocity > 1: - warnings.warn(f"This Parker wind profile is supersonic already at Rp: {save_name}") - break - else: - previous_mu_bar = mu_bar - - copyfile(filename, filename.split('temp/')[0] + filename.split('temp/')[1]) - tools.verbose_print("Copied final parker profile from temp to parent folder.", verbose=verbose) - - if cleantemp: #then we remove the temp files - os.remove(simname+'.in') - os.remove(simname+'.out') - os.remove(simname+'.ovr') - os.remove(filename) - tools.verbose_print("Temporary files removed.", verbose=verbose) - - -def run_s(plname, pdir, Mdot, T, SEDname, fH, zdict, mu_conv, - mu_maxit, overwrite, verbose, no_tidal): - """ - Calculates a single isothermal Parker wind profile. - - Parameters - ---------- - plname : str - Planet name (must have parameters stored in $SUNBATHER_PROJECT_PATH/planets.txt). - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - Mdot : str or numeric - log of the mass-loss rate in units of g s-1. - T : str or numeric - Temperature in units of K. - SEDname : str - Name of SED file to use. If SEDname is 'real', we use the name as - given in the planets.txt file, but if SEDname is something else, - we advice to use a separate pdir folder for this. - fH : float or None - Hydrogen abundance expressed as a fraction of the total. If a value is given, - Parker wind profiles will be calculated using p-winds standalone with a H/He - composition. If None is given, Parker wind profiles will be calculated using the - p-winds/Cloudy iterative method and the composition is specified via the zdict argument. - zdict : dict - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - Will only be used if fH is None, in which case the p-winds/Cloudy iterative method - is applied. - mu_conv : float - Convergence threshold expressed as the relative change in mu_bar between iterations. - Will only be used if fH is None, in which case the p-winds/Cloudy iterative method - is applied. - mu_maxit : int - Maximum number of iterations for the p-winds/Cloudy iterative method. Will only - be used if fH is None. - overwrite : bool - Whether to overwrite existing models. - verbose : bool - Whether to print diagnostics about the convergence of mu_bar. - no_tidal : bool - Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et al. (2024). - See also Appendix D of Vissapragada et al. (2022) for the p-winds implementation. - """ - - p = tools.Planet(plname) - if SEDname != 'real': - p.set_var(SEDname=SEDname) - altmax = min(20, int((p.a - p.Rstar) / p.R)) #solve profile up to 20 Rp, unless the star is closer than that - - if fH != None: #then run p_winds standalone - spectrum = cloudy_spec_to_pwinds(tools.cloudypath+'/data/SED/'+p.SEDname, 1., (p.a - altmax*p.R)/tools.AU) #assumes SED is at 1 AU - save_plain_parker_profile(p, Mdot, T, spectrum, h_fraction=fH, pdir=pdir, overwrite=overwrite, no_tidal=no_tidal, altmax=altmax) - else: #then run p_winds/Cloudy iterative scheme - save_cloudy_parker_profile(p, Mdot, T, zdict, pdir, - convergence=mu_conv, maxit=mu_maxit, cleantemp=True, - overwrite=overwrite, verbose=verbose, - no_tidal=no_tidal, altmax=altmax) - - -def catch_errors_run_s(*args): - """ - Executes the run_s() function with provided arguments, while catching errors more gracefully. - """ - - try: - run_s(*args) - except Exception as e: - traceback.print_exc() - - -def run_g(plname, pdir, cores, Mdot_l, Mdot_u, Mdot_s, - T_l, T_u, T_s, SEDname, fH, zdict, mu_conv, - mu_maxit, overwrite, verbose, no_tidal): - """ - Calculates a grid of isothermal Parker wind models, by executing the run_s() function in parallel. - - Parameters - ---------- - plname : str - Planet name (must have parameters stored in $SUNBATHER_PROJECT_PATH/planets.txt). - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - cores : int - Number of parallel processes to spawn (i.e., number of CPU cores). - Mdot_l : str or numeric - Lower bound on the log10(mass-loss rate) grid in units of g s-1. - Mdot_u : str or numeric - Upper bound on the log10(mass-loss rate) grid in units of g s-1. - Mdot_s : str or numeric - Step size of the log10(mass-loss rate) grid in units of g s-1. - T_l : str or numeric - Lower bound on the temperature grid in units of K. - T_u : str or numeric - Upper bound on the temperature grid in units of K. - T_s : str or numeric - Step size of the temperature grid in units of K. - SEDname : str - Name of SED file to use. If SEDname is 'real', we use the name as - given in the planets.txt file, but if SEDname is something else, - we advice to use a separate pdir folder for this. - fH : float or None - Hydrogen abundance expressed as a fraction of the total. If a value is given, - Parker wind profiles will be calculated using p-winds standalone with a H/He - composition. If None is given, Parker wind profiles will be calculated using the - p-winds/Cloudy iterative method and the composition is specified via the zdict argument. - zdict : dict - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - Will only be used if fH is None, in which case the p-winds/Cloudy iterative method - is applied. - mu_conv : float - Convergence threshold expressed as the relative change in mu_bar between iterations. - Will only be used if fH is None, in which case the p-winds/Cloudy iterative method - is applied. - mu_maxit : int - Maximum number of iterations for the p-winds/Cloudy iterative method. Will only - be used if fH is None. - overwrite : bool - Whether to overwrite existing models. - verbose : bool - Whether to print diagnostics about the convergence of mu_bar. - no_tidal : bool - Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et al. (2024). - See also Appendix D of Vissapragada et al. (2022) for the p-winds implementation. - """ - - p = multiprocessing.Pool(cores) - - pars = [] - for Mdot in np.arange(float(Mdot_l), float(Mdot_u)+1e-6, float(Mdot_s)): #1e-6 so that upper bound is inclusive - for T in np.arange(int(T_l), int(T_u)+1e-6, int(T_s)).astype(int): - pars.append((plname, pdir, Mdot, T, SEDname, fH, zdict, mu_conv, mu_maxit, overwrite, verbose, no_tidal)) - - p.starmap(catch_errors_run_s, pars) - p.close() - p.join() - - - - -if __name__ == '__main__': - - class OneOrThreeAction(argparse.Action): - """ - Custom class for an argparse argument with exactly 1 or 3 values. - """ - def __call__(self, parser, namespace, values, option_string=None): - if len(values) not in (1, 3): - parser.error("Exactly one or three values are required.") - setattr(namespace, self.dest, values) - - class AddDictAction(argparse.Action): - """ - Custom class to add an argparse argument to a dictionary. - """ - def __call__(self, parser, namespace, values, option_string=None): - if not hasattr(namespace, self.dest) or getattr(namespace, self.dest) is None: - setattr(namespace, self.dest, {}) - for value in values: - key, val = value.split('=') - getattr(namespace, self.dest)[key] = float(val) - - - t0 = time.time() - - parser = argparse.ArgumentParser(description="Creates 1D Parker profile(s) using the p_winds code and Cloudy.") - - parser.add_argument("-plname", required=True, help="planet name (must be in planets.txt)") - parser.add_argument("-pdir", required=True, help="directory where the profiles are saved. It is adviced to choose a name that " \ - "somehow represents the chosen parameters, e.g. 'fH_0.9' or 'z=10'. The path will be $SUNBATHER_PROJECT_PATH/parker_profiles/pdir/") - parser.add_argument("-Mdot", required=True, type=float, nargs='+', action=OneOrThreeAction, help="log10(mass-loss rate), or three values specifying a grid of " \ - "mass-loss rates: lowest, highest, stepsize. -Mdot will be rounded to three decimal places.") - parser.add_argument("-T", required=True, type=int, nargs='+', action=OneOrThreeAction, help="temperature, or three values specifying a grid of temperatures: lowest, highest, stepsize.") - parser.add_argument("-SEDname", type=str, default='real', help="name of SED to use. Must be in Cloudy's data/SED/ folder [default=SEDname set in planet.txt file]") - parser.add_argument("-overwrite", action='store_true', help="overwrite existing profile if passed [default=False]") - composition_group = parser.add_mutually_exclusive_group(required=True) - composition_group.add_argument("-fH", type=float, help="hydrogen fraction by number. Using this command results in running standalone p_winds without invoking Cloudy.") - composition_group.add_argument("-z", type=float, help="metallicity (=scale factor relative to solar for all elements except H and He). Using this " \ - "command results in running p_winds in an an iterative scheme where Cloudy updates the mu parameter.") - parser.add_argument("-zelem", action = AddDictAction, nargs='+', default = {}, help="abundance scale factor for specific elements, e.g. -zelem Fe=10 -zelem He=0.01. " \ - "Can also be used to toggle elements off, e.g. -zelem Ca=0. Combines with -z argument. Using this " \ - "command results in running p_winds in an an iterative scheme where Cloudy updates the mu parameter.") - parser.add_argument("-cores", type=int, default=1, help="number of parallel runs [default=1]") - parser.add_argument("-mu_conv", type=float, default=0.01, help="relative change in mu allowed for convergence, when using p_winds/Cloudy iterative scheme [default=0.01]") - parser.add_argument("-mu_maxit", type=int, default=7, help="maximum number of iterations the p_winds/Cloudy iterative scheme is ran " \ - "if convergence is not reached [default =7]") - parser.add_argument("-verbose", action='store_true', help="print out mu-bar values of each iteration [default=False]") - parser.add_argument("-no_tidal", action='store_true', help="neglect the stellar tidal gravity term [default=False, i.e. tidal term included]") - args = parser.parse_args() - - if args.z != None: - zdict = tools.get_zdict(z=args.z, zelem=args.zelem) - else: #if z==None we should not pass that to the tools.get_zdict function - zdict = tools.get_zdict(zelem=args.zelem) - - if args.fH != None and (args.zelem != {} or args.mu_conv != 0.01 or args.mu_maxit != 7): - warnings.warn("The -zelem, -mu_conv, and -mu_maxit commands only combine with -z, not with -fH, so I will ignore their input.") - - #set up the folder structure if it doesn't exist yet - if not os.path.isdir(tools.projectpath+'/parker_profiles/'): - os.mkdir(tools.projectpath+'/parker_profiles') - if not os.path.isdir(tools.projectpath+'/parker_profiles/'+args.plname+'/'): - os.mkdir(tools.projectpath+'/parker_profiles/'+args.plname) - if not os.path.isdir(tools.projectpath+'/parker_profiles/'+args.plname+'/'+args.pdir+'/'): - os.mkdir(tools.projectpath+'/parker_profiles/'+args.plname+'/'+args.pdir+'/') - if (args.fH == None) and (not os.path.isdir(tools.projectpath+'/parker_profiles/'+args.plname+'/'+args.pdir+'/temp/')): - os.mkdir(tools.projectpath+'/parker_profiles/'+args.plname+'/'+args.pdir+'/temp') - - if (len(args.T) == 1 and len(args.Mdot) == 1): #then we run a single model - run_s(args.plname, args.pdir, args.Mdot[0], args.T[0], args.SEDname, args.fH, zdict, args.mu_conv, args.mu_maxit, args.overwrite, args.verbose, args.no_tidal) - elif (len(args.T) == 3 and len(args.Mdot) == 3): #then we run a grid over both parameters - run_g(args.plname, args.pdir, args.cores, args.Mdot[0], args.Mdot[1], args.Mdot[2], args.T[0], args.T[1], args.T[2], args.SEDname, args.fH, zdict, args.mu_conv, args.mu_maxit, args.overwrite, args.verbose, args.no_tidal) - elif (len(args.T) == 3 and len(args.Mdot) == 1): #then we run a grid over only T - run_g(args.plname, args.pdir, args.cores, args.Mdot[0], args.Mdot[0], args.Mdot[0], args.T[0], args.T[1], args.T[2], args.SEDname, args.fH, zdict, args.mu_conv, args.mu_maxit, args.overwrite, args.verbose, args.no_tidal) - elif (len(args.T) == 1 and len(args.Mdot) == 3): #then we run a grid over only Mdot - run_g(args.plname, args.pdir, args.cores, args.Mdot[0], args.Mdot[1], args.Mdot[2], args.T[0], args.T[0], args.T[0], args.SEDname, args.fH, zdict, args.mu_conv, args.mu_maxit, args.overwrite, args.verbose, args.no_tidal) - - print("\nCalculations took", int(time.time()-t0) // 3600, "hours, ", (int(time.time()-t0)%3600) // 60, "minutes and ", (int(time.time()-t0)%60), "seconds.\n") diff --git a/src/convergeT_parker.py b/src/convergeT_parker.py deleted file mode 100644 index d7a5446..0000000 --- a/src/convergeT_parker.py +++ /dev/null @@ -1,393 +0,0 @@ -#sunbather imports -import tools -import solveT - -#other imports -import pandas as pd -import numpy as np -import multiprocessing -from shutil import copyfile -import time -import os -import re -import argparse -import traceback - - -def find_close_model(parentfolder, T, Mdot, tolT=2000, tolMdot=1.0): - """ - Takes a parent folder where multiple 1D parker profiles have been ran, - and for given T and Mdot it looks for another model that is already finished and closest - to the given model, so that we can start our new simulation from that converged temperature - structure. It returns the T and Mdot - of the close converged folder, or None if there aren't any (within the tolerance). - - Parameters - ---------- - parentfolder : str - Parent folder containing sunbather simulations within folders with the parker_*T0*_*Mdot* name format. - T : numeric - Target isothermal temperature in units of K. - Mdot : numeric - log of the target mass-loss rate in units of g s-1. - tolT : numeric, optional - Maximum T0 difference with the target temperature, by default 2000 K - tolMdot : numeric, optional - Maximum log10(Mdot) difference with the target mass-loss rate, by default 1 dex - - Returns - ------- - clconv : list - [T0, Mdot] of the closest found finished model, or [None, None] if none were found within the tolerance. - """ - - pattern = re.compile(r'parker_\d+_\d+\.\d{3}$') #this is how folder names should be - all_files_and_folders = os.listdir(parentfolder) - allfolders = [os.path.join(parentfolder, folder)+'/' for folder in all_files_and_folders if pattern.match(folder) and os.path.isdir(os.path.join(parentfolder, folder))] - - convergedfolders = [] #stores the T and Mdot values of all folders with 0.out files - for folder in allfolders: - if os.path.isfile(folder+'converged.out'): - folderparams = folder.split('/')[-2].split('_') - convergedfolders.append([int(folderparams[1]), float(folderparams[2])]) - - if [int(T), float(Mdot)] in convergedfolders: #if the current folder is found, remove it - convergedfolders.remove([int(T), float(Mdot)]) - - if convergedfolders == []: #then we default to constant starting value - clconv = [None, None] - else: #find closest converged profile - dist = lambda x, y: (x[0]-y[0])**2 + (2000*(x[1]-y[1]))**2 #1 order of magnitude Mdot is now 'equal weighted' to 2000K - clconv = min(convergedfolders, key=lambda fol: dist(fol, [int(T), float(Mdot)])) #closest converged [T, Mdot] - if (np.abs(clconv[0] - int(T)) > tolT) or (np.abs(clconv[1] - float(Mdot)) > tolMdot): - clconv = [None, None] - - return clconv - - -def run_s(plname, Mdot, T, itno, fc, dir, SEDname, overwrite, startT, pdir, zdict=None, altmax=8, save_sp=[], constantT=False, maxit=16): - """ - Solves for a nonisothermal temperature profile of a single isothermal Parker wind (density and velocity) profile. - - Parameters - ---------- - plname : str - Planet name (must have parameters stored in $SUNBATHER_PROJECT_PATH/planets.txt). - Mdot : str or numeric - log of the mass-loss rate in units of g s-1. - T : str or int - Temperature in units of g s-1. - itno : int - Iteration number to start from (can only be different from 1 - if this same model has been ran before, and then also - overwrite = True needs to be set). If value is 0, will automatically - look for the highest iteration number to start from. - fc : numeric - H/C convergence factor, see Linssen et al. (2024). A sensible value is 1.1. - dir : str - Directory as $SUNBATHER_PROJECT_PATH/sims/1D/planetname/*dir*/ - where the temperature profile will be solved. A folder named - parker_*T*_*Mdot*/ will be made there. - SEDname : str - Name of SED file to use. If SEDname='real', we use the name as - given in the planets.txt file, but if SEDname is something else, - we advice to use a separate dir folder for this. - overwrite : bool - Whether to overwrite if this simulation already exists. - startT : str - Either 'constant', 'free' or 'nearby'. Sets the initial - temperature profile guessed/used for the first iteration. - 'constant' sets it equal to the parker wind isothermal value. - 'free' lets Cloudy solve it, so you will get the radiative equilibrium structure. - 'nearby' looks in the dir folder for previously solved - Parker wind profiles and starts from a converged one. Then, if no converged - ones are available, uses 'free' instead. - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ - where we take the isothermal parker wind density and velocity profiles from. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - zdict : dict, optional - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - Default is None, which results in a solar composition. - altmax : int, optional - Maximum altitude of the simulation in units of planet radius, by default 8 - save_sp : list, optional - A list of atomic/ionic species to let Cloudy save the number density profiles - for. Those are needed when doing radiative transfer to produce - transmission spectra. For example, to be able to make - metastable helium spectra, 'He' needs to be in the save_sp list. By default []. - constantT : bool, optional - If True, instead of sovling for a nonisothermal temperature profile, - the Parker wind profile is ran at the isothermal value. By default False. - maxit : int, optional - Maximum number of iterations, by default 16. - """ - - Mdot = "%.3f" % float(Mdot) #enforce this format to get standard file names. - T = str(T) - - #set up the planet object - planet = tools.Planet(plname) - if SEDname != 'real': - planet.set_var(SEDname=SEDname) - - #set up the folder structure - pathTstruc = tools.projectpath+'/sims/1D/'+planet.name+'/'+dir+'/' - path = pathTstruc+'parker_'+T+'_'+Mdot+'/' - - #check if this parker profile exists in the given pdir - try: - pprof = tools.read_parker(planet.name, T, Mdot, pdir) - except FileNotFoundError: - print("This parker profile does not exist:", tools.projectpath+'/parker_profiles/'+planet.name+'/'+pdir+'/pprof_'+planet.name+'_T='+str(T)+'_M='+Mdot+'.txt') - return #quit the run_s function but not the code - - #check for overwriting - if os.path.isdir(path): #the simulation exists already - if not overwrite: - print("Simulation already exists and overwrite = False:", plname, dir, Mdot, T) - return #this quits the function but if we're running a grid, it doesn't quit the whole Python code - else: - os.mkdir(path[:-1]) #make the folder - - #get profiles and parameters we need for the input file - alt = pprof.alt.values - hden = tools.rho_to_hden(pprof.rho.values, abundances=tools.get_abundances(zdict)) - dlaw = tools.alt_array_to_Cloudy(alt, hden, altmax, planet.R, 1000, log=True) - - nuFnu_1AU_linear, Ryd = tools.get_SED_norm_1AU(planet.SEDname) - nuFnu_a_log = np.log10(nuFnu_1AU_linear / ((planet.a - altmax*planet.R)/tools.AU)**2) - - comments = '# plname='+planet.name+'\n# parker_T='+str(T)+'\n# parker_Mdot='+str(Mdot)+'\n# parker_dir='+pdir+'\n# altmax='+str(altmax) - - if constantT: #this will run the profile at the isothermal T value instead of converging a nonisothermal profile - if save_sp == []: - tools.write_Cloudy_in(path+'constantT', title=planet.name+' 1D Parker with T='+str(T)+' and log(Mdot)='+str(Mdot), - flux_scaling=[nuFnu_a_log, Ryd], SED=planet.SEDname, dlaw=dlaw, double_tau=True, - overwrite=overwrite, cosmic_rays=True, zdict=zdict, comments=comments, constantT=T) - else: - tools.write_Cloudy_in(path+'constantT', title=planet.name+' 1D Parker with T='+str(T)+' and log(Mdot)='+str(Mdot), - flux_scaling=[nuFnu_a_log, Ryd], SED=planet.SEDname, dlaw=dlaw, double_tau=True, - overwrite=overwrite, cosmic_rays=True, zdict=zdict, comments=comments, constantT=T, - outfiles=['.den', '.en'], denspecies=save_sp, selected_den_levels=True) - - tools.run_Cloudy('constantT', folder=path) #run the Cloudy simulation - return - - #if we got to here, we are not doing a constantT simulation, so we set up the convergence scheme files - #write Cloudy template input file - each iteration will add their current temperature structure to this template - tools.write_Cloudy_in(path+'template', title=planet.name+' 1D Parker with T='+str(T)+' and log(Mdot)='+str(Mdot), - flux_scaling=[nuFnu_a_log, Ryd], SED=planet.SEDname, dlaw=dlaw, double_tau=True, - overwrite=overwrite, cosmic_rays=True, zdict=zdict, comments=comments) - - if itno == 0: #this means we resume from the highest found previously ran iteration - pattern = r'iteration(\d+)\.out' #search pattern: iteration followed by an integer - max_iteration = -1 #set an impossible number - for filename in os.listdir(path): #loop through all files/folder in the path - if os.path.isfile(os.path.join(path, filename)): #if it is a file (not a folder) - if re.search(pattern, filename): #if it matches the pattern - iteration_number = int(re.search(pattern, filename).group(1)) #extract the iteration number - if iteration_number > max_iteration: #update highest found iteration number - max_iteration = iteration_number - if max_iteration == -1: #this means no files were found - print(f"This folder does not contain any iteration files {path}, so I cannot resume from the highest one. Will instead start at itno = 1.") - itno = 1 - else: - print(f"Found the highest iteration {path}iteration{max_iteration}, will resume at that same itno.") - itno = max_iteration - - if itno == 1: - #get starting temperature structure - clconv = find_close_model(pathTstruc, T, Mdot) #find if there are any nearby models we can start from - if startT == 'constant': #then we start with the isothermal value - tools.copyadd_Cloudy_in(path+'template', path+'iteration1', constantT=T) - - elif clconv == [None, None] or startT == 'free': #then we start in free (=radiative eq.) mode - copyfile(path+'template.in', path+'iteration1.in') - - elif startT == 'nearby': #then clconv cannot be [None, None] and we start from a previous converged T(r) - print(f"Model {path} starting from previously converged temperature profile: T0 = {clconv[0]}, Mdot = {clconv[1]}") - prev_conv_T = pd.read_table(pathTstruc+'parker_'+str(clconv[0])+'_'+"{:.3f}".format(clconv[1])+'/converged.txt', delimiter=' ') - Cltlaw = tools.alt_array_to_Cloudy(prev_conv_T.R * planet.R, prev_conv_T.Te, altmax, planet.R, 1000) - tools.copyadd_Cloudy_in(path+'template', path+'iteration1', tlaw=Cltlaw) - - - #with everything in order, run the actual temperature convergence scheme - solveT.run_loop(path, itno, fc, save_sp, maxit) - - -def catch_errors_run_s(*args): - """ - Executes the run_s() function with provided arguments, while catching errors more gracefully. - """ - - try: - run_s(*args) - except Exception as e: - traceback.print_exc() - - -def run_g(plname, cores, Mdot_l, Mdot_u, Mdot_s, T_l, T_u, T_s, fc, dir, SEDname, overwrite, startT, pdir, zdict, altmax, save_sp, constantT, maxit): - """ - Solves for a nonisothermal temperature profile of a grid of isothermal Parker wind models, - by executing the run_s() function in parallel. - - Parameters - ---------- - plname : str - Planet name (must have parameters stored in $SUNBATHER_PROJECT_PATH/planets.txt). - cores : int - Number of parallel processes to spawn (i.e., number of CPU cores). - Mdot_l : str or numeric - Lower bound on the log10(mass-loss rate) grid in units of g s-1. - Mdot_u : str or numeric - Upper bound on the log10(mass-loss rate) grid in units of g s-1. - Mdot_s : str or numeric - Step size of the log10(mass-loss rate) grid in units of g s-1. - T_l : str or numeric - Lower bound on the temperature grid in units of K. - T_u : str or numeric - Upper bound on the temperature grid in units of K. - T_s : str or numeric - Step size of the temperature grid in units of K. - fc : numeric - H/C convergence factor, see Linssen et al. (2024). A sensible value is 1.1. - dir : str - Directory as $SUNBATHER_PROJECT_PATH/sims/1D/planetname/*dir*/ - where the temperature profile will be solved. A folder named - parker_*T*_*Mdot*/ will be made there. - SEDname : str - Name of SED file to use. If SEDname is 'real', we use the name as - given in the planets.txt file, but if SEDname is something else, - we advice to use a separate dir folder for this. - overwrite : bool - Whether to overwrite if this simulation already exists. - startT : str - Either 'constant', 'free' or 'nearby'. Sets the initial - temperature profile guessed/used for the first iteration. - 'constant' sets it equal to the parker wind isothermal value. - 'free' lets Cloudy solve it, so you will get the radiative equilibrium structure. - 'nearby' looks in the dir folder for previously solved - Parker wind profiles and starts from a converged one. Then, if no converged - ones are available, uses 'free' instead. - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ - where we take the isothermal parker wind density and velocity profiles from. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - zdict : dict, optional - Dictionary with the scale factors of all elements relative - to the default solar composition. Can be easily created with tools.get_zdict(). - Default is None, which results in a solar composition. - altmax : int, optional - Maximum altitude of the simulation in units of planet radius, by default 8 - save_sp : list, optional - A list of atomic/ionic species to let Cloudy save the number density profiles - for. Those are needed when doing radiative transfer to produce - transmission spectra. For example, to be able to make - metastable helium spectra, 'He' needs to be in the save_sp list. By default []. - constantT : bool, optional - If True, instead of sovling for a nonisothermal temperature profile, - the Parker wind profile is ran at the isothermal value. By default False. - maxit : int, optional - Maximum number of iterations, by default 16. - """ - - p = multiprocessing.Pool(cores) - - pars = [] - for Mdot in np.arange(float(Mdot_l), float(Mdot_u)+1e-6, float(Mdot_s)): #1e-6 so that upper bound is inclusive - for T in np.arange(int(T_l), int(T_u)+1e-6, int(T_s)).astype(int): - pars.append((plname, Mdot, T, 1, fc, dir, SEDname, overwrite, startT, pdir, zdict, altmax, save_sp, constantT, maxit)) - - p.starmap(catch_errors_run_s, pars) - p.close() - p.join() - - - - -if __name__ == '__main__': - - class OneOrThreeAction(argparse.Action): - """ - Custom class for an argparse argument with exactly 1 or 3 values. - """ - def __call__(self, parser, namespace, values, option_string=None): - if len(values) not in (1, 3): - parser.error("Exactly one or three values are required.") - setattr(namespace, self.dest, values) - - class AddDictAction(argparse.Action): - """ - Custom class to add an argparse argument to a dictionary. - """ - def __call__(self, parser, namespace, values, option_string=None): - if not hasattr(namespace, self.dest) or getattr(namespace, self.dest) is None: - setattr(namespace, self.dest, {}) - for value in values: - key, val = value.split('=') - getattr(namespace, self.dest)[key] = float(val) - - - t0 = time.time() - - parser = argparse.ArgumentParser(description="Runs the temperature convergence for 1D Parker profile(s).") - - parser.add_argument("-plname", required=True, help="planet name (must be in planets.txt)") - parser.add_argument("-dir", required=True, type=str, help="folder where the temperature structures are solved. e.g. Tstruc_fH_0.9 or Tstruc_z_100_3xEUV etc.") - parser.add_argument("-pdir", required=True, type=str, help="parker profile folder/dir to use, e.g. fH_0.9 or z_100.") - parser.add_argument("-Mdot", required=True, type=float, nargs='+', action=OneOrThreeAction, help="log10(mass-loss rate), or three values specifying a grid of " \ - "mass-loss rates: lowest, highest, stepsize. -Mdot will be rounded to three decimal places.") - parser.add_argument("-T", required=True, type=int, nargs='+', action=OneOrThreeAction, help="temperature, or three values specifying a grid of temperatures: lowest, highest, stepsize.") - parser.add_argument("-cores", type=int, default=1, help="number of parallel runs [default=1]") - parser.add_argument("-fc", type=float, default=1.1, help="convergence factor (heat/cool should be below this value) [default=1.1]") - parser.add_argument("-startT", choices=["nearby", "free", "constant"], default="nearby", help="initial T structure, either 'constant', 'free' or 'nearby' [default=nearby]") - parser.add_argument("-itno", type=int, default=1, help="starting iteration number (itno != 1 only works with -overwrite). As a special use, you can pass " \ - "-itno 0 which will automatically find the highest previously ran iteration number [default=1]") - parser.add_argument("-maxit", type=int, default=20, help="maximum number of iterations [default = 20]") - parser.add_argument("-SEDname", type=str, default='real', help="name of SED to use. Must be in Cloudy's data/SED/ folder [default=SEDname set in planet.txt file]") - parser.add_argument("-overwrite", action='store_true', help="overwrite existing simulation if passed [default=False]") - parser.add_argument("-z", type=float, default=1., help="metallicity (=scale factor relative to solar for all elements except H and He) [default=1.]") - parser.add_argument("-zelem", action = AddDictAction, nargs='+', default = {}, help="abundance scale factor for specific elements, e.g. -zelem Fe=10 -zelem He=0.01. " \ - "Can also be used to toggle elements off, e.g. -zelem Ca=0. Combines with -z argument. Using this " \ - "command results in running p_winds in an an iterative scheme where Cloudy updates the mu parameter.") - parser.add_argument("-altmax", type=int, default=8, help="maximum altitude of the simulation in units of Rp. [default=8]") - parser.add_argument("-save_sp", type=str, nargs='+', default=['all'], help="atomic or ionic species to save densities for (needed for radiative transfer). " \ - "You can add multiple as e.g. -save_sp He Ca+ Fe3+ Passing 'all' includes all species that weren't turned off. In that case, you can "\ - "set the maximum degree of ionization with the -save_sp_max_ion flag. default=[] i.e. none.") - parser.add_argument("-save_sp_max_ion", type=int, default=6, help="only used when you set -save_sp all This command sets the maximum degree of ionization "\ - "that will be saved. [default=6] but using lower values saves significant file size if high ions are not needed. The maximum number is 12, "\ - "but such highly ionized species only occur at very high XUV flux, such as in young systems.") - parser.add_argument("-constantT", action='store_true', help="run the profile at the isothermal temperature instead of converging upon the temperature structure. [default=False]") - - - args = parser.parse_args() - - zdict = tools.get_zdict(z=args.z, zelem=args.zelem) - - if 'all' in args.save_sp: - args.save_sp = tools.get_specieslist(exclude_elements=[sp for sp,zval in zdict.items() if zval == 0.], max_ion=args.save_sp_max_ion) - - #set up the folder structure if it doesn't exist yet - if not os.path.isdir(tools.projectpath+'/sims/'): - os.mkdir(tools.projectpath+'/sims') - if not os.path.isdir(tools.projectpath+'/sims/1D/'): - os.mkdir(tools.projectpath+'/sims/1D') - if not os.path.isdir(tools.projectpath+'/sims/1D/'+args.plname+'/'): - os.mkdir(tools.projectpath+'/sims/1D/'+args.plname) - if not os.path.isdir(tools.projectpath+'/sims/1D/'+args.plname+'/'+args.dir+'/'): - os.mkdir(tools.projectpath+'/sims/1D/'+args.plname+'/'+args.dir) - - if (len(args.T) == 1 and len(args.Mdot) == 1): #then we run a single model - run_s(args.plname, args.Mdot[0], str(args.T[0]), args.itno, args.fc, args.dir, args.SEDname, args.overwrite, args.startT, args.pdir, zdict, args.altmax, args.save_sp, args.constantT, args.maxit) - elif (len(args.T) == 3 and len(args.Mdot) == 3): #then we run a grid over both parameters - run_g(args.plname, args.cores, args.Mdot[0], args.Mdot[1], args.Mdot[2], args.T[0], args.T[1], args.T[2], args.fc, args.dir, args.SEDname, args.overwrite, args.startT, args.pdir, zdict, args.altmax, args.save_sp, args.constantT, args.maxit) - elif (len(args.T) == 3 and len(args.Mdot) == 1): #then we run a grid over only T - run_g(args.plname, args.cores, args.Mdot[0], args.Mdot[0], args.Mdot[0], args.T[0], args.T[1], args.T[2], args.fc, args.dir, args.SEDname, args.overwrite, args.startT, args.pdir, zdict, args.altmax, args.save_sp, args.constantT, args.maxit) - elif (len(args.T) == 1 and len(args.Mdot) == 3): #then we run a grid over only Mdot - run_g(args.plname, args.cores, args.Mdot[0], args.Mdot[1], args.Mdot[2], args.T[0], args.T[0], args.T[0], args.fc, args.dir, args.SEDname, args.overwrite, args.startT, args.pdir, zdict, args.altmax, args.save_sp, args.constantT, args.maxit) - - print("\nCalculations took", int(time.time()-t0) // 3600, "hours, ", (int(time.time()-t0)%3600) // 60, "minutes and ", (int(time.time()-t0)%60), "seconds.\n") diff --git a/src/solveT.py b/src/solveT.py deleted file mode 100644 index 5dc7f61..0000000 --- a/src/solveT.py +++ /dev/null @@ -1,720 +0,0 @@ -#sunbather imports -import tools - -#other imports -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.lines import Line2D -from scipy.optimize import minimize_scalar -from scipy.interpolate import interp1d -import scipy.stats as sps -import os -import warnings - - -def calc_expansion(r, rho, v, Te, mu): - """ - Calculates expansion cooling (Linssen et al. 2024 Eq. 3 second term). - - Parameters - ---------- - r : numpy.ndarray - Radius in units of cm - rho : numpy.ndarray - Density in units of g cm-3 - v : numpy.ndarray - Velocity in units of cm s-1 - Te : numpy.ndarray - Temperature in units of K - mu : numpy.ndarray - Mean particle mass in units of amu - - Returns - ------- - expansion : numpy.ndarray - Expansion cooling rate. - """ - - expansion = tools.k/tools.mH * Te * v / mu * np.gradient(rho, r) - assert np.max(expansion) <= 0, "Found positive expansion cooling rates (i.e., heating)." - - return expansion - - -def calc_advection(r, rho, v, Te, mu): - """ - Calcules advection heating/cooling (Linssen et al. 2024 Eq. 3 first term). - - Parameters - ---------- - r : numpy.ndarray - Radius in units of cm - rho : numpy.ndarray - Density in units of g cm-3 - v : numpy.ndarray - Velocity in units of cm s-1 - Te : numpy.ndarray - Temperature in units of K - mu : numpy.ndarray - Mean particle mass in units of amu - - Returns - ------- - advection : numpy.ndarray - Advection heating/cooling rate. - """ - - advection = -1 * tools.k/(tools.mH * 2/3) * rho * v * np.gradient(Te/mu, r) - - return advection - - -def simtogrid(sim, grid): - """ - Extracts various needed quantities from a Cloudy simulation and interpolates - them onto the provided radius grid. - - Parameters - ---------- - sim : tools.Sim - Cloudy simulation. - grid : numpy.ndarray - Radius grid in units of cm. - - Returns - ------- - Te : numpy.ndarray - Temperature in units of K. - mu : numpy.ndarray - Mean particle mass in units of amu. - rho : numpy.ndarray - Density in units of g cm-3. - v : numpy.ndarray - Velocity in units of cm s-1. - radheat : numpy.ndarray - Radiative heating rate in units of erg s-1 cm-3. - radcool : numpy.ndarray - Radiative cooling rate in units of erg s-1 cm-3, as positive values. - expcool : numpy.ndarray - Expansion cooling rate in units of erg s-1 cm-3, as positive values. - advheat : numpy.ndarray - Advection heating rate in units of erg s-1 cm-3. - advcool : numpy.ndarray - Advection cooling rate in units of erg s-1 cm-3, as positive values. - """ - - #get Cloudy quantities - Te = interp1d(sim.ovr.alt, sim.ovr.Te, fill_value='extrapolate')(grid) - mu = interp1d(sim.ovr.alt[sim.ovr.alt < 0.999 * sim.altmax * sim.p.R], sim.ovr.mu[sim.ovr.alt < 0.999 * sim.altmax * sim.p.R], fill_value='extrapolate')(grid) - radheat = interp1d(sim.ovr.alt, sim.cool.htot, fill_value='extrapolate')(grid) - radcool = interp1d(sim.ovr.alt, sim.cool.ctot, fill_value='extrapolate')(grid) - - #get isothermal Parker wind quantities - rho = interp1d(sim.par.prof.alt, sim.par.prof.rho, fill_value='extrapolate')(grid) - v = interp1d(sim.par.prof.alt, sim.par.prof.v, fill_value='extrapolate')(grid) - - #calculate bulk terms - expcool = -1 * calc_expansion(grid, rho, v, Te, mu) #minus sign to get expansion cooling rates as positive values - adv = calc_advection(grid, rho, v, Te, mu) - - #apply very slight smoothing because the Cloudy .ovr quantities have mediocre reported numerical precision - expcool = tools.smooth_gaus_savgol(expcool, fraction=0.01) - adv = tools.smooth_gaus_savgol(adv, fraction=0.01) - - advheat, advcool = np.copy(adv), -1 * np.copy(adv) - advheat[advheat < 0] = 0. - advcool[advcool < 0] = 0. - - return Te, mu, rho, v, radheat, radcool, expcool, advheat, advcool - - -def calc_HCratio(radheat, radcool, expcool, advheat, advcool): - """ - Calculates the ratio of total heating to total cooling. - - Parameters - ---------- - radheat : numpy.ndarray - Radiative heating rate in units of erg s-1 cm-3. - radcool : numpy.ndarray - Radiative cooling rate in units of erg s-1 cm-3, as positive values. - expcool : numpy.ndarray - Expansion cooling rate in units of erg s-1 cm-3, as positive values. - advheat : numpy.ndarray - Advection heating rate in units of erg s-1 cm-3. - advcool : numpy.ndarray - Advection cooling rate in units of erg s-1 cm-3, as positive values. - - Returns - ------- - HCratio : numpy.ndarray - Total heating rate H divided by total cooling rate C when H > C, - or -C / H when C > H. The absolute value of HCratio is always >=1, - and the sign indicates whether heating or cooling is stronger. - """ - - totheat = radheat + advheat - totcool = radcool + expcool + advcool #all cooling rates are positive values - nettotal = (totheat - totcool) - - HCratio = np.sign(nettotal) * np.maximum(totheat, totcool) / np.minimum(totheat,totcool) - - return HCratio - - -def get_new_Tstruc(old_Te, HCratio, fac): - """ - Returns a new temperature profile based on a previous non-converged - temperature profile and the associated heating/cooling imbalance. - - Parameters - ---------- - old_Te : numpy.ndarray - Previous temperature profile in units of K. - HCratio : numpy.ndarray - Heating/cooling imbalance, output of the calc_HCratio() function. - fac : numpy.ndarray - Scaling factor that sets how large the temperature adjustment is. - - Returns - ------- - newTe : numpy.ndarray - New temperature profile. - """ - - deltaT = fac * np.sign(HCratio) * np.log10(np.abs(HCratio)) #take log-based approach to deltaT - fT = np.copy(deltaT) #the temperature multiplication fraction - fT[deltaT < 0] = 1 + deltaT[deltaT < 0] - fT[deltaT > 0] = 1/(1 - deltaT[deltaT > 0]) - fT = np.clip(fT, 0.5, 2) #max change is a factor 2 up or down in temperature - newTe = old_Te * fT - newTe = np.clip(newTe, 1e1, 1e6) #set minimum temperature to 10K - - return newTe - - -def calc_cloc(radheat, radcool, expcool, advheat, advcool, HCratio): - """ - Checks if there is a point in the atmosphere where we can use - the construction algorithm. It searches for two criteria: - 1. If there is a point from where on advection heating is stronger than - radiative heating, and the temperature profile is reasonably converged. - 2. If there is a point from where on radiative cooling is weak - compared to expansion and advection cooling. - - Parameters - ---------- - radheat : numpy.ndarray - Radiative heating rate in units of erg s-1 cm-3. - radcool : numpy.ndarray - Radiative cooling rate in units of erg s-1 cm-3, as positive values. - expcool : numpy.ndarray - Expansion cooling rate in units of erg s-1 cm-3, as positive values. - advheat : numpy.ndarray - Advection heating rate in units of erg s-1 cm-3. - advcool : numpy.ndarray - Advection cooling rate in units of erg s-1 cm-3, as positive values. - HCratio : numpy.ndarray - Heating/cooling imbalance, output of the calc_HCratio() function. - - Returns - ------- - cloc : int - Index of the grid from where to start the construction algorithm. - """ - - def first_true_index(arr): - """ - Return the index of the first True value in the array. - If there are no True in the array, returns 0 - """ - return np.argmax(arr) - - def last_true_index(arr): - """ - Return the index of the last True value in the array. - If there are no True in the array, returns len(arr)-1 - """ - return len(arr) - np.argmax(arr[::-1]) - 1 - - def last_false_index(arr): - """ - Return the index of the last False value in the array. - If there are no False in the array, returns len(arr)-1 - """ - return len(arr) - np.argmax(~arr[::-1]) - 1 - - #check for advection dominated regime - adv_cloc = len(HCratio) #start by setting a 'too high' value - advheat_dominates = (advheat > radheat) #boolean array where advection heating dominates - bothrad_dominate = ((radheat > advheat) & (radcool > advcool) & (radcool > expcool)) #boolean array where radiative heating dominates AND radiative cooling dominates - highest_r_above_which_no_bothrad_dominate = last_true_index(bothrad_dominate) - advheat_dominates[:highest_r_above_which_no_bothrad_dominate] = False #now the boolean array stores where advection heating dominates AND where there is no point at higher altitudes that is rad. heat and rad. cool dominated - if True in advheat_dominates: #if there is no such point, adv_cloc stays default value - advdomloc = first_true_index(advheat_dominates) #get lowest altitude location where advection dominates - advheat_unimportant = (advheat < 0.25 * radheat) #boolean array where advection heating is relatively unimportant - advunimploc = last_true_index(advheat_unimportant[:advdomloc]) #first point at lower altitude where advection becomes unimportant (if no point exists, it will become advdomloc) - #then walk to higher altitude again to find converged point. We are more lax with H/C ratio if advection dominates more. - almost_converged = (np.abs(HCratio[advunimploc:]) < 1.3 * np.clip((advheat[advunimploc:] / radheat[advunimploc:])**(2./3.), 1, 10)) - if True in almost_converged: #otherwise it stays default value - adv_cloc = advunimploc + first_true_index(almost_converged) - - #check for regime where radiative cooling is weak. Usually this means that expansion cooling dominates, but advection cooling can contribute in some cases - expcool_dominates = (radcool / (radcool+expcool+advcool) < 0.2) # boolean array storing whether radiation cooling is weak - if False not in expcool_dominates: # if they are all True - exp_cloc = 0 - else: # if they are not all True - exp_cloc = last_false_index(expcool_dominates) #this way of evaluating it guarantees that all entries after this one are True - - cloc = min(adv_cloc, exp_cloc) #use the lowest radius point - - return cloc - - -def relaxTstruc(grid, path, itno, Te, HCratio): - """ - Proposes a new temperature profile using a 'relaxation' algorithm. - - Parameters - ---------- - grid : numpy.ndarray - Radius grid in units of cm. - path : str - Full path to the folder where the simulations are saved and ran. - itno : int - Iteration number. - Te : numpy.ndarray - Temperature profile of the last iteration at the 'grid' radii, in units of K. - HCratio : numpy.ndarray - Heating/cooling imbalance of the temperature profile of the last iteration, - output of the calc_HCratio() function. - - Returns - ------- - newTe_relax : numpy.ndarray - Adjusted temperature profile to use for the next iteration. - """ - - if itno == 2: #save for first time - np.savetxt(path+'iterations.txt', np.column_stack((grid, np.repeat(0.3, len(grid)), Te)), - header='grid fac1 Te1', comments='', delimiter=' ', fmt='%.7e') - - iterations_file = pd.read_csv(path+'iterations.txt', header=0, sep=' ') - fac = iterations_file['fac'+str(itno-1)].values - - newTe_relax = get_new_Tstruc(Te, HCratio, fac) #adjust the temperature profile - newTe_relax = tools.smooth_gaus_savgol(newTe_relax, fraction = 1./(20*itno)) #smooth it - newTe_relax = np.clip(newTe_relax, 1e1, 1e6) #smoothing may have pushed newTe_relax < 10K again. - - if itno >= 4: #check for fluctuations. If so, we decrease the deltaT factor - prev_prevTe = iterations_file['Te'+str(itno-2)] - previous_ratio = Te / prev_prevTe #compare itno-2 to itno-1 - this_ratio = newTe_relax / Te #compare itno-1 to the current itno (because of smoothing this ratio is not exactly the same as fT) - fl = (((previous_ratio < 1) & (this_ratio > 1)) | ((previous_ratio > 1) & (this_ratio < 1))) #boolean indicating where temperature fluctuates - fac[fl] = 2/3 * fac[fl] #take smaller changes in T in regions where the temperature fluctuates - fac = np.clip(tools.smooth_gaus_savgol(fac, size=10), 0.02, 0.3) #smooth the factor itself as well - newTe_relax = get_new_Tstruc(Te, HCratio, fac) #recalculate new temperature profile with updated fac - newTe_relax = tools.smooth_gaus_savgol(newTe_relax, fraction = 1/(20*itno)) #smooth it - newTe_relax = np.clip(newTe_relax, 1e1, 1e6) - - iterations_file['fac'+str(itno)] = fac - iterations_file.to_csv(path+'iterations.txt', sep=' ', float_format='%.7e', index=False) - - return newTe_relax - - -def constructTstruc(grid, newTe_relax, cloc, v, rho, mu, radheat, radcool): - """ - Proposes a new temperature profile based on a 'construction' algorithm, - starting at the cloc and at higher altitudes. - - Parameters - ---------- - grid : numpy.ndarray - Radius grid in units of cm. - newTe_relax : numpy.ndarray - Newly proposed temperature profile from the relaxation algorithm. - cloc : int - Index of the grid from where to start the construction algorithm. - v : numpy.ndarray - Velocity in units of cm s-1 at the 'grid' radii. - rho : numpy.ndarray - Density in units of g cm-3 at the 'grid' radii. - mu : numpy.ndarray - Mean particle mass in units of amu at the 'grid' radii. - radheat : numpy.ndarray - Radiative heating rate in units of erg s-1 cm-3, at the 'grid' radii. - radcool : numpy.ndarray - Radiative cooling rate in units of erg s-1 cm-3, as positive values, at the 'grid' radii. - - Returns - ------- - newTe_construct : numpy.ndarray - Adjusted temperature profile to use for the next iteration. - """ - - newTe_construct = np.copy(newTe_relax) #start with the temp struc from the relaxation function - - expansion_Tdivmu = tools.k/tools.mH * v * np.gradient(rho, grid) #this is expansion except for the T/mu term (still negative values) - advection_gradTdivmu = -1 * tools.k/(tools.mH * 2/3) * rho * v #this is advection except for the d(T/mu)/dr term - - def one_cell_HCratio(T, index): - expcool = expansion_Tdivmu[index] * T / mu[index] - adv = advection_gradTdivmu[index] * ((T/mu[index]) - (newTe_construct[index-1]/mu[index-1]))/(grid[index] - grid[index-1]) - - #instead of completely keeping the radiative heating and cooling rate the same while we are solving for T in this bin, - #we adjust it a little bit. This helps to prevent that the temperature changes are too drastic and go into a regime where - #radiation becomes important again. We guess a quadratic dependence of the rates on T. This is not the true dependence, - #but it does reduce to the original rate when T -> original T, which is important. - guess_radheat = radheat[index] * (newTe_construct[index] / T)**2 - guess_radcool = radcool[index] * (T / newTe_construct[index])**2 - - totheat = guess_radheat + max(adv, 0) #if adv is negative we don't add it here - totcool = guess_radcool - expcool - min(adv, 0) #if adv is positive we don't add it here, we subtract expcool and adv because they are negative - - HCratio = max(totheat, totcool) / min(totheat, totcool) #both entities are positive - - return HCratio - 1 #find root of this value to get H/C close to 1 - - - for i in range(cloc+1, len(grid)): #walk from cloc to higher altitudes - result = minimize_scalar(one_cell_HCratio, method='bounded', bounds=[1e1,1e6], args=(i)) - newTe_construct[i] = result.x - - - #smooth around the abrupt edge where the constructed part sets in - smooth_newTe_construct = tools.smooth_gaus_savgol(newTe_construct, fraction=0.03) #first smooth the complete T(r) profile - smooth_newTe_construct = np.clip(smooth_newTe_construct, 1e1, 1e6) #after smoothing we might have ended up below 10K - #now combine the smoothed profile around 'cloc', and the non-smoothed version away from 'cloc' - smooth_weight = np.zeros(len(grid)) - smooth_weight += sps.norm.pdf(range(len(grid)), cloc, int(len(grid)/30)) - smooth_weight /= np.max(smooth_weight) #normalize - raw_weight = 1 - smooth_weight - newTe_construct = smooth_newTe_construct * smooth_weight + newTe_construct * raw_weight - - return newTe_construct - - -def make_rates_plot(altgrid, Te, newTe_relax, radheat, radcool, expcool, advheat, advcool, rho, HCratio, altmax, fc, - newTe_construct=None, cloc=None, title=None, savename=None): - """ - Makes a plot of the previous and newly proposed temperature profiles, - as well as the different heating/cooling rates and their ratio based on the - previous temperature profile. - - Parameters - ---------- - altgrid : numpy.ndarray - Radius grid in units of Rp. - Te : numpy.ndarray - Temperature profile of the last iteration in units of K. - newTe_relax : numpy.ndarray - Proposed temperature profile based on the relaxation algorithm. - radheat : numpy.ndarray - Radiative heating rate in units of erg s-1 cm-3. - radcool : numpy.ndarray - Radiative cooling rate in units of erg s-1 cm-3, as positive values. - expcool : numpy.ndarray - Expansion cooling rate in units of erg s-1 cm-3, as positive values. - advheat : numpy.ndarray - Advection heating rate in units of erg s-1 cm-3. - advcool : numpy.ndarray - Advection cooling rate in units of erg s-1 cm-3, as positive values. - rho : numpy.ndarray - Density in units of g cm-3 - HCratio : numpy.ndarray - Heating/cooling imbalance, output of the calc_HCratio() function. - altmax : numeric - Maximum altitude of the simulation in units of planet radius. - fc : numeric - Convergence threshold for H/C. - newTe_construct : numpy.ndarray, optional - Proposed temperature profile based on the construction algorithm, by default None - cloc : int, optional - Index of the grid from where the construction algorithm was ran, by default None - title : str, optional - Title of the figure, by default None - savename : str, optional - Full path + filename to save the figure to, by default None - """ - - HCratiopos, HCrationeg = np.copy(HCratio), -1 * np.copy(HCratio) - HCratiopos[HCratiopos < 0] = 0. - HCrationeg[HCrationeg < 0] = 0. - - fig, (ax1, ax2, ax3) = plt.subplots(3, figsize=(4,7)) - if title != None: - ax1.set_title(title) - ax1.plot(altgrid, Te, color='#4CAF50', label='previous') - ax1.plot(altgrid, newTe_relax, color='#FFA500', label='relaxation') - if newTe_construct is not None: - ax1.plot(altgrid, newTe_construct, color='#800080', label='construction') - ax1.scatter(altgrid[cloc], newTe_relax[cloc], color='#800080') - ax1.set_ylabel('Temperature [K]') - ax1.legend(loc='best', fontsize=8) - - ax2.plot(altgrid, radheat/rho, color='red', linewidth=2.) - ax2.plot(altgrid, radcool/rho, color='blue') - ax2.plot(altgrid, expcool/rho, color='blue', linestyle='dashed') - ax2.plot(altgrid, advheat/rho, color='red', linestyle='dotted') - ax2.plot(altgrid, advcool/rho, color='blue', linestyle='dotted') - ax2.set_yscale('log') - ax2.set_ylim(0.1*min(min(radheat/rho), min(radcool/rho)), 2*max(max(radheat/rho), max(radcool/rho), max(expcool/rho), max(advheat/rho), max(advcool/rho))) - ax2.set_ylabel('Rate [erg/s/g]') - ax2.legend(((Line2D([], [], color='red', linestyle=(0,(6,6))), Line2D([], [], color='blue', linestyle=(6,(6,6)))), - Line2D([], [], color='blue', linestyle='dashed'), - (Line2D([], [], color='red', linestyle=(0,(1,2,1,8))), Line2D([], [], color='blue', linestyle=(6,(1,2,1,8))))), - ('radiation', 'expansion', 'advection'), loc='best', fontsize=8) - - ax3.plot(altgrid, HCratiopos, color='red') - ax3.plot(altgrid, HCrationeg, color='blue') - ax3.axhline(fc, color='k', linestyle='dotted') - ax3.set_yscale('log') - ax3.set_ylim(bottom=1) - ax3.set_ylabel('Ratio heat/cool') - - #use these with the altgrid: - tools.set_alt_ax(ax1, altmax=altmax, labels=False) - tools.set_alt_ax(ax2, altmax=altmax, labels=False) - tools.set_alt_ax(ax3, altmax=altmax, labels=True) - - fig.tight_layout() - if savename != None: - plt.savefig(savename, bbox_inches='tight', dpi=200) - plt.clf() - plt.close() - - -def make_converged_plot(altgrid, altmax, path, Te, radheat, rho, radcool, expcool, advheat, advcool): - """ - Makes a plot of the converged temperature profile, as well as the different - heating/cooling rates. - - Parameters - ---------- - altgrid : numpy.ndarray - Radius grid in units of Rp. - altmax : numeric - Maximum altitude of the simulation in units of planet radius. - path : _type_ - _description_ - Te : numpy.ndarray - Converged temperature profile in units of K. - radheat : numpy.ndarray - Radiative heating rate in units of erg s-1 cm-3. - rho : numpy.ndarray - Density in units of g cm-3 - radcool : numpy.ndarray - Radiative cooling rate in units of erg s-1 cm-3, as positive values. - expcool : numpy.ndarray - Expansion cooling rate in units of erg s-1 cm-3, as positive values. - advheat : numpy.ndarray - Advection heating rate in units of erg s-1 cm-3. - advcool : numpy.ndarray - Advection cooling rate in units of erg s-1 cm-3, as positive values. - """ - - fig, (ax1, ax2) = plt.subplots(2, figsize=(4,5.5)) - ax1.plot(altgrid, Te, color='k') - ax1.set_ylabel('Temperature [K]') - - ax2.plot(altgrid, radheat/rho, color='red') - ax2.plot(altgrid, radcool/rho, color='blue') - ax2.plot(altgrid, expcool/rho, color='blue', linestyle='dashed') - ax2.plot(altgrid, advheat/rho, color='red', linestyle='dotted') - ax2.plot(altgrid, advcool/rho, color='blue', linestyle='dotted') - ax2.set_yscale('log') - ax2.set_ylim(0.1*min(min(radheat/rho), min(radcool/rho)), 2*max(max(radheat/rho), max(radcool/rho), max(expcool/rho), max(advheat/rho), max(advcool/rho))) - ax2.set_ylabel('Rate [erg/s/g]') - ax2.legend(((Line2D([], [], color='red', linestyle=(0,(6,6))), Line2D([], [], color='blue', linestyle=(6,(6,6)))), - Line2D([], [], color='blue', linestyle='dashed'), - (Line2D([], [], color='red', linestyle=(0,(1,2,1,8))), Line2D([], [], color='blue', linestyle=(6,(1,2,1,8))))), - ('radiation', 'expansion', 'advection'), loc='best', fontsize=8) - - - #use these with the altgrid: - tools.set_alt_ax(ax1, altmax=altmax, labels=False) - tools.set_alt_ax(ax2, altmax=altmax) - - fig.tight_layout() - plt.savefig(path+'converged.png', bbox_inches='tight', dpi=200) - plt.clf() - plt.close() - - -def check_converged(fc, HCratio, newTe, prevTe, linthresh=50.): - """ - Checks whether the temperature profile is converged. At every radial cell, - it checks for three conditions, one of which must be satisfied: - 1. The H/C ratio is less than fc (this is the "main" criterion). - 2. The newly proposed temperature profile is within the temperature difference - that a H/C equal to fc would induce. In principle, we would expect that if - this were the case, H/C itself would be < fc, but smoothing of the - temperature profile can cause different behavior. For example, we can get stuck - in a loop where H/C > fc, we then propose a new temperature profile that is - significantly different, but then after the smoothing step we end up with - the profile that we had before. To break out of such a loop that never converges, - we check if the temperature changes are less than we would expect for an - "fc-converged" profile, even if H/C itself is still >fc. In practice, this - means that the temperature profile changes less than 0.3 * log10(1.1), - which is ~1%, so up to 100 K for a typical profile. - 3. The newly proposed temperature profile is less than `linthresh` different - from the last iteration. This can be assumed to be precise enough convergence. - - Parameters - ---------- - fc : numeric - Convergence threshold for the total heating/cooling ratio. - HCratio : numpy.ndarray - Heating/cooling imbalance, output of the calc_HCratio() function. - newTe : numpy.ndarray - Newly proposed temperature profile based on both relaxation and - construction algorithms, in units of K. - prevTe : numpy.ndarray - Temperature profile of the previous iteration in units of K. - linthresh : numeric, optional - Convergence threshold for T(r) as an absolute temperature difference - in units of K, by default 50. - - Returns - ------- - converged : bool - Whether the temperature profile is converged. - """ - - ratioTe = np.maximum(newTe, prevTe) / np.minimum(newTe, prevTe) #take element wise ratio - diffTe = np.abs(newTe - prevTe) #take element-wise absolute difference - - if np.all((np.abs(HCratio) < fc) | (ratioTe < (1 + 0.3 * np.log10(fc))) | (diffTe < linthresh)): - converged = True - else: - converged = False - - return converged - - -def clean_converged_folder(folder): - """ - Deletes all files in a folder that are not called "converged*". - In the context of this module, it thus cleans all files of earlier - iterations, as well as helper files, preserving only the final - converged simulation. - - Parameters - ---------- - folder : str - Folder where the iterative algorithm is ran, typically: - $SUNBATHER_PROJECT_PATH/sims/1D/*plname*/*dir*/parker_*T0*_*Mdot*/ - """ - - if not os.path.isdir(folder): - warnings.warn(f"This folder does not exist: {folder}") - - elif not os.path.isfile(folder+'/converged.in'): - warnings.warn(f"This folder wasn't converged, I will not clean it: {folder}") - - else: - for filename in os.listdir(folder): - if filename[:9] != 'converged' and os.path.isfile(os.path.join(folder, filename)): - os.remove(os.path.join(folder, filename)) - - -def run_loop(path, itno, fc, save_sp=[], maxit=16): - """ - Solves for the nonisothermal temperature profile of a Parker wind - profile through an iterative convergence scheme including Cloudy. - - Parameters - ---------- - path : str - Folder where the iterative algorithm is ran, typically: - $SUNBATHER_PROJECT_PATH/sims/1D/*plname*/*dir*/parker_*T0*_*Mdot*/. - In this folder, the 'template.in' and 'iteration1.in' files must be - present, which are created automatically by the convergeT_parker.py module. - itno : int - Iteration number to start from. Can only be different from 1 if - this profile has been (partly) solved before. - fc : float - H/C convergence factor, see Linssen et al. (2024). A sensible value is 1.1. - save_sp : list, optional - A list of atomic/ionic species to let Cloudy save the number density profiles - for in the final converged simulation. Those are needed when doing radiative - transfer to produce transmission spectra. For example, to be able to make - metastable helium spectra, 'He' needs to be in the save_sp list. By default []. - maxit : int, optional - Maximum number of iterations, by default 16. - """ - - if itno == 1: #iteration1 is just running Cloudy. Then, we move on to iteration2 - tools.run_Cloudy('iteration1', folder=path) - itno += 1 - - #now, we have ran our iteration1 and can start the iterative scheme to find a new profile: - while itno <= maxit: - prev_sim = tools.Sim(path+f'iteration{itno-1}') #load Cloudy results from previous iteration - Rp = prev_sim.p.R #planet radius in cm - altmax = prev_sim.altmax #maximum radius of the simulation in units of Rp - - #make logspaced grid to use throughout the code, interpolate all quantities onto this grid. - rgrid = np.logspace(np.log10(Rp), np.log10(altmax*Rp), num=1000) - - Te, mu, rho, v, radheat, radcool, expcool, advheat, advcool = simtogrid(prev_sim, rgrid) #get all needed Cloudy quantities on the grid - HCratio = calc_HCratio(radheat, radcool, expcool, advheat, advcool) #H/C or C/H ratio, depending on which is larger - - #now the procedure starts - we first produce a new temperature profile - newTe_relax = relaxTstruc(rgrid, path, itno, Te, HCratio) #apply the relaxation algorithm - cloc = calc_cloc(radheat, radcool, expcool, advheat, advcool, HCratio) #look for a point from where we could use construction - newTe_construct = None - if cloc < len(rgrid) - 1: - newTe_construct = constructTstruc(rgrid, newTe_relax, int(cloc), v, rho, mu, radheat, radcool) #apply construction algorithm - - make_rates_plot(rgrid/Rp, Te, newTe_relax, radheat, radcool, expcool, advheat, advcool, - rho, HCratio, altmax, fc, title=f'iteration {itno}', - savename=path+f'iteration{itno}.png', newTe_construct=newTe_construct, cloc=cloc) - - #get the final new temperature profile, based on whether the construction algorithm was applied - if newTe_construct is None: - newTe = newTe_relax - else: - newTe = newTe_construct - - #add this temperature profile to the 'iterations' file for future reference - iterations_file = pd.read_csv(path+'iterations.txt', header=0, sep=' ') - iterations_file['Te'+str(itno)] = newTe - iterations_file.to_csv(path+'iterations.txt', sep=' ', float_format='%.7e', index=False) - - #now we check if the profile is converged. - if itno <= 2: #always update the Te profile at least once - in case we start from a 'close' Parker wind profile that immediately satisfies fc - converged = False - else: - prevTe = iterations_file['Te'+str(itno-1)].values #read out from file instead of Sim because the file has higher resolution - converged = check_converged(fc, HCratio, newTe, prevTe, linthresh=50.) #check convergence criteria - - if converged: #run once more with more output - make_converged_plot(rgrid/Rp, altmax, path, Te, radheat, rho, radcool, expcool, advheat, advcool) - #calculate these terms for the output converged.txt file - for fast access of some key parameters without loading in the Cloudy sim. - np.savetxt(path+'converged.txt', np.column_stack((rgrid/Rp, rho, Te, mu, radheat, radcool, expcool, advheat, advcool)), fmt='%1.5e', - header='R rho Te mu radheat radcool expcool advheat advcool', comments='') - - #we run the last simulation one more time but with all the output files - tools.copyadd_Cloudy_in(path+'iteration'+str(itno-1), path+'converged', - outfiles=['.heat', '.den', '.en'], denspecies=save_sp, - selected_den_levels=True, hcfrac=0.01) - tools.run_Cloudy('converged', folder=path) - tools.Sim(path+'converged') #read in the simulation, so we open the .en file (if it exists) and hence compress its size (see tools.process_energies()) - clean_converged_folder(path) #remove all non-converged files - print(f"Temperature profile converged: {path}") - - break - - else: #set up the next iteration - Cltlaw = tools.alt_array_to_Cloudy(rgrid, newTe, altmax, Rp, 1000) #convert the temperature profile to a table format accepted by Cloudy - - tools.copyadd_Cloudy_in(path+'template', path+'iteration'+str(itno), tlaw=Cltlaw) #add temperature profile to the template input file - if itno != maxit: #no use running it if we are not entering the next while-loop iteration - tools.run_Cloudy(f'iteration{itno}', folder=path) - else: - print(f"Failed temperature convergence after {itno} iterations: {path}") - - itno += 1 diff --git a/src/RT.py b/src/sunbather/RT.py similarity index 58% rename from src/RT.py rename to src/sunbather/RT.py index 8120009..482c8e2 100644 --- a/src/RT.py +++ b/src/sunbather/RT.py @@ -1,22 +1,28 @@ -#sunbather imports -import tools - -#other imports +import warnings import pandas as pd import numpy as np -import numpy.ma as ma +from numpy import ma from scipy.interpolate import interp1d from scipy.special import voigt_profile from scipy.integrate import trapezoid from scipy.ndimage import gaussian_filter1d -import warnings +from sunbather import tools -sigt0 = 2.654e-2 #cm2 s-1 = cm2 Hz, from Axner et al. 2004 +sigt0 = 2.654e-2 # cm2 s-1 = cm2 Hz, from Axner et al. 2004 -def project_1D_to_2D(r1, q1, Rp, numb=101, x_projection=False, cut_at=None, - skip_alt_range=None, skip_alt_range_dayside=None, skip_alt_range_nightside=None): +def project_1D_to_2D( + r1, + q1, + Rp, + numb=101, + x_projection=False, + cut_at=None, + skip_alt_range=None, + skip_alt_range_dayside=None, + skip_alt_range_nightside=None, +): """ Projects a 1D sub-stellar solution onto a 2D grid. This function preserves the maximum altitude of the 1D ray, so that the 2D output looks like a half @@ -81,30 +87,48 @@ def project_1D_to_2D(r1, q1, Rp, numb=101, x_projection=False, cut_at=None, assert r1[1] > r1[0], "arrays must be in order of ascending altitude" - b_edges = np.logspace(np.log10(0.1*Rp), np.log10(r1[-1] - 0.9*Rp), num=numb) + 0.9*Rp #impact parameters for 2D rays - these are the boundaries of the 'rays' - b_centers = (b_edges[1:] + b_edges[:-1]) / 2. #these are the actual positions of the rays and this is where the quantity is calculated at - xhalf = np.logspace(np.log10(0.101*Rp), np.log10(r1[-1]+0.1*Rp), num=numb) - 0.1*Rp #positive x grid - x = np.concatenate((-xhalf[::-1], xhalf)) #total x grid with both negative and positive values (for day- and nightside) + b_edges = ( + np.logspace(np.log10(0.1 * Rp), np.log10(r1[-1] - 0.9 * Rp), num=numb) + + 0.9 * Rp + ) # impact parameters for 2D rays - these are the boundaries of the 'rays' + b_centers = ( + b_edges[1:] + b_edges[:-1] + ) / 2.0 # these are the actual positions of the rays and this is where the quantity is calculated at + xhalf = ( + np.logspace(np.log10(0.101 * Rp), np.log10(r1[-1] + 0.1 * Rp), num=numb) + - 0.1 * Rp + ) # positive x grid + x = np.concatenate( + (-xhalf[::-1], xhalf) + ) # total x grid with both negative and positive values (for day- and nightside) xx, bb = np.meshgrid(x, b_centers) - rr = np.sqrt(bb**2 + xx**2) #radii from planet core in 2D + rr = np.sqrt(bb**2 + xx**2) # radii from planet core in 2D - q2 = interp1d(r1, q1, fill_value=0., bounds_error=False)(rr) + q2 = interp1d(r1, q1, fill_value=0.0, bounds_error=False)(rr) if x_projection: - q2 = q2 * xx / rr #now q2 is the projection in the x-direction + q2 = q2 * xx / rr # now q2 is the projection in the x-direction - if cut_at != None: #set values to zero outside the cut_at boundary - q2[rr > cut_at] = 0. + if cut_at is not None: # set values to zero outside the cut_at boundary + q2[rr > cut_at] = 0.0 - #some options that were used in Linssen&Oklopcic (2023) to find where the line contribution comes from: + # some options that were used in Linssen&Oklopcic (2023) to find where the line contribution comes from: if skip_alt_range is not None: assert skip_alt_range[0] < skip_alt_range[1] - q2[(rr > skip_alt_range[0]) & (rr < skip_alt_range[1])] = 0. + q2[(rr > skip_alt_range[0]) & (rr < skip_alt_range[1])] = 0.0 if skip_alt_range_dayside is not None: assert skip_alt_range_dayside[0] < skip_alt_range_dayside[1] - q2[(rr > skip_alt_range_dayside[0]) & (rr < skip_alt_range_dayside[1]) & (xx < 0.)] = 0. + q2[ + (rr > skip_alt_range_dayside[0]) + & (rr < skip_alt_range_dayside[1]) + & (xx < 0.0) + ] = 0.0 if skip_alt_range_nightside is not None: assert skip_alt_range_nightside[0] < skip_alt_range_nightside[1] - q2[(rr > skip_alt_range_nightside[0]) & (rr < skip_alt_range_nightside[1]) & (xx > 0.)] = 0. + q2[ + (rr > skip_alt_range_nightside[0]) + & (rr < skip_alt_range_nightside[1]) + & (xx > 0.0) + ] = 0.0 return b_edges, b_centers, x, q2 @@ -138,9 +162,13 @@ def limbdark_quad(mu, ab): limb darkening law. """ - a, b = ab[:,0], ab[:,1] - I = 1 - a[:,None,None]*(1-mu[None,:,:]) - b[:,None,None]*(1-mu[None,:,:])**2 - + a, b = ab[:, 0], ab[:, 1] + I = ( + 1 + - a[:, None, None] * (1 - mu[None, :, :]) + - b[:, None, None] * (1 - mu[None, :, :]) ** 2 + ) + return I @@ -165,19 +193,21 @@ def avg_limbdark_quad(ab): the quadratic limb darkening law. """ - a, b = ab[:,0], ab[:,1] - rf = np.linspace(0, 1, num=1000) #sample the stellar disk in 1000 rings - rfm = (rf[:-1] + rf[1:])/2 #midpoints - mu = np.sqrt(1 - rfm**2) #mu of each ring - I = 1 - a[:,None]*(1-mu[None,:]) - b[:,None]*(1-mu[None,:])**2 #I of each ring - projsurf = np.pi*(rf[1:]**2 - rf[:-1]**2) #area of each ring + a, b = ab[:, 0], ab[:, 1] + rf = np.linspace(0, 1, num=1000) # sample the stellar disk in 1000 rings + rfm = (rf[:-1] + rf[1:]) / 2 # midpoints + mu = np.sqrt(1 - rfm**2) # mu of each ring + I = ( + 1 - a[:, None] * (1 - mu[None, :]) - b[:, None] * (1 - mu[None, :]) ** 2 + ) # I of each ring + projsurf = np.pi * (rf[1:] ** 2 - rf[:-1] ** 2) # area of each ring - I_avg = np.sum(I * projsurf, axis=1) / np.pi #sum over the radial axis + I_avg = np.sum(I * projsurf, axis=1) / np.pi # sum over the radial axis return I_avg -def calc_tau(x, ndens, Te, vx, nu, nu0, m, sig0, gamma, v_turb=0.): +def calc_tau(x, ndens, Te, vx, nu, nu0, m, sig0, gamma, v_turb=0.0): """ Calculates optical depth using Eq. 19 from Oklopcic&Hirata 2018. Does this at once for all rays, lines and frequencies. When doing @@ -230,16 +260,29 @@ def calc_tau(x, ndens, Te, vx, nu, nu0, m, sig0, gamma, v_turb=0.): if not isinstance(gamma, np.ndarray): gamma = np.array([gamma]) - gaus_sigma = np.sqrt(tools.k * Te[None,None,:] / m + 0.5*v_turb**2) * nu0[None,:,None,None] / tools.c - #the following has a minus sign like in Eq. 21 of Oklopcic&Hirata (2018) because their formula is only correct if you take v_LOS from star->planet i.e. vx - Delnu = (nu[:,None,None,None] - nu0[None,:,None,None]) - nu0[None,:,None,None] / tools.c * vx[None,None,:] - tau_cube = trapezoid(ndens[None,None,:] * sig0[None,:,None,None] * voigt_profile(Delnu, gaus_sigma, gamma[None,:,None,None]), x=x) - tau = np.sum(tau_cube, axis=1) #sum up the contributions of the different lines -> now tau has axis 0:freq, axis 1:rayno + gaus_sigma = ( + np.sqrt(tools.k * Te[None, None, :] / m + 0.5 * v_turb**2) + * nu0[None, :, None, None] + / tools.c + ) + # the following has a minus sign like in Eq. 21 of Oklopcic&Hirata (2018) because their formula is only correct if you take v_LOS from star->planet i.e. vx + Delnu = (nu[:, None, None, None] - nu0[None, :, None, None]) - nu0[ + None, :, None, None + ] / tools.c * vx[None, None, :] + tau_cube = trapezoid( + ndens[None, None, :] + * sig0[None, :, None, None] + * voigt_profile(Delnu, gaus_sigma, gamma[None, :, None, None]), + x=x, + ) + tau = np.sum( + tau_cube, axis=1 + ) # sum up the contributions of the different lines -> now tau has axis 0:freq, axis 1:rayno return tau -def calc_cum_tau(x, ndens, Te, vx, nu, nu0, m, sig0, gamma, v_turb=0.): +def calc_cum_tau(x, ndens, Te, vx, nu, nu0, m, sig0, gamma, v_turb=0.0): """ Calculates cumulative optical depth using Eq. 19 from Oklopcic&Hirata 2018, at one particular frequency. Does this at once for all rays and lines. @@ -291,19 +334,33 @@ def calc_cum_tau(x, ndens, Te, vx, nu, nu0, m, sig0, gamma, v_turb=0.): if not isinstance(gamma, np.ndarray): gamma = np.array([gamma]) - gaus_sigma = np.sqrt(tools.k * Te[None,None,:] / m + 0.5*v_turb**2) * nu0[None,:,None,None] / tools.c - #the following has a minus sign like in Eq. 21 of Oklopcic&Hirata (2018) because their formula is only correct if you take v_LOS from star->planet i.e. vx - Delnu = (nu - nu0[:,None,None]) - nu0[:,None,None] / tools.c * vx[None,:] - integrand = ndens[None,:] * sig0[:,None,None] * voigt_profile(Delnu, gaus_sigma, gamma[:,None,None]) + gaus_sigma = ( + np.sqrt(tools.k * Te[None, None, :] / m + 0.5 * v_turb**2) + * nu0[None, :, None, None] + / tools.c + ) + # the following has a minus sign like in Eq. 21 of Oklopcic&Hirata (2018) because their formula is only correct if you take v_LOS from star->planet i.e. vx + Delnu = (nu - nu0[:, None, None]) - nu0[:, None, None] / tools.c * vx[None, :] + integrand = ( + ndens[None, :] + * sig0[:, None, None] + * voigt_profile(Delnu, gaus_sigma, gamma[:, None, None]) + ) bin_tau = np.zeros_like(integrand) - bin_tau[:,:,1:] = (integrand[:,:,1:] + np.roll(integrand, 1, axis=2)[:,:,1:])/2. * np.diff(x)[None,None,:] - bin_tau = np.sum(bin_tau, axis=0) #sum up contribution of different lines, now bin_tau has same shape as Te - cum_tau = np.cumsum(bin_tau, axis=1) #do cumulative sum over the x-direction + bin_tau[:, :, 1:] = ( + (integrand[:, :, 1:] + np.roll(integrand, 1, axis=2)[:, :, 1:]) + / 2.0 + * np.diff(x)[None, None, :] + ) + bin_tau = np.sum( + bin_tau, axis=0 + ) # sum up contribution of different lines, now bin_tau has same shape as Te + cum_tau = np.cumsum(bin_tau, axis=1) # do cumulative sum over the x-direction return cum_tau, bin_tau -def tau_to_FinFout(b_edges, tau, Rs, bp=0., ab=np.zeros(2), a=0., phase=0.): +def tau_to_FinFout(b_edges, tau, Rs, bp=0.0, ab=np.zeros(2), a=0.0, phase=0.0): """ Takes in optical depth values and calculates the Fin/Fout transit spectrum, using the stellar radius and optional limb darkening and transit phase @@ -342,25 +399,47 @@ def tau_to_FinFout(b_edges, tau, Rs, bp=0., ab=np.zeros(2), a=0., phase=0.): """ if ab.ndim == 1: - ab = ab[None,:] - - #add some impact parameters and tau=inf bins that make up the planet core: - b_edges = np.concatenate((np.linspace(0, b_edges[0], num=50, endpoint=False), b_edges)) - b_centers = (b_edges[1:] + b_edges[:-1]) / 2 #calculate bin centers with the added planet core rays included - tau = np.concatenate((np.ones((np.shape(tau)[0], 50))*np.inf, tau), axis=1) - - projsurf = np.pi*(b_edges[1:]**2 - b_edges[:-1]**2) #ring surface of each ray (now has same length as b_centers) - phis = np.linspace(0, 2*np.pi, num=500, endpoint=False) #divide rings into different angles phi - #rc is the distance to stellar center. Axis 0: radial rings, axis 1: phi - rc = np.sqrt((bp*Rs + b_centers[:,None]*np.cos(phis[None,:]))**2 + (b_centers[:,None]*np.sin(phis[None,:]) + a*np.sin(2*np.pi*phase))**2) - rc = ma.masked_where(rc > Rs, rc) #will ensure I is masked (and later set to 0) outside stellar projected disk - mu = np.sqrt(1 - (rc/Rs)**2) #angle, see 'limbdark_quad' function + ab = ab[None, :] + + # add some impact parameters and tau=inf bins that make up the planet core: + b_edges = np.concatenate( + (np.linspace(0, b_edges[0], num=50, endpoint=False), b_edges) + ) + b_centers = ( + b_edges[1:] + b_edges[:-1] + ) / 2 # calculate bin centers with the added planet core rays included + tau = np.concatenate((np.ones((np.shape(tau)[0], 50)) * np.inf, tau), axis=1) + + projsurf = np.pi * ( + b_edges[1:] ** 2 - b_edges[:-1] ** 2 + ) # ring surface of each ray (now has same length as b_centers) + phis = np.linspace( + 0, 2 * np.pi, num=500, endpoint=False + ) # divide rings into different angles phi + # rc is the distance to stellar center. Axis 0: radial rings, axis 1: phi + rc = np.sqrt( + (bp * Rs + b_centers[:, None] * np.cos(phis[None, :])) ** 2 + + (b_centers[:, None] * np.sin(phis[None, :]) + a * np.sin(2 * np.pi * phase)) + ** 2 + ) + rc = ma.masked_where( + rc > Rs, rc + ) # will ensure I is masked (and later set to 0) outside stellar projected disk + mu = np.sqrt(1 - (rc / Rs) ** 2) # angle, see 'limbdark_quad' function I = limbdark_quad(mu, ab) - Ir_avg = np.sum(I, axis=2) / len(phis) #average I per ray - Ir_avg = Ir_avg.filled(fill_value=0.) #convert back to regular numpy array - Is_avg = avg_limbdark_quad(ab) #average I of the full stellar disk - - FinFout = np.ones_like(tau[:,0]) - np.sum(((1 - np.exp(-tau)) * Ir_avg*projsurf[None,:]/(Is_avg[:,None]*np.pi*Rs**2)), axis=1) + Ir_avg = np.sum(I, axis=2) / len(phis) # average I per ray + Ir_avg = Ir_avg.filled(fill_value=0.0) # convert back to regular numpy array + Is_avg = avg_limbdark_quad(ab) # average I of the full stellar disk + + FinFout = np.ones_like(tau[:, 0]) - np.sum( + ( + (1 - np.exp(-tau)) + * Ir_avg + * projsurf[None, :] + / (Is_avg[:, None] * np.pi * Rs**2) + ), + axis=1, + ) return FinFout @@ -383,28 +462,49 @@ def read_NIST_lines(species, wavlower=None, wavupper=None): Line coefficients needed for radiative transfer calculations. """ - spNIST = pd.read_table(tools.sunbatherpath+'/RT_tables/'+species+'_lines_NIST.txt') #line info - #remove lines with nan fik or Aik values. Note that lineno doesn't change (uses index instead of rowno.) + spNIST = pd.read_table( + tools.sunbatherpath + "/RT_tables/" + species + "_lines_NIST.txt" + ) # line info + # remove lines with nan fik or Aik values. Note that lineno doesn't change (uses index instead of rowno.) spNIST = spNIST[spNIST.fik.notna()] - spNIST = spNIST[spNIST['Aki(s^-1)'].notna()] + spNIST = spNIST[spNIST["Aki(s^-1)"].notna()] if spNIST.empty: warnings.warn(f"No lines with necessary coefficients found for {species}") return spNIST - if type(spNIST['Ei(Ry)'].iloc[0]) == str: #if there are no [](), the datatype will be float already - spNIST['Ei(Ry)'] = spNIST['Ei(Ry)'].str.extract('(\d+)', expand=False).astype(float) #remove non-numeric characters such as [] and () - spNIST['sig0'] = sigt0 * spNIST.fik - spNIST['nu0'] = tools.c*1e8 / (spNIST['ritz_wl_vac(A)']) #speed of light to AA/s - spNIST['lorgamma'] = spNIST['Aki(s^-1)'] / (4*np.pi) #lorentzian gamma is not function of depth or nu. Value in Hz - - if wavlower != None: - spNIST.drop(labels=spNIST.index[spNIST['ritz_wl_vac(A)'] <= wavlower], inplace=True) - if wavupper != None: - spNIST.drop(labels=spNIST.index[spNIST['ritz_wl_vac(A)'] >= wavupper], inplace=True) + if isinstance(spNIST["Ei(Ry)"].iloc[0], str): # if there are no [](), the datatype will be float already + spNIST["Ei(Ry)"] = ( + spNIST["Ei(Ry)"].str.extract(r"(\d+)", expand=False).astype(float) + ) # remove non-numeric characters such as [] and () + spNIST["sig0"] = sigt0 * spNIST.fik + spNIST["nu0"] = tools.c * 1e8 / (spNIST["ritz_wl_vac(A)"]) # speed of light to AA/s + spNIST["lorgamma"] = spNIST["Aki(s^-1)"] / ( + 4 * np.pi + ) # lorentzian gamma is not function of depth or nu. Value in Hz + + if wavlower is not None: + spNIST.drop( + labels=spNIST.index[spNIST["ritz_wl_vac(A)"] <= wavlower], inplace=True + ) + if wavupper is not None: + spNIST.drop( + labels=spNIST.index[spNIST["ritz_wl_vac(A)"] >= wavupper], inplace=True + ) return spNIST -def FinFout(sim, wavsAA, species, numrays=100, width_fac=1., ab=np.zeros(2), phase=0., phase_bulkshift=False, v_turb=0., cut_at=None): +def FinFout( + sim, + wavsAA, + species, + numrays=100, + width_fac=1.0, + ab=np.zeros(2), + phase=0.0, + phase_bulkshift=False, + v_turb=0.0, + cut_at=None, +): """ Calculates a transit spectrum in units of in-transit flux / out-of-transit flux (i.e., Fin/Fout). Only spectral lines originating from provided species will be calculated. @@ -412,7 +512,7 @@ def FinFout(sim, wavsAA, species, numrays=100, width_fac=1., ab=np.zeros(2), pha Parameters ---------- sim : tools.Sim - Cloudy simulation output of an upper atmosphere. Needs to have tools.Planet and + Cloudy simulation output of an upper atmosphere. Needs to have tools.Planet and tools.Parker class attributes. wavsAA : array-like Wavelengths to calculate transit spectrum on, in units of Å (1D array). @@ -466,19 +566,27 @@ def FinFout(sim, wavsAA, species, numrays=100, width_fac=1., ab=np.zeros(2), pha but which could not be calculated due to their excitation state not being reported by Cloudy. """ - assert hasattr(sim, 'p'), "The sim must have an attributed Planet object" - assert 'v' in sim.ovr.columns, "We need a velocity structure, such as that from adding a Parker object to the sim" - assert hasattr(sim, 'den'), "The sim must have a .den file that stores the densities of the atomic/ionic excitation states. " \ - "Please re-run your Cloudy simulation while saving these. Either re-run sunbather.convergeT_parker.py " \ - "with the -save_sp flag, or use the tools.insertden_Cloudy_in() function with rerun=True." - - ab = np.array(ab) #turn possible list into array + assert hasattr(sim, "p"), "The sim must have an attributed Planet object" + assert ( + "v" in sim.ovr.columns + ), "We need a velocity structure, such as that from adding a Parker object to the sim" + assert hasattr(sim, "den"), ( + "The sim must have a .den file that stores the densities of the atomic/ionic excitation states. " + "Please re-run your Cloudy simulation while saving these. Either re-run sunbather.convergeT_parker.py " + "with the -save_sp flag, or use the tools.insertden_Cloudy_in() function with rerun=True." + ) + + ab = np.array(ab) # turn possible list into array if ab.ndim == 1: - ab = ab[None,:] #add frequency axis - assert ab.ndim == 2 and np.shape(ab)[1] == 2 and (np.shape(ab)[0] == 1 or np.shape(ab)[0] == len(wavsAA)), "Give ab as shape (1,2) or (2,) or (len(wavsAA),2)" + ab = ab[None, :] # add frequency axis + assert ( + ab.ndim == 2 + and np.shape(ab)[1] == 2 + and (np.shape(ab)[0] == 1 or np.shape(ab)[0] == len(wavsAA)) + ), "Give ab as shape (1,2) or (2,) or (len(wavsAA),2)" Rs, Rp = sim.p.Rstar, sim.p.R - nus = tools.c*1e8 / wavsAA #Hz, converted c to AA/s + nus = tools.c * 1e8 / wavsAA # Hz, converted c to AA/s r1 = sim.ovr.alt.values[::-1] Te1 = sim.ovr.Te.values[::-1] @@ -488,70 +596,119 @@ def FinFout(sim, wavsAA, species, numrays=100, width_fac=1., ab=np.zeros(2), pha be, _, x, vx = project_1D_to_2D(r1, v1, Rp, numb=numrays, x_projection=True) if phase_bulkshift: - assert hasattr(sim.p, 'Kp'), "The Planet object does not have a Kp attribute, likely because either a, Mp or Mstar is unknown" - vx = vx - sim.p.Kp * np.sin(phase * 2*np.pi) #negative sign because x is defined as positive towards the observer. + assert hasattr( + sim.p, "Kp" + ), "The Planet object does not have a Kp attribute, likely because either a, Mp or Mstar is unknown" + vx = vx - sim.p.Kp * np.sin( + phase * 2 * np.pi + ) # negative sign because x is defined as positive towards the observer. state_ndens = {} - tau = np.zeros((len(wavsAA), len(be)-1)) + tau = np.zeros((len(wavsAA), len(be) - 1)) if isinstance(species, str): species = [species] - found_lines = [] #will store nu0 of all lines that were used (might be nice to make it a dict per species in future!) - notfound_lines = [] #will store nu0 of all lines that were not found + found_lines = ( + [] + ) # will store nu0 of all lines that were used (might be nice to make it a dict per species in future!) + notfound_lines = [] # will store nu0 of all lines that were not found for spec in species: if spec in sim.den.columns: - warnings.warn(f"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. " + \ - f"I will make the spectrum assuming all {spec} is in the ground-state.") - elif not any(spec+"[" in col for col in sim.den.columns): - warnings.warn(f"Your requested species {spec} is not present in Cloudy's output, so the spectrum will be flat. " + \ - "Please re-do your Cloudy simulation while saving this species. Either use the tools.insertden_Cloudy_in() " + \ - "function, or run convergeT_parker.py again with the correct -save_sp arguments.") + warnings.warn( + f"Your requested species {spec} is not resolved into multiple energy levels by Cloudy. " + + f"I will make the spectrum assuming all {spec} is in the ground-state." + ) + elif not any(spec + "[" in col for col in sim.den.columns): + warnings.warn( + f"Your requested species {spec} is not present in Cloudy's output, so the spectrum will be flat. " + + "Please re-do your Cloudy simulation while saving this species. Either use the tools.insertden_Cloudy_in() " + + "function, or run convergeT_parker.py again with the correct -save_sp arguments." + ) continue spNIST = read_NIST_lines(spec, wavlower=wavsAA[0], wavupper=wavsAA[-1]) - - if len(species) == 1 and len(spNIST) == 0: - warnings.warn(f"Your requested species {spec} does not have any lines in this wavelength range (according to the NIST database), " \ - "so the spectrum will be flat.") - - for lineno in spNIST.index.values: #loop over all lines in the spNIST table. - gaus_sigma_max = np.sqrt(tools.k * np.nanmax(Te) / tools.get_mass(spec) + 0.5*v_turb**2) * spNIST.nu0.loc[lineno] / tools.c #maximum stddev of Gaussian part - max_voigt_width = 5*(gaus_sigma_max+spNIST['lorgamma'].loc[lineno]) * width_fac #the max offset of Voigt components (=natural+thermal broad.) - linenu_low = (1 + np.min(vx)/tools.c) * spNIST.nu0.loc[lineno] - max_voigt_width - linenu_hi = (1 + np.max(vx)/tools.c) * spNIST.nu0.loc[lineno] + max_voigt_width - - nus_line = nus[(nus > linenu_low) & (nus < linenu_hi)] #the frequency values that make sense to calculate for this line - if nus_line.size == 0: #then this line is not in our wav range and we skip it - continue #to next spectral line - #get all columns in .den file which energy corresponds to this Ei - colname, lineweight = tools.find_line_lowerstate_in_en_df(spec, spNIST.loc[lineno], sim.en) - if colname == None: #we skip this line if the line energy is not found. - notfound_lines.append(spNIST['ritz_wl_vac(A)'][lineno]) - continue #to next spectral line - - found_lines.append((spNIST['ritz_wl_vac(A)'].loc[lineno], colname)) #if we got to here, we did find the spectral line + if len(species) == 1 and len(spNIST) == 0: + warnings.warn( + f"Your requested species {spec} does not have any lines in this wavelength range (according to the NIST database), " + "so the spectrum will be flat." + ) + + for lineno in spNIST.index.values: # loop over all lines in the spNIST table. + gaus_sigma_max = ( + np.sqrt( + tools.k * np.nanmax(Te) / tools.get_mass(spec) + 0.5 * v_turb**2 + ) + * spNIST.nu0.loc[lineno] + / tools.c + ) # maximum stddev of Gaussian part + max_voigt_width = ( + 5 * (gaus_sigma_max + spNIST["lorgamma"].loc[lineno]) * width_fac + ) # the max offset of Voigt components (=natural+thermal broad.) + linenu_low = (1 + np.min(vx) / tools.c) * spNIST.nu0.loc[ + lineno + ] - max_voigt_width + linenu_hi = (1 + np.max(vx) / tools.c) * spNIST.nu0.loc[ + lineno + ] + max_voigt_width + + nus_line = nus[ + (nus > linenu_low) & (nus < linenu_hi) + ] # the frequency values that make sense to calculate for this line + if ( + nus_line.size == 0 + ): # then this line is not in our wav range and we skip it + continue # to next spectral line + + # get all columns in .den file which energy corresponds to this Ei + colname, lineweight = tools.find_line_lowerstate_in_en_df( + spec, spNIST.loc[lineno], sim.en + ) + if colname is None: # we skip this line if the line energy is not found. + notfound_lines.append(spNIST["ritz_wl_vac(A)"][lineno]) + continue # to next spectral line + + found_lines.append( + (spNIST["ritz_wl_vac(A)"].loc[lineno], colname) + ) # if we got to here, we did find the spectral line if colname in state_ndens.keys(): ndens = state_ndens[colname] else: ndens1 = sim.den[colname].values[::-1] - be, _, x, ndens = project_1D_to_2D(r1, ndens1, Rp, numb=numrays, cut_at=cut_at) - state_ndens[colname] = ndens #add to dictionary for future reference - - ndens_lw = ndens*lineweight #important that we make this a new variable as otherwise state_ndens would change as well! - - tau_line = calc_tau(x, ndens_lw, Te, vx, nus_line, spNIST.nu0.loc[lineno], tools.get_mass(spec), spNIST.sig0.loc[lineno], spNIST['lorgamma'].loc[lineno], v_turb=v_turb) - tau[(nus > linenu_low) & (nus < linenu_hi), :] += tau_line #add the tau values to the correct nu bins + be, _, x, ndens = project_1D_to_2D( + r1, ndens1, Rp, numb=numrays, cut_at=cut_at + ) + state_ndens[colname] = ndens # add to dictionary for future reference + + ndens_lw = ( + ndens * lineweight + ) # important that we make this a new variable as otherwise state_ndens would change as well! + + tau_line = calc_tau( + x, + ndens_lw, + Te, + vx, + nus_line, + spNIST.nu0.loc[lineno], + tools.get_mass(spec), + spNIST.sig0.loc[lineno], + spNIST["lorgamma"].loc[lineno], + v_turb=v_turb, + ) + tau[ + (nus > linenu_low) & (nus < linenu_hi), : + ] += tau_line # add the tau values to the correct nu bins FinFout = tau_to_FinFout(be, tau, Rs, bp=sim.p.bp, ab=ab, phase=phase, a=sim.p.a) return FinFout, found_lines, notfound_lines -def tau_1D(sim, wavAA, species, width_fac=1., v_turb=0.): +def tau_1D(sim, wavAA, species, width_fac=1.0, v_turb=0.0): """ Maps out the optical depth at one specific wavelength. The running integral of the optical deph is calculated at each depth of the ray. @@ -564,7 +721,7 @@ def tau_1D(sim, wavAA, species, width_fac=1., v_turb=0.): Parameters ---------- sim : tools.Sim - Cloudy simulation output of an upper atmosphere. Needs to have tools.Planet and + Cloudy simulation output of an upper atmosphere. Needs to have tools.Planet and tools.Parker class attributes. wavAA : numeric Wavelength to calculate the optical depths at, in units of Å. @@ -597,56 +754,93 @@ def tau_1D(sim, wavAA, species, width_fac=1., v_turb=0.): but which could not be calculated due to their excitation state not being reported by Cloudy. """ - assert isinstance(wavAA, float) or isinstance(wavAA, int), "Pass one wavelength in Å as a float or int" - assert hasattr(sim, 'p'), "The sim must have an attributed Planet object" - assert 'v' in sim.ovr.columns, "We need a velocity structure, such as that from adding a Parker object to the sim." + assert isinstance(wavAA, (float, int)), "Pass one wavelength in Å as a float or int" + assert hasattr(sim, "p"), "The sim must have an attributed Planet object" + assert ( + "v" in sim.ovr.columns + ), "We need a velocity structure, such as that from adding a Parker object to the sim." Rs, Rp = sim.p.Rstar, sim.p.R - nu = tools.c*1e8 / wavAA #Hz, converted c to AA/s + nu = tools.c * 1e8 / wavAA # Hz, converted c to AA/s d = sim.ovr.depth.values Te = sim.ovr.Te.values - v = sim.ovr.v.values #radial velocity - vx = -v #because we do the substellar ray which is towards the -x direction + v = sim.ovr.v.values # radial velocity + vx = -v # because we do the substellar ray which is towards the -x direction tot_cum_tau, tot_bin_tau = np.zeros_like(d), np.zeros_like(d) if isinstance(species, str): species = [species] - found_lines = [] #will store nu0 of all lines that were used (might be nice to make it a dict per species in future!) - notfound_lines = [] #will store nu0 of all lines that were not found + found_lines = ( + [] + ) # will store nu0 of all lines that were used (might be nice to make it a dict per species in future!) + notfound_lines = [] # will store nu0 of all lines that were not found for spec in species: spNIST = read_NIST_lines(spec) - for lineno in spNIST.index.values: #loop over all lines in the spNIST table. - gaus_sigma_max = np.sqrt(tools.k * np.nanmax(Te) / tools.get_mass(spec) + 0.5*v_turb**2) * spNIST.nu0.loc[lineno] / tools.c #maximum stddev of Gaussian part - max_voigt_width = 5*(gaus_sigma_max+spNIST['lorgamma'].loc[lineno]) * width_fac #the max offset of Voigt components (=natural+thermal broad.) - linenu_low = (1 + np.min(vx)/tools.c) * spNIST.nu0.loc[lineno] - max_voigt_width - linenu_hi = (1 + np.max(vx)/tools.c) * spNIST.nu0.loc[lineno] + max_voigt_width - - if (nu < linenu_low) | (nu > linenu_hi): #then this line does not probe our requested wav and we skip it - continue #to next spectral line - - #get all columns in .den file which energy corresponds to this Ei - colname, lineweight = tools.find_line_lowerstate_in_en_df(spec, spNIST.loc[lineno], sim.en) - if colname == None: #we skip this line if the line energy is not found. - notfound_lines.append(spNIST['ritz_wl_vac(A)'][lineno]) - continue #to next spectral line - - found_lines.append((spNIST['ritz_wl_vac(A)'].loc[lineno], colname)) #if we got to here, we did find the spectral line - - ndens = sim.den[colname].values * lineweight #see explanation in FinFout_2D function - - cum_tau, bin_tau = calc_cum_tau(d, ndens, Te, vx, nu, spNIST.nu0.loc[lineno], tools.get_mass(spec), spNIST.sig0.loc[lineno], spNIST['lorgamma'].loc[lineno], v_turb=v_turb) - tot_cum_tau += cum_tau[0] #add the tau values to the total (of all species & lines together) + for lineno in spNIST.index.values: # loop over all lines in the spNIST table. + gaus_sigma_max = ( + np.sqrt( + tools.k * np.nanmax(Te) / tools.get_mass(spec) + 0.5 * v_turb**2 + ) + * spNIST.nu0.loc[lineno] + / tools.c + ) # maximum stddev of Gaussian part + max_voigt_width = ( + 5 * (gaus_sigma_max + spNIST["lorgamma"].loc[lineno]) * width_fac + ) # the max offset of Voigt components (=natural+thermal broad.) + linenu_low = (1 + np.min(vx) / tools.c) * spNIST.nu0.loc[ + lineno + ] - max_voigt_width + linenu_hi = (1 + np.max(vx) / tools.c) * spNIST.nu0.loc[ + lineno + ] + max_voigt_width + + if (nu < linenu_low) | ( + nu > linenu_hi + ): # then this line does not probe our requested wav and we skip it + continue # to next spectral line + + # get all columns in .den file which energy corresponds to this Ei + colname, lineweight = tools.find_line_lowerstate_in_en_df( + spec, spNIST.loc[lineno], sim.en + ) + if colname is None: # we skip this line if the line energy is not found. + notfound_lines.append(spNIST["ritz_wl_vac(A)"][lineno]) + continue # to next spectral line + + found_lines.append( + (spNIST["ritz_wl_vac(A)"].loc[lineno], colname) + ) # if we got to here, we did find the spectral line + + ndens = ( + sim.den[colname].values * lineweight + ) # see explanation in FinFout_2D function + + cum_tau, bin_tau = calc_cum_tau( + d, + ndens, + Te, + vx, + nu, + spNIST.nu0.loc[lineno], + tools.get_mass(spec), + spNIST.sig0.loc[lineno], + spNIST["lorgamma"].loc[lineno], + v_turb=v_turb, + ) + tot_cum_tau += cum_tau[ + 0 + ] # add the tau values to the total (of all species & lines together) tot_bin_tau += bin_tau[0] return tot_cum_tau, tot_bin_tau, found_lines, notfound_lines -def tau_12D(sim, wavAA, species, width_fac=1., v_turb=0., cut_at=None): +def tau_12D(sim, wavAA, species, width_fac=1.0, v_turb=0.0, cut_at=None): """ Maps out the optical depth at one specific wavelength. The running integral of the optical deph is calculated at each stellar light ray @@ -656,7 +850,7 @@ def tau_12D(sim, wavAA, species, width_fac=1., v_turb=0., cut_at=None): Parameters ---------- sim : tools.Sim - Cloudy simulation output of an upper atmosphere. Needs to have tools.Planet and + Cloudy simulation output of an upper atmosphere. Needs to have tools.Planet and tools.Parker class attributes. wavAA : numeric Wavelength to calculate the optical depths at, in units of Å. @@ -693,49 +887,91 @@ def tau_12D(sim, wavAA, species, width_fac=1., v_turb=0., cut_at=None): but which could not be calculated due to their excitation state not being reported by Cloudy. """ - assert isinstance(wavAA, float) or isinstance(wavAA, int), "Pass one wavelength in Å as a float or int" - assert hasattr(sim, 'p') - assert 'v' in sim.ovr.columns, "We need a velocity structure, such as that from adding a Parker object to the sim." + assert isinstance(wavAA, (float, int)), "Pass one wavelength in Å as a float or int" + assert hasattr(sim, "p") + assert ( + "v" in sim.ovr.columns + ), "We need a velocity structure, such as that from adding a Parker object to the sim." - nu = tools.c*1e8 / wavAA #Hz, converted c to AA/s + nu = tools.c * 1e8 / wavAA # Hz, converted c to AA/s - be, bc, x, Te = project_1D_to_2D(sim.ovr.alt.values[::-1], sim.ovr.Te.values[::-1], sim.p.R) - be, bc, x, vx = project_1D_to_2D(sim.ovr.alt.values[::-1], sim.ovr.v.values[::-1], sim.p.R, x_projection=True) + be, bc, x, Te = project_1D_to_2D( + sim.ovr.alt.values[::-1], sim.ovr.Te.values[::-1], sim.p.R + ) + be, bc, x, vx = project_1D_to_2D( + sim.ovr.alt.values[::-1], sim.ovr.v.values[::-1], sim.p.R, x_projection=True + ) tot_cum_tau, tot_bin_tau = np.zeros_like(vx), np.zeros_like(vx) if isinstance(species, str): species = [species] - found_lines = [] #will store nu0 of all lines that were used (might be nice to make it a dict per species in future!) - notfound_lines = [] #will store nu0 of all lines that were not found + found_lines = ( + [] + ) # will store nu0 of all lines that were used (might be nice to make it a dict per species in future!) + notfound_lines = [] # will store nu0 of all lines that were not found for spec in species: spNIST = read_NIST_lines(spec) - for lineno in spNIST.index.values: #loop over all lines in the spNIST table. - gaus_sigma_max = np.sqrt(tools.k * np.nanmax(Te) / tools.get_mass(spec) + 0.5*v_turb**2) * spNIST.nu0.loc[lineno] / tools.c #maximum stddev of Gaussian part - max_voigt_width = 5*(gaus_sigma_max+spNIST['lorgamma'].loc[lineno]) * width_fac #the max offset of Voigt components (=natural+thermal broad.) - linenu_low = (1 + np.min(vx)/tools.c) * spNIST.nu0.loc[lineno] - max_voigt_width - linenu_hi = (1 + np.max(vx)/tools.c) * spNIST.nu0.loc[lineno] + max_voigt_width - - if (nu < linenu_low) | (nu > linenu_hi): #then this line does not probe our requested wav and we skip it - continue #to next spectral line - - #get all columns in .den file which energy corresponds to this Ei - colname, lineweight = tools.find_line_lowerstate_in_en_df(spec, spNIST.loc[lineno], sim.en) - if colname == None: #we skip this line if the line energy is not found. - notfound_lines.append(spNIST['ritz_wl_vac(A)'][lineno]) - continue #to next spectral line - - found_lines.append((spNIST['ritz_wl_vac(A)'].loc[lineno], colname)) #if we got to here, we did find the spectral line - - #multiply with the lineweight! Such that for unresolved J, a line originating from J=1/2 does not also get density of J=3/2 state - _, _, _, ndens = project_1D_to_2D(sim.ovr.alt.values[::-1], sim.den[colname].values[::-1], sim.p.R, cut_at=cut_at) + for lineno in spNIST.index.values: # loop over all lines in the spNIST table. + gaus_sigma_max = ( + np.sqrt( + tools.k * np.nanmax(Te) / tools.get_mass(spec) + 0.5 * v_turb**2 + ) + * spNIST.nu0.loc[lineno] + / tools.c + ) # maximum stddev of Gaussian part + max_voigt_width = ( + 5 * (gaus_sigma_max + spNIST["lorgamma"].loc[lineno]) * width_fac + ) # the max offset of Voigt components (=natural+thermal broad.) + linenu_low = (1 + np.min(vx) / tools.c) * spNIST.nu0.loc[ + lineno + ] - max_voigt_width + linenu_hi = (1 + np.max(vx) / tools.c) * spNIST.nu0.loc[ + lineno + ] + max_voigt_width + + if (nu < linenu_low) | ( + nu > linenu_hi + ): # then this line does not probe our requested wav and we skip it + continue # to next spectral line + + # get all columns in .den file which energy corresponds to this Ei + colname, lineweight = tools.find_line_lowerstate_in_en_df( + spec, spNIST.loc[lineno], sim.en + ) + if colname is None: # we skip this line if the line energy is not found. + notfound_lines.append(spNIST["ritz_wl_vac(A)"][lineno]) + continue # to next spectral line + + found_lines.append( + (spNIST["ritz_wl_vac(A)"].loc[lineno], colname) + ) # if we got to here, we did find the spectral line + + # multiply with the lineweight! Such that for unresolved J, a line originating from J=1/2 does not also get density of J=3/2 state + _, _, _, ndens = project_1D_to_2D( + sim.ovr.alt.values[::-1], + sim.den[colname].values[::-1], + sim.p.R, + cut_at=cut_at, + ) ndens *= lineweight - cum_tau, bin_tau = calc_cum_tau(x, ndens, Te, vx, nu, spNIST.nu0.loc[lineno], tools.get_mass(spec), spNIST.sig0.loc[lineno], spNIST['lorgamma'].loc[lineno], v_turb=v_turb) - tot_cum_tau += cum_tau #add the tau values to the total (of all species & lines together) + cum_tau, bin_tau = calc_cum_tau( + x, + ndens, + Te, + vx, + nu, + spNIST.nu0.loc[lineno], + tools.get_mass(spec), + spNIST.sig0.loc[lineno], + spNIST["lorgamma"].loc[lineno], + v_turb=v_turb, + ) + tot_cum_tau += cum_tau # add the tau values to the total (of all species & lines together) tot_bin_tau += bin_tau return tot_cum_tau, tot_bin_tau, found_lines, notfound_lines @@ -759,7 +995,7 @@ def FinFout2RpRs(FinFout): Transit spectrum in units of planet size / star size. """ - RpRs = np.sqrt(1-FinFout) + RpRs = np.sqrt(1 - FinFout) return RpRs @@ -782,7 +1018,7 @@ def RpRs2FinFout(RpRs): In-transit / out-transit flux values """ - FinFout = 1-RpRs**2 + FinFout = 1 - RpRs**2 return FinFout @@ -829,7 +1065,12 @@ def air2vac(wavs_air): """ s = 1e4 / wavs_air - n = 1 + 0.00008336624212083 + 0.02408926869968 / (130.1065924522 - s**2) + 0.0001599740894897 / (38.92568793293 - s**2) + n = ( + 1 + + 0.00008336624212083 + + 0.02408926869968 / (130.1065924522 - s**2) + + 0.0001599740894897 / (38.92568793293 - s**2) + ) wavs_vac = wavs_air * n return wavs_vac @@ -858,9 +1099,9 @@ def constantR_wavs(wav_lower, wav_upper, R): wavs = [] while wav < wav_upper: wavs.append(wav) - wav += wav/R + wav += wav / R wavs = np.array(wavs) - + return wavs @@ -869,7 +1110,7 @@ def convolve_spectrum_R(wavs, flux, R, verbose=False): Convolves a spectrum with a Gaussian filter down to a lower spectral resolution. This function uses a constant gaussian width that is calculated from the middle wavelength point. This means that it only works properly when the wavs array spans a relatively small bandwidth. - Since R = delta-lambda / lambda, if the bandwidth is too large, the assumption made here that + Since R = delta-lambda / lambda, if the bandwidth is too large, the assumption made here that delta-lambda is the same over the whole array will not be valid. Parameters @@ -890,17 +1131,26 @@ def convolve_spectrum_R(wavs, flux, R, verbose=False): """ assert wavs[1] > wavs[0], "Wavelengths must be in ascending order" - assert np.allclose(np.diff(wavs), np.diff(wavs)[0], atol=0., rtol=1e-5), "Wavelengths must be equidistant" + assert np.allclose( + np.diff(wavs), np.diff(wavs)[0], atol=0.0, rtol=1e-5 + ), "Wavelengths must be equidistant" if wavs[-1] / wavs[0] > 1.05: - warnings.warn("The wavelengths change by more than 5 percent in your array. Converting R into a constant delta-lambda becomes questionable.") + warnings.warn( + "The wavelengths change by more than 5 percent in your array. Converting R into a constant delta-lambda becomes questionable." + ) - delta_lambda = wavs[int(len(wavs)/2)] / R #width of the filter in wavelength - use middle wav point - FWHM = delta_lambda / np.diff(wavs)[0] #width of the filter in pixels - sigma = FWHM / (2*np.sqrt(2*np.log(2))) #std dev. of the gaussian in pixels + delta_lambda = ( + wavs[int(len(wavs) / 2)] / R + ) # width of the filter in wavelength - use middle wav point + FWHM = delta_lambda / np.diff(wavs)[0] # width of the filter in pixels + sigma = FWHM / (2 * np.sqrt(2 * np.log(2))) # std dev. of the gaussian in pixels if verbose: - print(f"R={R}, lamb={wavs[0]}, delta-lamb={delta_lambda}, FWHM={FWHM} pix, sigma={sigma} pix") + print( + f"R={R}, lamb={wavs[0]}, delta-lamb={delta_lambda}, FWHM={FWHM} pix, sigma={sigma} pix" + ) convolved_spectrum = gaussian_filter1d(flux, sigma) - return convolved_spectrum \ No newline at end of file + return convolved_spectrum + # add the tau values to the total (of all species & lines together) diff --git a/src/RT_tables/Al+10_levels_NIST.txt b/src/sunbather/RT_tables/Al+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+10_levels_NIST.txt rename to src/sunbather/RT_tables/Al+10_levels_NIST.txt diff --git a/src/RT_tables/Al+10_levels_processed.txt b/src/sunbather/RT_tables/Al+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+10_levels_processed.txt rename to src/sunbather/RT_tables/Al+10_levels_processed.txt diff --git a/src/RT_tables/Al+10_lines_NIST.txt b/src/sunbather/RT_tables/Al+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+10_lines_NIST.txt rename to src/sunbather/RT_tables/Al+10_lines_NIST.txt diff --git a/src/RT_tables/Al+11_levels_NIST.txt b/src/sunbather/RT_tables/Al+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+11_levels_NIST.txt rename to src/sunbather/RT_tables/Al+11_levels_NIST.txt diff --git a/src/RT_tables/Al+11_levels_processed.txt b/src/sunbather/RT_tables/Al+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+11_levels_processed.txt rename to src/sunbather/RT_tables/Al+11_levels_processed.txt diff --git a/src/RT_tables/Al+11_lines_NIST.txt b/src/sunbather/RT_tables/Al+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+11_lines_NIST.txt rename to src/sunbather/RT_tables/Al+11_lines_NIST.txt diff --git a/src/RT_tables/Al+12_levels_NIST.txt b/src/sunbather/RT_tables/Al+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+12_levels_NIST.txt rename to src/sunbather/RT_tables/Al+12_levels_NIST.txt diff --git a/src/RT_tables/Al+12_levels_processed.txt b/src/sunbather/RT_tables/Al+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+12_levels_processed.txt rename to src/sunbather/RT_tables/Al+12_levels_processed.txt diff --git a/src/RT_tables/Al+12_lines_NIST.txt b/src/sunbather/RT_tables/Al+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+12_lines_NIST.txt rename to src/sunbather/RT_tables/Al+12_lines_NIST.txt diff --git a/src/RT_tables/Al+2_levels_NIST.txt b/src/sunbather/RT_tables/Al+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+2_levels_NIST.txt rename to src/sunbather/RT_tables/Al+2_levels_NIST.txt diff --git a/src/RT_tables/Al+2_levels_processed.txt b/src/sunbather/RT_tables/Al+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+2_levels_processed.txt rename to src/sunbather/RT_tables/Al+2_levels_processed.txt diff --git a/src/RT_tables/Al+2_lines_NIST.txt b/src/sunbather/RT_tables/Al+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+2_lines_NIST.txt rename to src/sunbather/RT_tables/Al+2_lines_NIST.txt diff --git a/src/RT_tables/Al+3_levels_NIST.txt b/src/sunbather/RT_tables/Al+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+3_levels_NIST.txt rename to src/sunbather/RT_tables/Al+3_levels_NIST.txt diff --git a/src/RT_tables/Al+3_levels_processed.txt b/src/sunbather/RT_tables/Al+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+3_levels_processed.txt rename to src/sunbather/RT_tables/Al+3_levels_processed.txt diff --git a/src/RT_tables/Al+3_lines_NIST.txt b/src/sunbather/RT_tables/Al+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+3_lines_NIST.txt rename to src/sunbather/RT_tables/Al+3_lines_NIST.txt diff --git a/src/RT_tables/Al+4_levels_NIST.txt b/src/sunbather/RT_tables/Al+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+4_levels_NIST.txt rename to src/sunbather/RT_tables/Al+4_levels_NIST.txt diff --git a/src/RT_tables/Al+4_levels_processed.txt b/src/sunbather/RT_tables/Al+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+4_levels_processed.txt rename to src/sunbather/RT_tables/Al+4_levels_processed.txt diff --git a/src/RT_tables/Al+4_lines_NIST.txt b/src/sunbather/RT_tables/Al+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+4_lines_NIST.txt rename to src/sunbather/RT_tables/Al+4_lines_NIST.txt diff --git a/src/RT_tables/Al+5_levels_NIST.txt b/src/sunbather/RT_tables/Al+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+5_levels_NIST.txt rename to src/sunbather/RT_tables/Al+5_levels_NIST.txt diff --git a/src/RT_tables/Al+5_levels_processed.txt b/src/sunbather/RT_tables/Al+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+5_levels_processed.txt rename to src/sunbather/RT_tables/Al+5_levels_processed.txt diff --git a/src/RT_tables/Al+5_lines_NIST.txt b/src/sunbather/RT_tables/Al+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+5_lines_NIST.txt rename to src/sunbather/RT_tables/Al+5_lines_NIST.txt diff --git a/src/RT_tables/Al+6_levels_NIST.txt b/src/sunbather/RT_tables/Al+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+6_levels_NIST.txt rename to src/sunbather/RT_tables/Al+6_levels_NIST.txt diff --git a/src/RT_tables/Al+6_levels_processed.txt b/src/sunbather/RT_tables/Al+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+6_levels_processed.txt rename to src/sunbather/RT_tables/Al+6_levels_processed.txt diff --git a/src/RT_tables/Al+6_lines_NIST.txt b/src/sunbather/RT_tables/Al+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+6_lines_NIST.txt rename to src/sunbather/RT_tables/Al+6_lines_NIST.txt diff --git a/src/RT_tables/Al+7_levels_NIST.txt b/src/sunbather/RT_tables/Al+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+7_levels_NIST.txt rename to src/sunbather/RT_tables/Al+7_levels_NIST.txt diff --git a/src/RT_tables/Al+7_levels_processed.txt b/src/sunbather/RT_tables/Al+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+7_levels_processed.txt rename to src/sunbather/RT_tables/Al+7_levels_processed.txt diff --git a/src/RT_tables/Al+7_lines_NIST.txt b/src/sunbather/RT_tables/Al+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+7_lines_NIST.txt rename to src/sunbather/RT_tables/Al+7_lines_NIST.txt diff --git a/src/RT_tables/Al+8_levels_NIST.txt b/src/sunbather/RT_tables/Al+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+8_levels_NIST.txt rename to src/sunbather/RT_tables/Al+8_levels_NIST.txt diff --git a/src/RT_tables/Al+8_levels_processed.txt b/src/sunbather/RT_tables/Al+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+8_levels_processed.txt rename to src/sunbather/RT_tables/Al+8_levels_processed.txt diff --git a/src/RT_tables/Al+8_lines_NIST.txt b/src/sunbather/RT_tables/Al+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+8_lines_NIST.txt rename to src/sunbather/RT_tables/Al+8_lines_NIST.txt diff --git a/src/RT_tables/Al+9_levels_NIST.txt b/src/sunbather/RT_tables/Al+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+9_levels_NIST.txt rename to src/sunbather/RT_tables/Al+9_levels_NIST.txt diff --git a/src/RT_tables/Al+9_levels_processed.txt b/src/sunbather/RT_tables/Al+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+9_levels_processed.txt rename to src/sunbather/RT_tables/Al+9_levels_processed.txt diff --git a/src/RT_tables/Al+9_lines_NIST.txt b/src/sunbather/RT_tables/Al+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+9_lines_NIST.txt rename to src/sunbather/RT_tables/Al+9_lines_NIST.txt diff --git a/src/RT_tables/Al+_levels_NIST.txt b/src/sunbather/RT_tables/Al+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al+_levels_NIST.txt rename to src/sunbather/RT_tables/Al+_levels_NIST.txt diff --git a/src/RT_tables/Al+_levels_processed.txt b/src/sunbather/RT_tables/Al+_levels_processed.txt similarity index 100% rename from src/RT_tables/Al+_levels_processed.txt rename to src/sunbather/RT_tables/Al+_levels_processed.txt diff --git a/src/RT_tables/Al+_lines_NIST.txt b/src/sunbather/RT_tables/Al+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al+_lines_NIST.txt rename to src/sunbather/RT_tables/Al+_lines_NIST.txt diff --git a/src/RT_tables/Al_levels_NIST.txt b/src/sunbather/RT_tables/Al_levels_NIST.txt similarity index 100% rename from src/RT_tables/Al_levels_NIST.txt rename to src/sunbather/RT_tables/Al_levels_NIST.txt diff --git a/src/RT_tables/Al_levels_processed.txt b/src/sunbather/RT_tables/Al_levels_processed.txt similarity index 100% rename from src/RT_tables/Al_levels_processed.txt rename to src/sunbather/RT_tables/Al_levels_processed.txt diff --git a/src/RT_tables/Al_lines_NIST.txt b/src/sunbather/RT_tables/Al_lines_NIST.txt similarity index 100% rename from src/RT_tables/Al_lines_NIST.txt rename to src/sunbather/RT_tables/Al_lines_NIST.txt diff --git a/src/RT_tables/Ar+10_levels_NIST.txt b/src/sunbather/RT_tables/Ar+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+10_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+10_levels_NIST.txt diff --git a/src/RT_tables/Ar+10_levels_processed.txt b/src/sunbather/RT_tables/Ar+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+10_levels_processed.txt rename to src/sunbather/RT_tables/Ar+10_levels_processed.txt diff --git a/src/RT_tables/Ar+10_lines_NIST.txt b/src/sunbather/RT_tables/Ar+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+10_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+10_lines_NIST.txt diff --git a/src/RT_tables/Ar+11_levels_NIST.txt b/src/sunbather/RT_tables/Ar+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+11_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+11_levels_NIST.txt diff --git a/src/RT_tables/Ar+11_levels_processed.txt b/src/sunbather/RT_tables/Ar+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+11_levels_processed.txt rename to src/sunbather/RT_tables/Ar+11_levels_processed.txt diff --git a/src/RT_tables/Ar+11_lines_NIST.txt b/src/sunbather/RT_tables/Ar+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+11_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+11_lines_NIST.txt diff --git a/src/RT_tables/Ar+12_levels_NIST.txt b/src/sunbather/RT_tables/Ar+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+12_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+12_levels_NIST.txt diff --git a/src/RT_tables/Ar+12_levels_processed.txt b/src/sunbather/RT_tables/Ar+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+12_levels_processed.txt rename to src/sunbather/RT_tables/Ar+12_levels_processed.txt diff --git a/src/RT_tables/Ar+12_lines_NIST.txt b/src/sunbather/RT_tables/Ar+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+12_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+12_lines_NIST.txt diff --git a/src/RT_tables/Ar+2_levels_NIST.txt b/src/sunbather/RT_tables/Ar+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+2_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+2_levels_NIST.txt diff --git a/src/RT_tables/Ar+2_levels_processed.txt b/src/sunbather/RT_tables/Ar+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+2_levels_processed.txt rename to src/sunbather/RT_tables/Ar+2_levels_processed.txt diff --git a/src/RT_tables/Ar+2_lines_NIST.txt b/src/sunbather/RT_tables/Ar+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+2_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+2_lines_NIST.txt diff --git a/src/RT_tables/Ar+3_levels_NIST.txt b/src/sunbather/RT_tables/Ar+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+3_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+3_levels_NIST.txt diff --git a/src/RT_tables/Ar+3_levels_processed.txt b/src/sunbather/RT_tables/Ar+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+3_levels_processed.txt rename to src/sunbather/RT_tables/Ar+3_levels_processed.txt diff --git a/src/RT_tables/Ar+3_lines_NIST.txt b/src/sunbather/RT_tables/Ar+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+3_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+3_lines_NIST.txt diff --git a/src/RT_tables/Ar+4_levels_NIST.txt b/src/sunbather/RT_tables/Ar+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+4_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+4_levels_NIST.txt diff --git a/src/RT_tables/Ar+4_levels_processed.txt b/src/sunbather/RT_tables/Ar+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+4_levels_processed.txt rename to src/sunbather/RT_tables/Ar+4_levels_processed.txt diff --git a/src/RT_tables/Ar+4_lines_NIST.txt b/src/sunbather/RT_tables/Ar+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+4_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+4_lines_NIST.txt diff --git a/src/RT_tables/Ar+5_levels_NIST.txt b/src/sunbather/RT_tables/Ar+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+5_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+5_levels_NIST.txt diff --git a/src/RT_tables/Ar+5_levels_processed.txt b/src/sunbather/RT_tables/Ar+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+5_levels_processed.txt rename to src/sunbather/RT_tables/Ar+5_levels_processed.txt diff --git a/src/RT_tables/Ar+5_lines_NIST.txt b/src/sunbather/RT_tables/Ar+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+5_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+5_lines_NIST.txt diff --git a/src/RT_tables/Ar+6_levels_NIST.txt b/src/sunbather/RT_tables/Ar+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+6_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+6_levels_NIST.txt diff --git a/src/RT_tables/Ar+6_levels_processed.txt b/src/sunbather/RT_tables/Ar+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+6_levels_processed.txt rename to src/sunbather/RT_tables/Ar+6_levels_processed.txt diff --git a/src/RT_tables/Ar+6_lines_NIST.txt b/src/sunbather/RT_tables/Ar+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+6_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+6_lines_NIST.txt diff --git a/src/RT_tables/Ar+7_levels_NIST.txt b/src/sunbather/RT_tables/Ar+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+7_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+7_levels_NIST.txt diff --git a/src/RT_tables/Ar+7_levels_processed.txt b/src/sunbather/RT_tables/Ar+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+7_levels_processed.txt rename to src/sunbather/RT_tables/Ar+7_levels_processed.txt diff --git a/src/RT_tables/Ar+7_lines_NIST.txt b/src/sunbather/RT_tables/Ar+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+7_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+7_lines_NIST.txt diff --git a/src/RT_tables/Ar+8_levels_NIST.txt b/src/sunbather/RT_tables/Ar+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+8_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+8_levels_NIST.txt diff --git a/src/RT_tables/Ar+8_levels_processed.txt b/src/sunbather/RT_tables/Ar+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+8_levels_processed.txt rename to src/sunbather/RT_tables/Ar+8_levels_processed.txt diff --git a/src/RT_tables/Ar+8_lines_NIST.txt b/src/sunbather/RT_tables/Ar+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+8_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+8_lines_NIST.txt diff --git a/src/RT_tables/Ar+9_levels_NIST.txt b/src/sunbather/RT_tables/Ar+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+9_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+9_levels_NIST.txt diff --git a/src/RT_tables/Ar+9_levels_processed.txt b/src/sunbather/RT_tables/Ar+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+9_levels_processed.txt rename to src/sunbather/RT_tables/Ar+9_levels_processed.txt diff --git a/src/RT_tables/Ar+9_lines_NIST.txt b/src/sunbather/RT_tables/Ar+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+9_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+9_lines_NIST.txt diff --git a/src/RT_tables/Ar+_levels_NIST.txt b/src/sunbather/RT_tables/Ar+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar+_levels_NIST.txt rename to src/sunbather/RT_tables/Ar+_levels_NIST.txt diff --git a/src/RT_tables/Ar+_levels_processed.txt b/src/sunbather/RT_tables/Ar+_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar+_levels_processed.txt rename to src/sunbather/RT_tables/Ar+_levels_processed.txt diff --git a/src/RT_tables/Ar+_lines_NIST.txt b/src/sunbather/RT_tables/Ar+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar+_lines_NIST.txt rename to src/sunbather/RT_tables/Ar+_lines_NIST.txt diff --git a/src/RT_tables/Ar_levels_NIST.txt b/src/sunbather/RT_tables/Ar_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ar_levels_NIST.txt rename to src/sunbather/RT_tables/Ar_levels_NIST.txt diff --git a/src/RT_tables/Ar_levels_processed.txt b/src/sunbather/RT_tables/Ar_levels_processed.txt similarity index 100% rename from src/RT_tables/Ar_levels_processed.txt rename to src/sunbather/RT_tables/Ar_levels_processed.txt diff --git a/src/RT_tables/Ar_lines_NIST.txt b/src/sunbather/RT_tables/Ar_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ar_lines_NIST.txt rename to src/sunbather/RT_tables/Ar_lines_NIST.txt diff --git a/src/RT_tables/B+2_levels_NIST.txt b/src/sunbather/RT_tables/B+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/B+2_levels_NIST.txt rename to src/sunbather/RT_tables/B+2_levels_NIST.txt diff --git a/src/RT_tables/B+2_levels_processed.txt b/src/sunbather/RT_tables/B+2_levels_processed.txt similarity index 100% rename from src/RT_tables/B+2_levels_processed.txt rename to src/sunbather/RT_tables/B+2_levels_processed.txt diff --git a/src/RT_tables/B+2_lines_NIST.txt b/src/sunbather/RT_tables/B+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/B+2_lines_NIST.txt rename to src/sunbather/RT_tables/B+2_lines_NIST.txt diff --git a/src/RT_tables/B+3_levels_NIST.txt b/src/sunbather/RT_tables/B+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/B+3_levels_NIST.txt rename to src/sunbather/RT_tables/B+3_levels_NIST.txt diff --git a/src/RT_tables/B+3_levels_processed.txt b/src/sunbather/RT_tables/B+3_levels_processed.txt similarity index 100% rename from src/RT_tables/B+3_levels_processed.txt rename to src/sunbather/RT_tables/B+3_levels_processed.txt diff --git a/src/RT_tables/B+3_lines_NIST.txt b/src/sunbather/RT_tables/B+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/B+3_lines_NIST.txt rename to src/sunbather/RT_tables/B+3_lines_NIST.txt diff --git a/src/RT_tables/B+4_levels_NIST.txt b/src/sunbather/RT_tables/B+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/B+4_levels_NIST.txt rename to src/sunbather/RT_tables/B+4_levels_NIST.txt diff --git a/src/RT_tables/B+4_levels_processed.txt b/src/sunbather/RT_tables/B+4_levels_processed.txt similarity index 100% rename from src/RT_tables/B+4_levels_processed.txt rename to src/sunbather/RT_tables/B+4_levels_processed.txt diff --git a/src/RT_tables/B+4_lines_NIST.txt b/src/sunbather/RT_tables/B+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/B+4_lines_NIST.txt rename to src/sunbather/RT_tables/B+4_lines_NIST.txt diff --git a/src/RT_tables/B+_levels_NIST.txt b/src/sunbather/RT_tables/B+_levels_NIST.txt similarity index 100% rename from src/RT_tables/B+_levels_NIST.txt rename to src/sunbather/RT_tables/B+_levels_NIST.txt diff --git a/src/RT_tables/B+_levels_processed.txt b/src/sunbather/RT_tables/B+_levels_processed.txt similarity index 100% rename from src/RT_tables/B+_levels_processed.txt rename to src/sunbather/RT_tables/B+_levels_processed.txt diff --git a/src/RT_tables/B+_lines_NIST.txt b/src/sunbather/RT_tables/B+_lines_NIST.txt similarity index 100% rename from src/RT_tables/B+_lines_NIST.txt rename to src/sunbather/RT_tables/B+_lines_NIST.txt diff --git a/src/RT_tables/B_levels_NIST.txt b/src/sunbather/RT_tables/B_levels_NIST.txt similarity index 100% rename from src/RT_tables/B_levels_NIST.txt rename to src/sunbather/RT_tables/B_levels_NIST.txt diff --git a/src/RT_tables/B_levels_processed.txt b/src/sunbather/RT_tables/B_levels_processed.txt similarity index 100% rename from src/RT_tables/B_levels_processed.txt rename to src/sunbather/RT_tables/B_levels_processed.txt diff --git a/src/RT_tables/B_lines_NIST.txt b/src/sunbather/RT_tables/B_lines_NIST.txt similarity index 100% rename from src/RT_tables/B_lines_NIST.txt rename to src/sunbather/RT_tables/B_lines_NIST.txt diff --git a/src/RT_tables/Be+2_levels_NIST.txt b/src/sunbather/RT_tables/Be+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Be+2_levels_NIST.txt rename to src/sunbather/RT_tables/Be+2_levels_NIST.txt diff --git a/src/RT_tables/Be+2_levels_processed.txt b/src/sunbather/RT_tables/Be+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Be+2_levels_processed.txt rename to src/sunbather/RT_tables/Be+2_levels_processed.txt diff --git a/src/RT_tables/Be+2_lines_NIST.txt b/src/sunbather/RT_tables/Be+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Be+2_lines_NIST.txt rename to src/sunbather/RT_tables/Be+2_lines_NIST.txt diff --git a/src/RT_tables/Be+3_levels_NIST.txt b/src/sunbather/RT_tables/Be+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Be+3_levels_NIST.txt rename to src/sunbather/RT_tables/Be+3_levels_NIST.txt diff --git a/src/RT_tables/Be+3_levels_processed.txt b/src/sunbather/RT_tables/Be+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Be+3_levels_processed.txt rename to src/sunbather/RT_tables/Be+3_levels_processed.txt diff --git a/src/RT_tables/Be+3_lines_NIST.txt b/src/sunbather/RT_tables/Be+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Be+3_lines_NIST.txt rename to src/sunbather/RT_tables/Be+3_lines_NIST.txt diff --git a/src/RT_tables/Be+_levels_NIST.txt b/src/sunbather/RT_tables/Be+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Be+_levels_NIST.txt rename to src/sunbather/RT_tables/Be+_levels_NIST.txt diff --git a/src/RT_tables/Be+_levels_processed.txt b/src/sunbather/RT_tables/Be+_levels_processed.txt similarity index 100% rename from src/RT_tables/Be+_levels_processed.txt rename to src/sunbather/RT_tables/Be+_levels_processed.txt diff --git a/src/RT_tables/Be+_lines_NIST.txt b/src/sunbather/RT_tables/Be+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Be+_lines_NIST.txt rename to src/sunbather/RT_tables/Be+_lines_NIST.txt diff --git a/src/RT_tables/Be_levels_NIST.txt b/src/sunbather/RT_tables/Be_levels_NIST.txt similarity index 100% rename from src/RT_tables/Be_levels_NIST.txt rename to src/sunbather/RT_tables/Be_levels_NIST.txt diff --git a/src/RT_tables/Be_levels_processed.txt b/src/sunbather/RT_tables/Be_levels_processed.txt similarity index 100% rename from src/RT_tables/Be_levels_processed.txt rename to src/sunbather/RT_tables/Be_levels_processed.txt diff --git a/src/RT_tables/Be_lines_NIST.txt b/src/sunbather/RT_tables/Be_lines_NIST.txt similarity index 100% rename from src/RT_tables/Be_lines_NIST.txt rename to src/sunbather/RT_tables/Be_lines_NIST.txt diff --git a/src/RT_tables/C+2_levels_NIST.txt b/src/sunbather/RT_tables/C+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/C+2_levels_NIST.txt rename to src/sunbather/RT_tables/C+2_levels_NIST.txt diff --git a/src/RT_tables/C+2_levels_processed.txt b/src/sunbather/RT_tables/C+2_levels_processed.txt similarity index 100% rename from src/RT_tables/C+2_levels_processed.txt rename to src/sunbather/RT_tables/C+2_levels_processed.txt diff --git a/src/RT_tables/C+2_lines_NIST.txt b/src/sunbather/RT_tables/C+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/C+2_lines_NIST.txt rename to src/sunbather/RT_tables/C+2_lines_NIST.txt diff --git a/src/RT_tables/C+3_levels_NIST.txt b/src/sunbather/RT_tables/C+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/C+3_levels_NIST.txt rename to src/sunbather/RT_tables/C+3_levels_NIST.txt diff --git a/src/RT_tables/C+3_levels_processed.txt b/src/sunbather/RT_tables/C+3_levels_processed.txt similarity index 100% rename from src/RT_tables/C+3_levels_processed.txt rename to src/sunbather/RT_tables/C+3_levels_processed.txt diff --git a/src/RT_tables/C+3_lines_NIST.txt b/src/sunbather/RT_tables/C+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/C+3_lines_NIST.txt rename to src/sunbather/RT_tables/C+3_lines_NIST.txt diff --git a/src/RT_tables/C+4_levels_NIST.txt b/src/sunbather/RT_tables/C+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/C+4_levels_NIST.txt rename to src/sunbather/RT_tables/C+4_levels_NIST.txt diff --git a/src/RT_tables/C+4_levels_processed.txt b/src/sunbather/RT_tables/C+4_levels_processed.txt similarity index 100% rename from src/RT_tables/C+4_levels_processed.txt rename to src/sunbather/RT_tables/C+4_levels_processed.txt diff --git a/src/RT_tables/C+4_lines_NIST.txt b/src/sunbather/RT_tables/C+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/C+4_lines_NIST.txt rename to src/sunbather/RT_tables/C+4_lines_NIST.txt diff --git a/src/RT_tables/C+5_levels_NIST.txt b/src/sunbather/RT_tables/C+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/C+5_levels_NIST.txt rename to src/sunbather/RT_tables/C+5_levels_NIST.txt diff --git a/src/RT_tables/C+5_levels_processed.txt b/src/sunbather/RT_tables/C+5_levels_processed.txt similarity index 100% rename from src/RT_tables/C+5_levels_processed.txt rename to src/sunbather/RT_tables/C+5_levels_processed.txt diff --git a/src/RT_tables/C+5_lines_NIST.txt b/src/sunbather/RT_tables/C+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/C+5_lines_NIST.txt rename to src/sunbather/RT_tables/C+5_lines_NIST.txt diff --git a/src/RT_tables/C+_levels_NIST.txt b/src/sunbather/RT_tables/C+_levels_NIST.txt similarity index 100% rename from src/RT_tables/C+_levels_NIST.txt rename to src/sunbather/RT_tables/C+_levels_NIST.txt diff --git a/src/RT_tables/C+_levels_processed.txt b/src/sunbather/RT_tables/C+_levels_processed.txt similarity index 100% rename from src/RT_tables/C+_levels_processed.txt rename to src/sunbather/RT_tables/C+_levels_processed.txt diff --git a/src/RT_tables/C+_lines_NIST.txt b/src/sunbather/RT_tables/C+_lines_NIST.txt similarity index 100% rename from src/RT_tables/C+_lines_NIST.txt rename to src/sunbather/RT_tables/C+_lines_NIST.txt diff --git a/src/RT_tables/C_levels_NIST.txt b/src/sunbather/RT_tables/C_levels_NIST.txt similarity index 100% rename from src/RT_tables/C_levels_NIST.txt rename to src/sunbather/RT_tables/C_levels_NIST.txt diff --git a/src/RT_tables/C_levels_processed.txt b/src/sunbather/RT_tables/C_levels_processed.txt similarity index 100% rename from src/RT_tables/C_levels_processed.txt rename to src/sunbather/RT_tables/C_levels_processed.txt diff --git a/src/RT_tables/C_lines_NIST.txt b/src/sunbather/RT_tables/C_lines_NIST.txt similarity index 100% rename from src/RT_tables/C_lines_NIST.txt rename to src/sunbather/RT_tables/C_lines_NIST.txt diff --git a/src/RT_tables/Ca+10_levels_NIST.txt b/src/sunbather/RT_tables/Ca+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+10_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+10_levels_NIST.txt diff --git a/src/RT_tables/Ca+10_levels_processed.txt b/src/sunbather/RT_tables/Ca+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+10_levels_processed.txt rename to src/sunbather/RT_tables/Ca+10_levels_processed.txt diff --git a/src/RT_tables/Ca+10_lines_NIST.txt b/src/sunbather/RT_tables/Ca+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+10_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+10_lines_NIST.txt diff --git a/src/RT_tables/Ca+11_levels_NIST.txt b/src/sunbather/RT_tables/Ca+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+11_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+11_levels_NIST.txt diff --git a/src/RT_tables/Ca+11_levels_processed.txt b/src/sunbather/RT_tables/Ca+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+11_levels_processed.txt rename to src/sunbather/RT_tables/Ca+11_levels_processed.txt diff --git a/src/RT_tables/Ca+11_lines_NIST.txt b/src/sunbather/RT_tables/Ca+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+11_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+11_lines_NIST.txt diff --git a/src/RT_tables/Ca+12_levels_NIST.txt b/src/sunbather/RT_tables/Ca+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+12_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+12_levels_NIST.txt diff --git a/src/RT_tables/Ca+12_levels_processed.txt b/src/sunbather/RT_tables/Ca+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+12_levels_processed.txt rename to src/sunbather/RT_tables/Ca+12_levels_processed.txt diff --git a/src/RT_tables/Ca+12_lines_NIST.txt b/src/sunbather/RT_tables/Ca+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+12_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+12_lines_NIST.txt diff --git a/src/RT_tables/Ca+2_levels_NIST.txt b/src/sunbather/RT_tables/Ca+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+2_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+2_levels_NIST.txt diff --git a/src/RT_tables/Ca+2_levels_processed.txt b/src/sunbather/RT_tables/Ca+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+2_levels_processed.txt rename to src/sunbather/RT_tables/Ca+2_levels_processed.txt diff --git a/src/RT_tables/Ca+2_lines_NIST.txt b/src/sunbather/RT_tables/Ca+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+2_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+2_lines_NIST.txt diff --git a/src/RT_tables/Ca+3_levels_NIST.txt b/src/sunbather/RT_tables/Ca+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+3_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+3_levels_NIST.txt diff --git a/src/RT_tables/Ca+3_levels_processed.txt b/src/sunbather/RT_tables/Ca+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+3_levels_processed.txt rename to src/sunbather/RT_tables/Ca+3_levels_processed.txt diff --git a/src/RT_tables/Ca+3_lines_NIST.txt b/src/sunbather/RT_tables/Ca+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+3_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+3_lines_NIST.txt diff --git a/src/RT_tables/Ca+4_levels_NIST.txt b/src/sunbather/RT_tables/Ca+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+4_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+4_levels_NIST.txt diff --git a/src/RT_tables/Ca+4_levels_processed.txt b/src/sunbather/RT_tables/Ca+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+4_levels_processed.txt rename to src/sunbather/RT_tables/Ca+4_levels_processed.txt diff --git a/src/RT_tables/Ca+4_lines_NIST.txt b/src/sunbather/RT_tables/Ca+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+4_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+4_lines_NIST.txt diff --git a/src/RT_tables/Ca+5_levels_NIST.txt b/src/sunbather/RT_tables/Ca+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+5_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+5_levels_NIST.txt diff --git a/src/RT_tables/Ca+5_levels_processed.txt b/src/sunbather/RT_tables/Ca+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+5_levels_processed.txt rename to src/sunbather/RT_tables/Ca+5_levels_processed.txt diff --git a/src/RT_tables/Ca+5_lines_NIST.txt b/src/sunbather/RT_tables/Ca+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+5_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+5_lines_NIST.txt diff --git a/src/RT_tables/Ca+6_levels_NIST.txt b/src/sunbather/RT_tables/Ca+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+6_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+6_levels_NIST.txt diff --git a/src/RT_tables/Ca+6_levels_processed.txt b/src/sunbather/RT_tables/Ca+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+6_levels_processed.txt rename to src/sunbather/RT_tables/Ca+6_levels_processed.txt diff --git a/src/RT_tables/Ca+6_lines_NIST.txt b/src/sunbather/RT_tables/Ca+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+6_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+6_lines_NIST.txt diff --git a/src/RT_tables/Ca+7_levels_NIST.txt b/src/sunbather/RT_tables/Ca+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+7_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+7_levels_NIST.txt diff --git a/src/RT_tables/Ca+7_levels_processed.txt b/src/sunbather/RT_tables/Ca+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+7_levels_processed.txt rename to src/sunbather/RT_tables/Ca+7_levels_processed.txt diff --git a/src/RT_tables/Ca+7_lines_NIST.txt b/src/sunbather/RT_tables/Ca+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+7_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+7_lines_NIST.txt diff --git a/src/RT_tables/Ca+8_levels_NIST.txt b/src/sunbather/RT_tables/Ca+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+8_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+8_levels_NIST.txt diff --git a/src/RT_tables/Ca+8_levels_processed.txt b/src/sunbather/RT_tables/Ca+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+8_levels_processed.txt rename to src/sunbather/RT_tables/Ca+8_levels_processed.txt diff --git a/src/RT_tables/Ca+8_lines_NIST.txt b/src/sunbather/RT_tables/Ca+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+8_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+8_lines_NIST.txt diff --git a/src/RT_tables/Ca+9_levels_NIST.txt b/src/sunbather/RT_tables/Ca+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+9_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+9_levels_NIST.txt diff --git a/src/RT_tables/Ca+9_levels_processed.txt b/src/sunbather/RT_tables/Ca+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+9_levels_processed.txt rename to src/sunbather/RT_tables/Ca+9_levels_processed.txt diff --git a/src/RT_tables/Ca+9_lines_NIST.txt b/src/sunbather/RT_tables/Ca+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+9_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+9_lines_NIST.txt diff --git a/src/RT_tables/Ca+_levels_NIST.txt b/src/sunbather/RT_tables/Ca+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca+_levels_NIST.txt rename to src/sunbather/RT_tables/Ca+_levels_NIST.txt diff --git a/src/RT_tables/Ca+_levels_processed.txt b/src/sunbather/RT_tables/Ca+_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca+_levels_processed.txt rename to src/sunbather/RT_tables/Ca+_levels_processed.txt diff --git a/src/RT_tables/Ca+_lines_NIST.txt b/src/sunbather/RT_tables/Ca+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca+_lines_NIST.txt rename to src/sunbather/RT_tables/Ca+_lines_NIST.txt diff --git a/src/RT_tables/Ca_levels_NIST.txt b/src/sunbather/RT_tables/Ca_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ca_levels_NIST.txt rename to src/sunbather/RT_tables/Ca_levels_NIST.txt diff --git a/src/RT_tables/Ca_levels_processed.txt b/src/sunbather/RT_tables/Ca_levels_processed.txt similarity index 100% rename from src/RT_tables/Ca_levels_processed.txt rename to src/sunbather/RT_tables/Ca_levels_processed.txt diff --git a/src/RT_tables/Ca_lines_NIST.txt b/src/sunbather/RT_tables/Ca_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ca_lines_NIST.txt rename to src/sunbather/RT_tables/Ca_lines_NIST.txt diff --git a/src/RT_tables/Cl+10_levels_NIST.txt b/src/sunbather/RT_tables/Cl+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+10_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+10_levels_NIST.txt diff --git a/src/RT_tables/Cl+10_levels_processed.txt b/src/sunbather/RT_tables/Cl+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+10_levels_processed.txt rename to src/sunbather/RT_tables/Cl+10_levels_processed.txt diff --git a/src/RT_tables/Cl+10_lines_NIST.txt b/src/sunbather/RT_tables/Cl+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+10_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+10_lines_NIST.txt diff --git a/src/RT_tables/Cl+11_levels_NIST.txt b/src/sunbather/RT_tables/Cl+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+11_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+11_levels_NIST.txt diff --git a/src/RT_tables/Cl+11_levels_processed.txt b/src/sunbather/RT_tables/Cl+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+11_levels_processed.txt rename to src/sunbather/RT_tables/Cl+11_levels_processed.txt diff --git a/src/RT_tables/Cl+11_lines_NIST.txt b/src/sunbather/RT_tables/Cl+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+11_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+11_lines_NIST.txt diff --git a/src/RT_tables/Cl+12_levels_NIST.txt b/src/sunbather/RT_tables/Cl+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+12_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+12_levels_NIST.txt diff --git a/src/RT_tables/Cl+12_levels_processed.txt b/src/sunbather/RT_tables/Cl+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+12_levels_processed.txt rename to src/sunbather/RT_tables/Cl+12_levels_processed.txt diff --git a/src/RT_tables/Cl+12_lines_NIST.txt b/src/sunbather/RT_tables/Cl+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+12_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+12_lines_NIST.txt diff --git a/src/RT_tables/Cl+2_levels_NIST.txt b/src/sunbather/RT_tables/Cl+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+2_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+2_levels_NIST.txt diff --git a/src/RT_tables/Cl+2_levels_processed.txt b/src/sunbather/RT_tables/Cl+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+2_levels_processed.txt rename to src/sunbather/RT_tables/Cl+2_levels_processed.txt diff --git a/src/RT_tables/Cl+2_lines_NIST.txt b/src/sunbather/RT_tables/Cl+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+2_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+2_lines_NIST.txt diff --git a/src/RT_tables/Cl+3_levels_NIST.txt b/src/sunbather/RT_tables/Cl+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+3_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+3_levels_NIST.txt diff --git a/src/RT_tables/Cl+3_levels_processed.txt b/src/sunbather/RT_tables/Cl+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+3_levels_processed.txt rename to src/sunbather/RT_tables/Cl+3_levels_processed.txt diff --git a/src/RT_tables/Cl+3_lines_NIST.txt b/src/sunbather/RT_tables/Cl+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+3_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+3_lines_NIST.txt diff --git a/src/RT_tables/Cl+4_levels_NIST.txt b/src/sunbather/RT_tables/Cl+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+4_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+4_levels_NIST.txt diff --git a/src/RT_tables/Cl+4_levels_processed.txt b/src/sunbather/RT_tables/Cl+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+4_levels_processed.txt rename to src/sunbather/RT_tables/Cl+4_levels_processed.txt diff --git a/src/RT_tables/Cl+4_lines_NIST.txt b/src/sunbather/RT_tables/Cl+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+4_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+4_lines_NIST.txt diff --git a/src/RT_tables/Cl+5_levels_NIST.txt b/src/sunbather/RT_tables/Cl+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+5_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+5_levels_NIST.txt diff --git a/src/RT_tables/Cl+5_levels_processed.txt b/src/sunbather/RT_tables/Cl+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+5_levels_processed.txt rename to src/sunbather/RT_tables/Cl+5_levels_processed.txt diff --git a/src/RT_tables/Cl+5_lines_NIST.txt b/src/sunbather/RT_tables/Cl+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+5_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+5_lines_NIST.txt diff --git a/src/RT_tables/Cl+6_levels_NIST.txt b/src/sunbather/RT_tables/Cl+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+6_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+6_levels_NIST.txt diff --git a/src/RT_tables/Cl+6_levels_processed.txt b/src/sunbather/RT_tables/Cl+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+6_levels_processed.txt rename to src/sunbather/RT_tables/Cl+6_levels_processed.txt diff --git a/src/RT_tables/Cl+6_lines_NIST.txt b/src/sunbather/RT_tables/Cl+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+6_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+6_lines_NIST.txt diff --git a/src/RT_tables/Cl+7_levels_NIST.txt b/src/sunbather/RT_tables/Cl+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+7_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+7_levels_NIST.txt diff --git a/src/RT_tables/Cl+7_levels_processed.txt b/src/sunbather/RT_tables/Cl+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+7_levels_processed.txt rename to src/sunbather/RT_tables/Cl+7_levels_processed.txt diff --git a/src/RT_tables/Cl+7_lines_NIST.txt b/src/sunbather/RT_tables/Cl+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+7_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+7_lines_NIST.txt diff --git a/src/RT_tables/Cl+8_levels_NIST.txt b/src/sunbather/RT_tables/Cl+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+8_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+8_levels_NIST.txt diff --git a/src/RT_tables/Cl+8_levels_processed.txt b/src/sunbather/RT_tables/Cl+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+8_levels_processed.txt rename to src/sunbather/RT_tables/Cl+8_levels_processed.txt diff --git a/src/RT_tables/Cl+8_lines_NIST.txt b/src/sunbather/RT_tables/Cl+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+8_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+8_lines_NIST.txt diff --git a/src/RT_tables/Cl+9_levels_NIST.txt b/src/sunbather/RT_tables/Cl+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+9_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+9_levels_NIST.txt diff --git a/src/RT_tables/Cl+9_levels_processed.txt b/src/sunbather/RT_tables/Cl+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+9_levels_processed.txt rename to src/sunbather/RT_tables/Cl+9_levels_processed.txt diff --git a/src/RT_tables/Cl+9_lines_NIST.txt b/src/sunbather/RT_tables/Cl+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+9_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+9_lines_NIST.txt diff --git a/src/RT_tables/Cl+_levels_NIST.txt b/src/sunbather/RT_tables/Cl+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl+_levels_NIST.txt rename to src/sunbather/RT_tables/Cl+_levels_NIST.txt diff --git a/src/RT_tables/Cl+_levels_processed.txt b/src/sunbather/RT_tables/Cl+_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl+_levels_processed.txt rename to src/sunbather/RT_tables/Cl+_levels_processed.txt diff --git a/src/RT_tables/Cl+_lines_NIST.txt b/src/sunbather/RT_tables/Cl+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl+_lines_NIST.txt rename to src/sunbather/RT_tables/Cl+_lines_NIST.txt diff --git a/src/RT_tables/Cl_levels_NIST.txt b/src/sunbather/RT_tables/Cl_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cl_levels_NIST.txt rename to src/sunbather/RT_tables/Cl_levels_NIST.txt diff --git a/src/RT_tables/Cl_levels_processed.txt b/src/sunbather/RT_tables/Cl_levels_processed.txt similarity index 100% rename from src/RT_tables/Cl_levels_processed.txt rename to src/sunbather/RT_tables/Cl_levels_processed.txt diff --git a/src/RT_tables/Cl_lines_NIST.txt b/src/sunbather/RT_tables/Cl_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cl_lines_NIST.txt rename to src/sunbather/RT_tables/Cl_lines_NIST.txt diff --git a/src/RT_tables/Co+10_levels_NIST.txt b/src/sunbather/RT_tables/Co+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+10_levels_NIST.txt rename to src/sunbather/RT_tables/Co+10_levels_NIST.txt diff --git a/src/RT_tables/Co+10_levels_processed.txt b/src/sunbather/RT_tables/Co+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+10_levels_processed.txt rename to src/sunbather/RT_tables/Co+10_levels_processed.txt diff --git a/src/RT_tables/Co+10_lines_NIST.txt b/src/sunbather/RT_tables/Co+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+10_lines_NIST.txt rename to src/sunbather/RT_tables/Co+10_lines_NIST.txt diff --git a/src/RT_tables/Co+11_levels_NIST.txt b/src/sunbather/RT_tables/Co+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+11_levels_NIST.txt rename to src/sunbather/RT_tables/Co+11_levels_NIST.txt diff --git a/src/RT_tables/Co+11_levels_processed.txt b/src/sunbather/RT_tables/Co+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+11_levels_processed.txt rename to src/sunbather/RT_tables/Co+11_levels_processed.txt diff --git a/src/RT_tables/Co+11_lines_NIST.txt b/src/sunbather/RT_tables/Co+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+11_lines_NIST.txt rename to src/sunbather/RT_tables/Co+11_lines_NIST.txt diff --git a/src/RT_tables/Co+12_levels_NIST.txt b/src/sunbather/RT_tables/Co+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+12_levels_NIST.txt rename to src/sunbather/RT_tables/Co+12_levels_NIST.txt diff --git a/src/RT_tables/Co+12_levels_processed.txt b/src/sunbather/RT_tables/Co+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+12_levels_processed.txt rename to src/sunbather/RT_tables/Co+12_levels_processed.txt diff --git a/src/RT_tables/Co+12_lines_NIST.txt b/src/sunbather/RT_tables/Co+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+12_lines_NIST.txt rename to src/sunbather/RT_tables/Co+12_lines_NIST.txt diff --git a/src/RT_tables/Co+2_levels_NIST.txt b/src/sunbather/RT_tables/Co+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+2_levels_NIST.txt rename to src/sunbather/RT_tables/Co+2_levels_NIST.txt diff --git a/src/RT_tables/Co+2_levels_processed.txt b/src/sunbather/RT_tables/Co+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+2_levels_processed.txt rename to src/sunbather/RT_tables/Co+2_levels_processed.txt diff --git a/src/RT_tables/Co+2_lines_NIST.txt b/src/sunbather/RT_tables/Co+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+2_lines_NIST.txt rename to src/sunbather/RT_tables/Co+2_lines_NIST.txt diff --git a/src/RT_tables/Co+3_levels_NIST.txt b/src/sunbather/RT_tables/Co+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+3_levels_NIST.txt rename to src/sunbather/RT_tables/Co+3_levels_NIST.txt diff --git a/src/RT_tables/Co+3_lines_NIST.txt b/src/sunbather/RT_tables/Co+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+3_lines_NIST.txt rename to src/sunbather/RT_tables/Co+3_lines_NIST.txt diff --git a/src/RT_tables/Co+4_levels_NIST.txt b/src/sunbather/RT_tables/Co+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+4_levels_NIST.txt rename to src/sunbather/RT_tables/Co+4_levels_NIST.txt diff --git a/src/RT_tables/Co+4_lines_NIST.txt b/src/sunbather/RT_tables/Co+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+4_lines_NIST.txt rename to src/sunbather/RT_tables/Co+4_lines_NIST.txt diff --git a/src/RT_tables/Co+5_levels_NIST.txt b/src/sunbather/RT_tables/Co+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+5_levels_NIST.txt rename to src/sunbather/RT_tables/Co+5_levels_NIST.txt diff --git a/src/RT_tables/Co+5_lines_NIST.txt b/src/sunbather/RT_tables/Co+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+5_lines_NIST.txt rename to src/sunbather/RT_tables/Co+5_lines_NIST.txt diff --git a/src/RT_tables/Co+6_levels_NIST.txt b/src/sunbather/RT_tables/Co+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+6_levels_NIST.txt rename to src/sunbather/RT_tables/Co+6_levels_NIST.txt diff --git a/src/RT_tables/Co+6_lines_NIST.txt b/src/sunbather/RT_tables/Co+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+6_lines_NIST.txt rename to src/sunbather/RT_tables/Co+6_lines_NIST.txt diff --git a/src/RT_tables/Co+7_levels_NIST.txt b/src/sunbather/RT_tables/Co+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+7_levels_NIST.txt rename to src/sunbather/RT_tables/Co+7_levels_NIST.txt diff --git a/src/RT_tables/Co+7_levels_processed.txt b/src/sunbather/RT_tables/Co+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+7_levels_processed.txt rename to src/sunbather/RT_tables/Co+7_levels_processed.txt diff --git a/src/RT_tables/Co+7_lines_NIST.txt b/src/sunbather/RT_tables/Co+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+7_lines_NIST.txt rename to src/sunbather/RT_tables/Co+7_lines_NIST.txt diff --git a/src/RT_tables/Co+8_levels_NIST.txt b/src/sunbather/RT_tables/Co+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+8_levels_NIST.txt rename to src/sunbather/RT_tables/Co+8_levels_NIST.txt diff --git a/src/RT_tables/Co+8_levels_processed.txt b/src/sunbather/RT_tables/Co+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+8_levels_processed.txt rename to src/sunbather/RT_tables/Co+8_levels_processed.txt diff --git a/src/RT_tables/Co+8_lines_NIST.txt b/src/sunbather/RT_tables/Co+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+8_lines_NIST.txt rename to src/sunbather/RT_tables/Co+8_lines_NIST.txt diff --git a/src/RT_tables/Co+9_levels_NIST.txt b/src/sunbather/RT_tables/Co+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+9_levels_NIST.txt rename to src/sunbather/RT_tables/Co+9_levels_NIST.txt diff --git a/src/RT_tables/Co+9_levels_processed.txt b/src/sunbather/RT_tables/Co+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+9_levels_processed.txt rename to src/sunbather/RT_tables/Co+9_levels_processed.txt diff --git a/src/RT_tables/Co+9_lines_NIST.txt b/src/sunbather/RT_tables/Co+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+9_lines_NIST.txt rename to src/sunbather/RT_tables/Co+9_lines_NIST.txt diff --git a/src/RT_tables/Co+_levels_NIST.txt b/src/sunbather/RT_tables/Co+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co+_levels_NIST.txt rename to src/sunbather/RT_tables/Co+_levels_NIST.txt diff --git a/src/RT_tables/Co+_levels_processed.txt b/src/sunbather/RT_tables/Co+_levels_processed.txt similarity index 100% rename from src/RT_tables/Co+_levels_processed.txt rename to src/sunbather/RT_tables/Co+_levels_processed.txt diff --git a/src/RT_tables/Co+_lines_NIST.txt b/src/sunbather/RT_tables/Co+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co+_lines_NIST.txt rename to src/sunbather/RT_tables/Co+_lines_NIST.txt diff --git a/src/RT_tables/Co_levels_NIST.txt b/src/sunbather/RT_tables/Co_levels_NIST.txt similarity index 100% rename from src/RT_tables/Co_levels_NIST.txt rename to src/sunbather/RT_tables/Co_levels_NIST.txt diff --git a/src/RT_tables/Co_levels_processed.txt b/src/sunbather/RT_tables/Co_levels_processed.txt similarity index 100% rename from src/RT_tables/Co_levels_processed.txt rename to src/sunbather/RT_tables/Co_levels_processed.txt diff --git a/src/RT_tables/Co_lines_NIST.txt b/src/sunbather/RT_tables/Co_lines_NIST.txt similarity index 100% rename from src/RT_tables/Co_lines_NIST.txt rename to src/sunbather/RT_tables/Co_lines_NIST.txt diff --git a/src/RT_tables/Cr+10_levels_NIST.txt b/src/sunbather/RT_tables/Cr+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+10_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+10_levels_NIST.txt diff --git a/src/RT_tables/Cr+10_levels_processed.txt b/src/sunbather/RT_tables/Cr+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+10_levels_processed.txt rename to src/sunbather/RT_tables/Cr+10_levels_processed.txt diff --git a/src/RT_tables/Cr+10_lines_NIST.txt b/src/sunbather/RT_tables/Cr+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+10_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+10_lines_NIST.txt diff --git a/src/RT_tables/Cr+11_levels_NIST.txt b/src/sunbather/RT_tables/Cr+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+11_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+11_levels_NIST.txt diff --git a/src/RT_tables/Cr+11_levels_processed.txt b/src/sunbather/RT_tables/Cr+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+11_levels_processed.txt rename to src/sunbather/RT_tables/Cr+11_levels_processed.txt diff --git a/src/RT_tables/Cr+11_lines_NIST.txt b/src/sunbather/RT_tables/Cr+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+11_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+11_lines_NIST.txt diff --git a/src/RT_tables/Cr+12_levels_NIST.txt b/src/sunbather/RT_tables/Cr+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+12_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+12_levels_NIST.txt diff --git a/src/RT_tables/Cr+12_levels_processed.txt b/src/sunbather/RT_tables/Cr+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+12_levels_processed.txt rename to src/sunbather/RT_tables/Cr+12_levels_processed.txt diff --git a/src/RT_tables/Cr+12_lines_NIST.txt b/src/sunbather/RT_tables/Cr+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+12_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+12_lines_NIST.txt diff --git a/src/RT_tables/Cr+2_levels_NIST.txt b/src/sunbather/RT_tables/Cr+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+2_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+2_levels_NIST.txt diff --git a/src/RT_tables/Cr+2_lines_NIST.txt b/src/sunbather/RT_tables/Cr+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+2_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+2_lines_NIST.txt diff --git a/src/RT_tables/Cr+3_levels_NIST.txt b/src/sunbather/RT_tables/Cr+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+3_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+3_levels_NIST.txt diff --git a/src/RT_tables/Cr+3_levels_processed.txt b/src/sunbather/RT_tables/Cr+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+3_levels_processed.txt rename to src/sunbather/RT_tables/Cr+3_levels_processed.txt diff --git a/src/RT_tables/Cr+3_lines_NIST.txt b/src/sunbather/RT_tables/Cr+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+3_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+3_lines_NIST.txt diff --git a/src/RT_tables/Cr+4_levels_NIST.txt b/src/sunbather/RT_tables/Cr+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+4_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+4_levels_NIST.txt diff --git a/src/RT_tables/Cr+4_levels_processed.txt b/src/sunbather/RT_tables/Cr+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+4_levels_processed.txt rename to src/sunbather/RT_tables/Cr+4_levels_processed.txt diff --git a/src/RT_tables/Cr+4_lines_NIST.txt b/src/sunbather/RT_tables/Cr+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+4_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+4_lines_NIST.txt diff --git a/src/RT_tables/Cr+5_levels_NIST.txt b/src/sunbather/RT_tables/Cr+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+5_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+5_levels_NIST.txt diff --git a/src/RT_tables/Cr+5_lines_NIST.txt b/src/sunbather/RT_tables/Cr+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+5_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+5_lines_NIST.txt diff --git a/src/RT_tables/Cr+6_levels_NIST.txt b/src/sunbather/RT_tables/Cr+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+6_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+6_levels_NIST.txt diff --git a/src/RT_tables/Cr+6_lines_NIST.txt b/src/sunbather/RT_tables/Cr+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+6_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+6_lines_NIST.txt diff --git a/src/RT_tables/Cr+7_levels_NIST.txt b/src/sunbather/RT_tables/Cr+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+7_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+7_levels_NIST.txt diff --git a/src/RT_tables/Cr+7_levels_processed.txt b/src/sunbather/RT_tables/Cr+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+7_levels_processed.txt rename to src/sunbather/RT_tables/Cr+7_levels_processed.txt diff --git a/src/RT_tables/Cr+7_lines_NIST.txt b/src/sunbather/RT_tables/Cr+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+7_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+7_lines_NIST.txt diff --git a/src/RT_tables/Cr+8_levels_NIST.txt b/src/sunbather/RT_tables/Cr+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+8_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+8_levels_NIST.txt diff --git a/src/RT_tables/Cr+8_levels_processed.txt b/src/sunbather/RT_tables/Cr+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+8_levels_processed.txt rename to src/sunbather/RT_tables/Cr+8_levels_processed.txt diff --git a/src/RT_tables/Cr+8_lines_NIST.txt b/src/sunbather/RT_tables/Cr+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+8_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+8_lines_NIST.txt diff --git a/src/RT_tables/Cr+9_levels_NIST.txt b/src/sunbather/RT_tables/Cr+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+9_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+9_levels_NIST.txt diff --git a/src/RT_tables/Cr+9_levels_processed.txt b/src/sunbather/RT_tables/Cr+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+9_levels_processed.txt rename to src/sunbather/RT_tables/Cr+9_levels_processed.txt diff --git a/src/RT_tables/Cr+9_lines_NIST.txt b/src/sunbather/RT_tables/Cr+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+9_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+9_lines_NIST.txt diff --git a/src/RT_tables/Cr+_levels_NIST.txt b/src/sunbather/RT_tables/Cr+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr+_levels_NIST.txt rename to src/sunbather/RT_tables/Cr+_levels_NIST.txt diff --git a/src/RT_tables/Cr+_levels_processed.txt b/src/sunbather/RT_tables/Cr+_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr+_levels_processed.txt rename to src/sunbather/RT_tables/Cr+_levels_processed.txt diff --git a/src/RT_tables/Cr+_lines_NIST.txt b/src/sunbather/RT_tables/Cr+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr+_lines_NIST.txt rename to src/sunbather/RT_tables/Cr+_lines_NIST.txt diff --git a/src/RT_tables/Cr_levels_NIST.txt b/src/sunbather/RT_tables/Cr_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cr_levels_NIST.txt rename to src/sunbather/RT_tables/Cr_levels_NIST.txt diff --git a/src/RT_tables/Cr_levels_processed.txt b/src/sunbather/RT_tables/Cr_levels_processed.txt similarity index 100% rename from src/RT_tables/Cr_levels_processed.txt rename to src/sunbather/RT_tables/Cr_levels_processed.txt diff --git a/src/RT_tables/Cr_lines_NIST.txt b/src/sunbather/RT_tables/Cr_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cr_lines_NIST.txt rename to src/sunbather/RT_tables/Cr_lines_NIST.txt diff --git a/src/RT_tables/Cu+10_levels_NIST.txt b/src/sunbather/RT_tables/Cu+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+10_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+10_levels_NIST.txt diff --git a/src/RT_tables/Cu+10_levels_processed.txt b/src/sunbather/RT_tables/Cu+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+10_levels_processed.txt rename to src/sunbather/RT_tables/Cu+10_levels_processed.txt diff --git a/src/RT_tables/Cu+10_lines_NIST.txt b/src/sunbather/RT_tables/Cu+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+10_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+10_lines_NIST.txt diff --git a/src/RT_tables/Cu+11_levels_NIST.txt b/src/sunbather/RT_tables/Cu+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+11_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+11_levels_NIST.txt diff --git a/src/RT_tables/Cu+11_levels_processed.txt b/src/sunbather/RT_tables/Cu+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+11_levels_processed.txt rename to src/sunbather/RT_tables/Cu+11_levels_processed.txt diff --git a/src/RT_tables/Cu+11_lines_NIST.txt b/src/sunbather/RT_tables/Cu+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+11_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+11_lines_NIST.txt diff --git a/src/RT_tables/Cu+12_levels_NIST.txt b/src/sunbather/RT_tables/Cu+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+12_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+12_levels_NIST.txt diff --git a/src/RT_tables/Cu+12_levels_processed.txt b/src/sunbather/RT_tables/Cu+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+12_levels_processed.txt rename to src/sunbather/RT_tables/Cu+12_levels_processed.txt diff --git a/src/RT_tables/Cu+12_lines_NIST.txt b/src/sunbather/RT_tables/Cu+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+12_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+12_lines_NIST.txt diff --git a/src/RT_tables/Cu+2_levels_NIST.txt b/src/sunbather/RT_tables/Cu+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+2_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+2_levels_NIST.txt diff --git a/src/RT_tables/Cu+2_lines_NIST.txt b/src/sunbather/RT_tables/Cu+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+2_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+2_lines_NIST.txt diff --git a/src/RT_tables/Cu+3_levels_NIST.txt b/src/sunbather/RT_tables/Cu+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+3_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+3_levels_NIST.txt diff --git a/src/RT_tables/Cu+3_lines_NIST.txt b/src/sunbather/RT_tables/Cu+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+3_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+3_lines_NIST.txt diff --git a/src/RT_tables/Cu+4_levels_NIST.txt b/src/sunbather/RT_tables/Cu+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+4_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+4_levels_NIST.txt diff --git a/src/RT_tables/Cu+4_lines_NIST.txt b/src/sunbather/RT_tables/Cu+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+4_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+4_lines_NIST.txt diff --git a/src/RT_tables/Cu+5_levels_NIST.txt b/src/sunbather/RT_tables/Cu+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+5_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+5_levels_NIST.txt diff --git a/src/RT_tables/Cu+5_lines_NIST.txt b/src/sunbather/RT_tables/Cu+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+5_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+5_lines_NIST.txt diff --git a/src/RT_tables/Cu+6_levels_NIST.txt b/src/sunbather/RT_tables/Cu+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+6_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+6_levels_NIST.txt diff --git a/src/RT_tables/Cu+6_lines_NIST.txt b/src/sunbather/RT_tables/Cu+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+6_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+6_lines_NIST.txt diff --git a/src/RT_tables/Cu+7_levels_NIST.txt b/src/sunbather/RT_tables/Cu+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+7_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+7_levels_NIST.txt diff --git a/src/RT_tables/Cu+7_levels_processed.txt b/src/sunbather/RT_tables/Cu+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+7_levels_processed.txt rename to src/sunbather/RT_tables/Cu+7_levels_processed.txt diff --git a/src/RT_tables/Cu+7_lines_NIST.txt b/src/sunbather/RT_tables/Cu+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+7_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+7_lines_NIST.txt diff --git a/src/RT_tables/Cu+8_levels_NIST.txt b/src/sunbather/RT_tables/Cu+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+8_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+8_levels_NIST.txt diff --git a/src/RT_tables/Cu+8_levels_processed.txt b/src/sunbather/RT_tables/Cu+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+8_levels_processed.txt rename to src/sunbather/RT_tables/Cu+8_levels_processed.txt diff --git a/src/RT_tables/Cu+8_lines_NIST.txt b/src/sunbather/RT_tables/Cu+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+8_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+8_lines_NIST.txt diff --git a/src/RT_tables/Cu+9_levels_NIST.txt b/src/sunbather/RT_tables/Cu+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+9_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+9_levels_NIST.txt diff --git a/src/RT_tables/Cu+9_levels_processed.txt b/src/sunbather/RT_tables/Cu+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+9_levels_processed.txt rename to src/sunbather/RT_tables/Cu+9_levels_processed.txt diff --git a/src/RT_tables/Cu+9_lines_NIST.txt b/src/sunbather/RT_tables/Cu+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+9_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+9_lines_NIST.txt diff --git a/src/RT_tables/Cu+_levels_NIST.txt b/src/sunbather/RT_tables/Cu+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu+_levels_NIST.txt rename to src/sunbather/RT_tables/Cu+_levels_NIST.txt diff --git a/src/RT_tables/Cu+_levels_processed.txt b/src/sunbather/RT_tables/Cu+_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu+_levels_processed.txt rename to src/sunbather/RT_tables/Cu+_levels_processed.txt diff --git a/src/RT_tables/Cu+_lines_NIST.txt b/src/sunbather/RT_tables/Cu+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu+_lines_NIST.txt rename to src/sunbather/RT_tables/Cu+_lines_NIST.txt diff --git a/src/RT_tables/Cu_levels_NIST.txt b/src/sunbather/RT_tables/Cu_levels_NIST.txt similarity index 100% rename from src/RT_tables/Cu_levels_NIST.txt rename to src/sunbather/RT_tables/Cu_levels_NIST.txt diff --git a/src/RT_tables/Cu_levels_processed.txt b/src/sunbather/RT_tables/Cu_levels_processed.txt similarity index 100% rename from src/RT_tables/Cu_levels_processed.txt rename to src/sunbather/RT_tables/Cu_levels_processed.txt diff --git a/src/RT_tables/Cu_lines_NIST.txt b/src/sunbather/RT_tables/Cu_lines_NIST.txt similarity index 100% rename from src/RT_tables/Cu_lines_NIST.txt rename to src/sunbather/RT_tables/Cu_lines_NIST.txt diff --git a/src/RT_tables/F+2_levels_NIST.txt b/src/sunbather/RT_tables/F+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+2_levels_NIST.txt rename to src/sunbather/RT_tables/F+2_levels_NIST.txt diff --git a/src/RT_tables/F+2_levels_processed.txt b/src/sunbather/RT_tables/F+2_levels_processed.txt similarity index 100% rename from src/RT_tables/F+2_levels_processed.txt rename to src/sunbather/RT_tables/F+2_levels_processed.txt diff --git a/src/RT_tables/F+2_lines_NIST.txt b/src/sunbather/RT_tables/F+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+2_lines_NIST.txt rename to src/sunbather/RT_tables/F+2_lines_NIST.txt diff --git a/src/RT_tables/F+3_levels_NIST.txt b/src/sunbather/RT_tables/F+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+3_levels_NIST.txt rename to src/sunbather/RT_tables/F+3_levels_NIST.txt diff --git a/src/RT_tables/F+3_levels_processed.txt b/src/sunbather/RT_tables/F+3_levels_processed.txt similarity index 100% rename from src/RT_tables/F+3_levels_processed.txt rename to src/sunbather/RT_tables/F+3_levels_processed.txt diff --git a/src/RT_tables/F+3_lines_NIST.txt b/src/sunbather/RT_tables/F+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+3_lines_NIST.txt rename to src/sunbather/RT_tables/F+3_lines_NIST.txt diff --git a/src/RT_tables/F+4_levels_NIST.txt b/src/sunbather/RT_tables/F+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+4_levels_NIST.txt rename to src/sunbather/RT_tables/F+4_levels_NIST.txt diff --git a/src/RT_tables/F+4_levels_processed.txt b/src/sunbather/RT_tables/F+4_levels_processed.txt similarity index 100% rename from src/RT_tables/F+4_levels_processed.txt rename to src/sunbather/RT_tables/F+4_levels_processed.txt diff --git a/src/RT_tables/F+4_lines_NIST.txt b/src/sunbather/RT_tables/F+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+4_lines_NIST.txt rename to src/sunbather/RT_tables/F+4_lines_NIST.txt diff --git a/src/RT_tables/F+5_levels_NIST.txt b/src/sunbather/RT_tables/F+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+5_levels_NIST.txt rename to src/sunbather/RT_tables/F+5_levels_NIST.txt diff --git a/src/RT_tables/F+5_levels_processed.txt b/src/sunbather/RT_tables/F+5_levels_processed.txt similarity index 100% rename from src/RT_tables/F+5_levels_processed.txt rename to src/sunbather/RT_tables/F+5_levels_processed.txt diff --git a/src/RT_tables/F+5_lines_NIST.txt b/src/sunbather/RT_tables/F+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+5_lines_NIST.txt rename to src/sunbather/RT_tables/F+5_lines_NIST.txt diff --git a/src/RT_tables/F+6_levels_NIST.txt b/src/sunbather/RT_tables/F+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+6_levels_NIST.txt rename to src/sunbather/RT_tables/F+6_levels_NIST.txt diff --git a/src/RT_tables/F+6_levels_processed.txt b/src/sunbather/RT_tables/F+6_levels_processed.txt similarity index 100% rename from src/RT_tables/F+6_levels_processed.txt rename to src/sunbather/RT_tables/F+6_levels_processed.txt diff --git a/src/RT_tables/F+6_lines_NIST.txt b/src/sunbather/RT_tables/F+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+6_lines_NIST.txt rename to src/sunbather/RT_tables/F+6_lines_NIST.txt diff --git a/src/RT_tables/F+7_levels_NIST.txt b/src/sunbather/RT_tables/F+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+7_levels_NIST.txt rename to src/sunbather/RT_tables/F+7_levels_NIST.txt diff --git a/src/RT_tables/F+7_levels_processed.txt b/src/sunbather/RT_tables/F+7_levels_processed.txt similarity index 100% rename from src/RT_tables/F+7_levels_processed.txt rename to src/sunbather/RT_tables/F+7_levels_processed.txt diff --git a/src/RT_tables/F+7_lines_NIST.txt b/src/sunbather/RT_tables/F+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+7_lines_NIST.txt rename to src/sunbather/RT_tables/F+7_lines_NIST.txt diff --git a/src/RT_tables/F+8_levels_NIST.txt b/src/sunbather/RT_tables/F+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+8_levels_NIST.txt rename to src/sunbather/RT_tables/F+8_levels_NIST.txt diff --git a/src/RT_tables/F+8_levels_processed.txt b/src/sunbather/RT_tables/F+8_levels_processed.txt similarity index 100% rename from src/RT_tables/F+8_levels_processed.txt rename to src/sunbather/RT_tables/F+8_levels_processed.txt diff --git a/src/RT_tables/F+8_lines_NIST.txt b/src/sunbather/RT_tables/F+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+8_lines_NIST.txt rename to src/sunbather/RT_tables/F+8_lines_NIST.txt diff --git a/src/RT_tables/F+_levels_NIST.txt b/src/sunbather/RT_tables/F+_levels_NIST.txt similarity index 100% rename from src/RT_tables/F+_levels_NIST.txt rename to src/sunbather/RT_tables/F+_levels_NIST.txt diff --git a/src/RT_tables/F+_levels_processed.txt b/src/sunbather/RT_tables/F+_levels_processed.txt similarity index 100% rename from src/RT_tables/F+_levels_processed.txt rename to src/sunbather/RT_tables/F+_levels_processed.txt diff --git a/src/RT_tables/F+_lines_NIST.txt b/src/sunbather/RT_tables/F+_lines_NIST.txt similarity index 100% rename from src/RT_tables/F+_lines_NIST.txt rename to src/sunbather/RT_tables/F+_lines_NIST.txt diff --git a/src/RT_tables/F_levels_NIST.txt b/src/sunbather/RT_tables/F_levels_NIST.txt similarity index 100% rename from src/RT_tables/F_levels_NIST.txt rename to src/sunbather/RT_tables/F_levels_NIST.txt diff --git a/src/RT_tables/F_lines_NIST.txt b/src/sunbather/RT_tables/F_lines_NIST.txt similarity index 100% rename from src/RT_tables/F_lines_NIST.txt rename to src/sunbather/RT_tables/F_lines_NIST.txt diff --git a/src/RT_tables/Fe+10_levels_NIST.txt b/src/sunbather/RT_tables/Fe+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+10_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+10_levels_NIST.txt diff --git a/src/RT_tables/Fe+10_levels_processed.txt b/src/sunbather/RT_tables/Fe+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+10_levels_processed.txt rename to src/sunbather/RT_tables/Fe+10_levels_processed.txt diff --git a/src/RT_tables/Fe+10_lines_NIST.txt b/src/sunbather/RT_tables/Fe+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+10_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+10_lines_NIST.txt diff --git a/src/RT_tables/Fe+11_levels_NIST.txt b/src/sunbather/RT_tables/Fe+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+11_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+11_levels_NIST.txt diff --git a/src/RT_tables/Fe+11_levels_processed.txt b/src/sunbather/RT_tables/Fe+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+11_levels_processed.txt rename to src/sunbather/RT_tables/Fe+11_levels_processed.txt diff --git a/src/RT_tables/Fe+11_lines_NIST.txt b/src/sunbather/RT_tables/Fe+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+11_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+11_lines_NIST.txt diff --git a/src/RT_tables/Fe+12_levels_NIST.txt b/src/sunbather/RT_tables/Fe+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+12_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+12_levels_NIST.txt diff --git a/src/RT_tables/Fe+12_levels_processed.txt b/src/sunbather/RT_tables/Fe+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+12_levels_processed.txt rename to src/sunbather/RT_tables/Fe+12_levels_processed.txt diff --git a/src/RT_tables/Fe+12_lines_NIST.txt b/src/sunbather/RT_tables/Fe+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+12_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+12_lines_NIST.txt diff --git a/src/RT_tables/Fe+2_levels_NIST.txt b/src/sunbather/RT_tables/Fe+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+2_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+2_levels_NIST.txt diff --git a/src/RT_tables/Fe+2_levels_processed.txt b/src/sunbather/RT_tables/Fe+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+2_levels_processed.txt rename to src/sunbather/RT_tables/Fe+2_levels_processed.txt diff --git a/src/RT_tables/Fe+2_lines_NIST.txt b/src/sunbather/RT_tables/Fe+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+2_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+2_lines_NIST.txt diff --git a/src/RT_tables/Fe+3_levels_NIST.txt b/src/sunbather/RT_tables/Fe+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+3_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+3_levels_NIST.txt diff --git a/src/RT_tables/Fe+3_levels_processed.txt b/src/sunbather/RT_tables/Fe+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+3_levels_processed.txt rename to src/sunbather/RT_tables/Fe+3_levels_processed.txt diff --git a/src/RT_tables/Fe+3_lines_NIST.txt b/src/sunbather/RT_tables/Fe+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+3_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+3_lines_NIST.txt diff --git a/src/RT_tables/Fe+4_levels_NIST.txt b/src/sunbather/RT_tables/Fe+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+4_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+4_levels_NIST.txt diff --git a/src/RT_tables/Fe+4_levels_processed.txt b/src/sunbather/RT_tables/Fe+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+4_levels_processed.txt rename to src/sunbather/RT_tables/Fe+4_levels_processed.txt diff --git a/src/RT_tables/Fe+4_lines_NIST.txt b/src/sunbather/RT_tables/Fe+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+4_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+4_lines_NIST.txt diff --git a/src/RT_tables/Fe+5_levels_NIST.txt b/src/sunbather/RT_tables/Fe+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+5_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+5_levels_NIST.txt diff --git a/src/RT_tables/Fe+5_levels_processed.txt b/src/sunbather/RT_tables/Fe+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+5_levels_processed.txt rename to src/sunbather/RT_tables/Fe+5_levels_processed.txt diff --git a/src/RT_tables/Fe+5_lines_NIST.txt b/src/sunbather/RT_tables/Fe+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+5_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+5_lines_NIST.txt diff --git a/src/RT_tables/Fe+6_levels_NIST.txt b/src/sunbather/RT_tables/Fe+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+6_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+6_levels_NIST.txt diff --git a/src/RT_tables/Fe+6_levels_processed.txt b/src/sunbather/RT_tables/Fe+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+6_levels_processed.txt rename to src/sunbather/RT_tables/Fe+6_levels_processed.txt diff --git a/src/RT_tables/Fe+6_lines_NIST.txt b/src/sunbather/RT_tables/Fe+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+6_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+6_lines_NIST.txt diff --git a/src/RT_tables/Fe+7_levels_NIST.txt b/src/sunbather/RT_tables/Fe+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+7_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+7_levels_NIST.txt diff --git a/src/RT_tables/Fe+7_levels_processed.txt b/src/sunbather/RT_tables/Fe+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+7_levels_processed.txt rename to src/sunbather/RT_tables/Fe+7_levels_processed.txt diff --git a/src/RT_tables/Fe+7_lines_NIST.txt b/src/sunbather/RT_tables/Fe+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+7_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+7_lines_NIST.txt diff --git a/src/RT_tables/Fe+8_levels_NIST.txt b/src/sunbather/RT_tables/Fe+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+8_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+8_levels_NIST.txt diff --git a/src/RT_tables/Fe+8_levels_processed.txt b/src/sunbather/RT_tables/Fe+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+8_levels_processed.txt rename to src/sunbather/RT_tables/Fe+8_levels_processed.txt diff --git a/src/RT_tables/Fe+8_lines_NIST.txt b/src/sunbather/RT_tables/Fe+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+8_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+8_lines_NIST.txt diff --git a/src/RT_tables/Fe+9_levels_NIST.txt b/src/sunbather/RT_tables/Fe+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+9_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+9_levels_NIST.txt diff --git a/src/RT_tables/Fe+9_levels_processed.txt b/src/sunbather/RT_tables/Fe+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+9_levels_processed.txt rename to src/sunbather/RT_tables/Fe+9_levels_processed.txt diff --git a/src/RT_tables/Fe+9_lines_NIST.txt b/src/sunbather/RT_tables/Fe+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+9_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+9_lines_NIST.txt diff --git a/src/RT_tables/Fe+_levels_NIST.txt b/src/sunbather/RT_tables/Fe+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe+_levels_NIST.txt rename to src/sunbather/RT_tables/Fe+_levels_NIST.txt diff --git a/src/RT_tables/Fe+_levels_processed.txt b/src/sunbather/RT_tables/Fe+_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe+_levels_processed.txt rename to src/sunbather/RT_tables/Fe+_levels_processed.txt diff --git a/src/RT_tables/Fe+_lines_NIST.txt b/src/sunbather/RT_tables/Fe+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe+_lines_NIST.txt rename to src/sunbather/RT_tables/Fe+_lines_NIST.txt diff --git a/src/RT_tables/Fe_levels_NIST.txt b/src/sunbather/RT_tables/Fe_levels_NIST.txt similarity index 100% rename from src/RT_tables/Fe_levels_NIST.txt rename to src/sunbather/RT_tables/Fe_levels_NIST.txt diff --git a/src/RT_tables/Fe_levels_processed.txt b/src/sunbather/RT_tables/Fe_levels_processed.txt similarity index 100% rename from src/RT_tables/Fe_levels_processed.txt rename to src/sunbather/RT_tables/Fe_levels_processed.txt diff --git a/src/RT_tables/Fe_lines_NIST.txt b/src/sunbather/RT_tables/Fe_lines_NIST.txt similarity index 100% rename from src/RT_tables/Fe_lines_NIST.txt rename to src/sunbather/RT_tables/Fe_lines_NIST.txt diff --git a/src/RT_tables/H_levels_NIST.txt b/src/sunbather/RT_tables/H_levels_NIST.txt similarity index 100% rename from src/RT_tables/H_levels_NIST.txt rename to src/sunbather/RT_tables/H_levels_NIST.txt diff --git a/src/RT_tables/H_levels_processed.txt b/src/sunbather/RT_tables/H_levels_processed.txt similarity index 100% rename from src/RT_tables/H_levels_processed.txt rename to src/sunbather/RT_tables/H_levels_processed.txt diff --git a/src/RT_tables/H_lines_NIST.txt b/src/sunbather/RT_tables/H_lines_NIST.txt similarity index 100% rename from src/RT_tables/H_lines_NIST.txt rename to src/sunbather/RT_tables/H_lines_NIST.txt diff --git a/src/RT_tables/H_lines_NIST_all.txt b/src/sunbather/RT_tables/H_lines_NIST_all.txt similarity index 100% rename from src/RT_tables/H_lines_NIST_all.txt rename to src/sunbather/RT_tables/H_lines_NIST_all.txt diff --git a/src/RT_tables/He+_levels_NIST.txt b/src/sunbather/RT_tables/He+_levels_NIST.txt similarity index 100% rename from src/RT_tables/He+_levels_NIST.txt rename to src/sunbather/RT_tables/He+_levels_NIST.txt diff --git a/src/RT_tables/He+_levels_processed.txt b/src/sunbather/RT_tables/He+_levels_processed.txt similarity index 100% rename from src/RT_tables/He+_levels_processed.txt rename to src/sunbather/RT_tables/He+_levels_processed.txt diff --git a/src/RT_tables/He+_lines_NIST.txt b/src/sunbather/RT_tables/He+_lines_NIST.txt similarity index 100% rename from src/RT_tables/He+_lines_NIST.txt rename to src/sunbather/RT_tables/He+_lines_NIST.txt diff --git a/src/RT_tables/He_levels_NIST.txt b/src/sunbather/RT_tables/He_levels_NIST.txt similarity index 100% rename from src/RT_tables/He_levels_NIST.txt rename to src/sunbather/RT_tables/He_levels_NIST.txt diff --git a/src/RT_tables/He_levels_processed.txt b/src/sunbather/RT_tables/He_levels_processed.txt similarity index 100% rename from src/RT_tables/He_levels_processed.txt rename to src/sunbather/RT_tables/He_levels_processed.txt diff --git a/src/RT_tables/He_lines_NIST.txt b/src/sunbather/RT_tables/He_lines_NIST.txt similarity index 100% rename from src/RT_tables/He_lines_NIST.txt rename to src/sunbather/RT_tables/He_lines_NIST.txt diff --git a/src/RT_tables/K+10_levels_NIST.txt b/src/sunbather/RT_tables/K+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+10_levels_NIST.txt rename to src/sunbather/RT_tables/K+10_levels_NIST.txt diff --git a/src/RT_tables/K+10_levels_processed.txt b/src/sunbather/RT_tables/K+10_levels_processed.txt similarity index 100% rename from src/RT_tables/K+10_levels_processed.txt rename to src/sunbather/RT_tables/K+10_levels_processed.txt diff --git a/src/RT_tables/K+10_lines_NIST.txt b/src/sunbather/RT_tables/K+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+10_lines_NIST.txt rename to src/sunbather/RT_tables/K+10_lines_NIST.txt diff --git a/src/RT_tables/K+11_levels_NIST.txt b/src/sunbather/RT_tables/K+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+11_levels_NIST.txt rename to src/sunbather/RT_tables/K+11_levels_NIST.txt diff --git a/src/RT_tables/K+11_levels_processed.txt b/src/sunbather/RT_tables/K+11_levels_processed.txt similarity index 100% rename from src/RT_tables/K+11_levels_processed.txt rename to src/sunbather/RT_tables/K+11_levels_processed.txt diff --git a/src/RT_tables/K+11_lines_NIST.txt b/src/sunbather/RT_tables/K+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+11_lines_NIST.txt rename to src/sunbather/RT_tables/K+11_lines_NIST.txt diff --git a/src/RT_tables/K+12_levels_NIST.txt b/src/sunbather/RT_tables/K+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+12_levels_NIST.txt rename to src/sunbather/RT_tables/K+12_levels_NIST.txt diff --git a/src/RT_tables/K+12_levels_processed.txt b/src/sunbather/RT_tables/K+12_levels_processed.txt similarity index 100% rename from src/RT_tables/K+12_levels_processed.txt rename to src/sunbather/RT_tables/K+12_levels_processed.txt diff --git a/src/RT_tables/K+12_lines_NIST.txt b/src/sunbather/RT_tables/K+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+12_lines_NIST.txt rename to src/sunbather/RT_tables/K+12_lines_NIST.txt diff --git a/src/RT_tables/K+2_levels_NIST.txt b/src/sunbather/RT_tables/K+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+2_levels_NIST.txt rename to src/sunbather/RT_tables/K+2_levels_NIST.txt diff --git a/src/RT_tables/K+2_levels_processed.txt b/src/sunbather/RT_tables/K+2_levels_processed.txt similarity index 100% rename from src/RT_tables/K+2_levels_processed.txt rename to src/sunbather/RT_tables/K+2_levels_processed.txt diff --git a/src/RT_tables/K+2_lines_NIST.txt b/src/sunbather/RT_tables/K+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+2_lines_NIST.txt rename to src/sunbather/RT_tables/K+2_lines_NIST.txt diff --git a/src/RT_tables/K+3_levels_NIST.txt b/src/sunbather/RT_tables/K+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+3_levels_NIST.txt rename to src/sunbather/RT_tables/K+3_levels_NIST.txt diff --git a/src/RT_tables/K+3_levels_processed.txt b/src/sunbather/RT_tables/K+3_levels_processed.txt similarity index 100% rename from src/RT_tables/K+3_levels_processed.txt rename to src/sunbather/RT_tables/K+3_levels_processed.txt diff --git a/src/RT_tables/K+3_lines_NIST.txt b/src/sunbather/RT_tables/K+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+3_lines_NIST.txt rename to src/sunbather/RT_tables/K+3_lines_NIST.txt diff --git a/src/RT_tables/K+4_levels_NIST.txt b/src/sunbather/RT_tables/K+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+4_levels_NIST.txt rename to src/sunbather/RT_tables/K+4_levels_NIST.txt diff --git a/src/RT_tables/K+4_levels_processed.txt b/src/sunbather/RT_tables/K+4_levels_processed.txt similarity index 100% rename from src/RT_tables/K+4_levels_processed.txt rename to src/sunbather/RT_tables/K+4_levels_processed.txt diff --git a/src/RT_tables/K+4_lines_NIST.txt b/src/sunbather/RT_tables/K+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+4_lines_NIST.txt rename to src/sunbather/RT_tables/K+4_lines_NIST.txt diff --git a/src/RT_tables/K+5_levels_NIST.txt b/src/sunbather/RT_tables/K+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+5_levels_NIST.txt rename to src/sunbather/RT_tables/K+5_levels_NIST.txt diff --git a/src/RT_tables/K+5_levels_processed.txt b/src/sunbather/RT_tables/K+5_levels_processed.txt similarity index 100% rename from src/RT_tables/K+5_levels_processed.txt rename to src/sunbather/RT_tables/K+5_levels_processed.txt diff --git a/src/RT_tables/K+5_lines_NIST.txt b/src/sunbather/RT_tables/K+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+5_lines_NIST.txt rename to src/sunbather/RT_tables/K+5_lines_NIST.txt diff --git a/src/RT_tables/K+6_levels_NIST.txt b/src/sunbather/RT_tables/K+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+6_levels_NIST.txt rename to src/sunbather/RT_tables/K+6_levels_NIST.txt diff --git a/src/RT_tables/K+6_levels_processed.txt b/src/sunbather/RT_tables/K+6_levels_processed.txt similarity index 100% rename from src/RT_tables/K+6_levels_processed.txt rename to src/sunbather/RT_tables/K+6_levels_processed.txt diff --git a/src/RT_tables/K+6_lines_NIST.txt b/src/sunbather/RT_tables/K+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+6_lines_NIST.txt rename to src/sunbather/RT_tables/K+6_lines_NIST.txt diff --git a/src/RT_tables/K+7_levels_NIST.txt b/src/sunbather/RT_tables/K+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+7_levels_NIST.txt rename to src/sunbather/RT_tables/K+7_levels_NIST.txt diff --git a/src/RT_tables/K+7_levels_processed.txt b/src/sunbather/RT_tables/K+7_levels_processed.txt similarity index 100% rename from src/RT_tables/K+7_levels_processed.txt rename to src/sunbather/RT_tables/K+7_levels_processed.txt diff --git a/src/RT_tables/K+7_lines_NIST.txt b/src/sunbather/RT_tables/K+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+7_lines_NIST.txt rename to src/sunbather/RT_tables/K+7_lines_NIST.txt diff --git a/src/RT_tables/K+8_levels_NIST.txt b/src/sunbather/RT_tables/K+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+8_levels_NIST.txt rename to src/sunbather/RT_tables/K+8_levels_NIST.txt diff --git a/src/RT_tables/K+8_levels_processed.txt b/src/sunbather/RT_tables/K+8_levels_processed.txt similarity index 100% rename from src/RT_tables/K+8_levels_processed.txt rename to src/sunbather/RT_tables/K+8_levels_processed.txt diff --git a/src/RT_tables/K+8_lines_NIST.txt b/src/sunbather/RT_tables/K+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+8_lines_NIST.txt rename to src/sunbather/RT_tables/K+8_lines_NIST.txt diff --git a/src/RT_tables/K+9_levels_NIST.txt b/src/sunbather/RT_tables/K+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+9_levels_NIST.txt rename to src/sunbather/RT_tables/K+9_levels_NIST.txt diff --git a/src/RT_tables/K+9_levels_processed.txt b/src/sunbather/RT_tables/K+9_levels_processed.txt similarity index 100% rename from src/RT_tables/K+9_levels_processed.txt rename to src/sunbather/RT_tables/K+9_levels_processed.txt diff --git a/src/RT_tables/K+9_lines_NIST.txt b/src/sunbather/RT_tables/K+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+9_lines_NIST.txt rename to src/sunbather/RT_tables/K+9_lines_NIST.txt diff --git a/src/RT_tables/K+_levels_NIST.txt b/src/sunbather/RT_tables/K+_levels_NIST.txt similarity index 100% rename from src/RT_tables/K+_levels_NIST.txt rename to src/sunbather/RT_tables/K+_levels_NIST.txt diff --git a/src/RT_tables/K+_levels_processed.txt b/src/sunbather/RT_tables/K+_levels_processed.txt similarity index 100% rename from src/RT_tables/K+_levels_processed.txt rename to src/sunbather/RT_tables/K+_levels_processed.txt diff --git a/src/RT_tables/K+_lines_NIST.txt b/src/sunbather/RT_tables/K+_lines_NIST.txt similarity index 100% rename from src/RT_tables/K+_lines_NIST.txt rename to src/sunbather/RT_tables/K+_lines_NIST.txt diff --git a/src/RT_tables/K_levels_NIST.txt b/src/sunbather/RT_tables/K_levels_NIST.txt similarity index 100% rename from src/RT_tables/K_levels_NIST.txt rename to src/sunbather/RT_tables/K_levels_NIST.txt diff --git a/src/RT_tables/K_levels_processed.txt b/src/sunbather/RT_tables/K_levels_processed.txt similarity index 100% rename from src/RT_tables/K_levels_processed.txt rename to src/sunbather/RT_tables/K_levels_processed.txt diff --git a/src/RT_tables/K_lines_NIST.txt b/src/sunbather/RT_tables/K_lines_NIST.txt similarity index 100% rename from src/RT_tables/K_lines_NIST.txt rename to src/sunbather/RT_tables/K_lines_NIST.txt diff --git a/src/RT_tables/Li+2_levels_NIST.txt b/src/sunbather/RT_tables/Li+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Li+2_levels_NIST.txt rename to src/sunbather/RT_tables/Li+2_levels_NIST.txt diff --git a/src/RT_tables/Li+2_levels_processed.txt b/src/sunbather/RT_tables/Li+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Li+2_levels_processed.txt rename to src/sunbather/RT_tables/Li+2_levels_processed.txt diff --git a/src/RT_tables/Li+2_lines_NIST.txt b/src/sunbather/RT_tables/Li+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Li+2_lines_NIST.txt rename to src/sunbather/RT_tables/Li+2_lines_NIST.txt diff --git a/src/RT_tables/Li+_levels_NIST.txt b/src/sunbather/RT_tables/Li+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Li+_levels_NIST.txt rename to src/sunbather/RT_tables/Li+_levels_NIST.txt diff --git a/src/RT_tables/Li+_levels_processed.txt b/src/sunbather/RT_tables/Li+_levels_processed.txt similarity index 100% rename from src/RT_tables/Li+_levels_processed.txt rename to src/sunbather/RT_tables/Li+_levels_processed.txt diff --git a/src/RT_tables/Li+_lines_NIST.txt b/src/sunbather/RT_tables/Li+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Li+_lines_NIST.txt rename to src/sunbather/RT_tables/Li+_lines_NIST.txt diff --git a/src/RT_tables/Li_levels_NIST.txt b/src/sunbather/RT_tables/Li_levels_NIST.txt similarity index 100% rename from src/RT_tables/Li_levels_NIST.txt rename to src/sunbather/RT_tables/Li_levels_NIST.txt diff --git a/src/RT_tables/Li_levels_processed.txt b/src/sunbather/RT_tables/Li_levels_processed.txt similarity index 100% rename from src/RT_tables/Li_levels_processed.txt rename to src/sunbather/RT_tables/Li_levels_processed.txt diff --git a/src/RT_tables/Li_lines_NIST.txt b/src/sunbather/RT_tables/Li_lines_NIST.txt similarity index 100% rename from src/RT_tables/Li_lines_NIST.txt rename to src/sunbather/RT_tables/Li_lines_NIST.txt diff --git a/src/RT_tables/Mg+10_levels_NIST.txt b/src/sunbather/RT_tables/Mg+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+10_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+10_levels_NIST.txt diff --git a/src/RT_tables/Mg+10_levels_processed.txt b/src/sunbather/RT_tables/Mg+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+10_levels_processed.txt rename to src/sunbather/RT_tables/Mg+10_levels_processed.txt diff --git a/src/RT_tables/Mg+10_lines_NIST.txt b/src/sunbather/RT_tables/Mg+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+10_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+10_lines_NIST.txt diff --git a/src/RT_tables/Mg+11_levels_NIST.txt b/src/sunbather/RT_tables/Mg+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+11_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+11_levels_NIST.txt diff --git a/src/RT_tables/Mg+11_levels_processed.txt b/src/sunbather/RT_tables/Mg+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+11_levels_processed.txt rename to src/sunbather/RT_tables/Mg+11_levels_processed.txt diff --git a/src/RT_tables/Mg+11_lines_NIST.txt b/src/sunbather/RT_tables/Mg+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+11_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+11_lines_NIST.txt diff --git a/src/RT_tables/Mg+2_levels_NIST.txt b/src/sunbather/RT_tables/Mg+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+2_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+2_levels_NIST.txt diff --git a/src/RT_tables/Mg+2_levels_processed.txt b/src/sunbather/RT_tables/Mg+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+2_levels_processed.txt rename to src/sunbather/RT_tables/Mg+2_levels_processed.txt diff --git a/src/RT_tables/Mg+2_lines_NIST.txt b/src/sunbather/RT_tables/Mg+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+2_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+2_lines_NIST.txt diff --git a/src/RT_tables/Mg+3_levels_NIST.txt b/src/sunbather/RT_tables/Mg+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+3_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+3_levels_NIST.txt diff --git a/src/RT_tables/Mg+3_levels_processed.txt b/src/sunbather/RT_tables/Mg+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+3_levels_processed.txt rename to src/sunbather/RT_tables/Mg+3_levels_processed.txt diff --git a/src/RT_tables/Mg+3_lines_NIST.txt b/src/sunbather/RT_tables/Mg+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+3_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+3_lines_NIST.txt diff --git a/src/RT_tables/Mg+4_levels_NIST.txt b/src/sunbather/RT_tables/Mg+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+4_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+4_levels_NIST.txt diff --git a/src/RT_tables/Mg+4_levels_processed.txt b/src/sunbather/RT_tables/Mg+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+4_levels_processed.txt rename to src/sunbather/RT_tables/Mg+4_levels_processed.txt diff --git a/src/RT_tables/Mg+4_lines_NIST.txt b/src/sunbather/RT_tables/Mg+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+4_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+4_lines_NIST.txt diff --git a/src/RT_tables/Mg+5_levels_NIST.txt b/src/sunbather/RT_tables/Mg+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+5_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+5_levels_NIST.txt diff --git a/src/RT_tables/Mg+5_levels_processed.txt b/src/sunbather/RT_tables/Mg+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+5_levels_processed.txt rename to src/sunbather/RT_tables/Mg+5_levels_processed.txt diff --git a/src/RT_tables/Mg+5_lines_NIST.txt b/src/sunbather/RT_tables/Mg+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+5_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+5_lines_NIST.txt diff --git a/src/RT_tables/Mg+6_levels_NIST.txt b/src/sunbather/RT_tables/Mg+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+6_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+6_levels_NIST.txt diff --git a/src/RT_tables/Mg+6_levels_processed.txt b/src/sunbather/RT_tables/Mg+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+6_levels_processed.txt rename to src/sunbather/RT_tables/Mg+6_levels_processed.txt diff --git a/src/RT_tables/Mg+6_lines_NIST.txt b/src/sunbather/RT_tables/Mg+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+6_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+6_lines_NIST.txt diff --git a/src/RT_tables/Mg+7_levels_NIST.txt b/src/sunbather/RT_tables/Mg+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+7_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+7_levels_NIST.txt diff --git a/src/RT_tables/Mg+7_levels_processed.txt b/src/sunbather/RT_tables/Mg+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+7_levels_processed.txt rename to src/sunbather/RT_tables/Mg+7_levels_processed.txt diff --git a/src/RT_tables/Mg+7_lines_NIST.txt b/src/sunbather/RT_tables/Mg+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+7_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+7_lines_NIST.txt diff --git a/src/RT_tables/Mg+8_levels_NIST.txt b/src/sunbather/RT_tables/Mg+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+8_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+8_levels_NIST.txt diff --git a/src/RT_tables/Mg+8_levels_processed.txt b/src/sunbather/RT_tables/Mg+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+8_levels_processed.txt rename to src/sunbather/RT_tables/Mg+8_levels_processed.txt diff --git a/src/RT_tables/Mg+8_lines_NIST.txt b/src/sunbather/RT_tables/Mg+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+8_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+8_lines_NIST.txt diff --git a/src/RT_tables/Mg+9_levels_NIST.txt b/src/sunbather/RT_tables/Mg+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+9_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+9_levels_NIST.txt diff --git a/src/RT_tables/Mg+9_levels_processed.txt b/src/sunbather/RT_tables/Mg+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+9_levels_processed.txt rename to src/sunbather/RT_tables/Mg+9_levels_processed.txt diff --git a/src/RT_tables/Mg+9_lines_NIST.txt b/src/sunbather/RT_tables/Mg+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+9_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+9_lines_NIST.txt diff --git a/src/RT_tables/Mg+_levels_NIST.txt b/src/sunbather/RT_tables/Mg+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg+_levels_NIST.txt rename to src/sunbather/RT_tables/Mg+_levels_NIST.txt diff --git a/src/RT_tables/Mg+_levels_processed.txt b/src/sunbather/RT_tables/Mg+_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg+_levels_processed.txt rename to src/sunbather/RT_tables/Mg+_levels_processed.txt diff --git a/src/RT_tables/Mg+_lines_NIST.txt b/src/sunbather/RT_tables/Mg+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg+_lines_NIST.txt rename to src/sunbather/RT_tables/Mg+_lines_NIST.txt diff --git a/src/RT_tables/Mg_levels_NIST.txt b/src/sunbather/RT_tables/Mg_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mg_levels_NIST.txt rename to src/sunbather/RT_tables/Mg_levels_NIST.txt diff --git a/src/RT_tables/Mg_levels_processed.txt b/src/sunbather/RT_tables/Mg_levels_processed.txt similarity index 100% rename from src/RT_tables/Mg_levels_processed.txt rename to src/sunbather/RT_tables/Mg_levels_processed.txt diff --git a/src/RT_tables/Mg_lines_NIST.txt b/src/sunbather/RT_tables/Mg_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mg_lines_NIST.txt rename to src/sunbather/RT_tables/Mg_lines_NIST.txt diff --git a/src/RT_tables/Mn+10_levels_NIST.txt b/src/sunbather/RT_tables/Mn+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+10_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+10_levels_NIST.txt diff --git a/src/RT_tables/Mn+10_levels_processed.txt b/src/sunbather/RT_tables/Mn+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+10_levels_processed.txt rename to src/sunbather/RT_tables/Mn+10_levels_processed.txt diff --git a/src/RT_tables/Mn+10_lines_NIST.txt b/src/sunbather/RT_tables/Mn+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+10_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+10_lines_NIST.txt diff --git a/src/RT_tables/Mn+11_levels_NIST.txt b/src/sunbather/RT_tables/Mn+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+11_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+11_levels_NIST.txt diff --git a/src/RT_tables/Mn+11_levels_processed.txt b/src/sunbather/RT_tables/Mn+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+11_levels_processed.txt rename to src/sunbather/RT_tables/Mn+11_levels_processed.txt diff --git a/src/RT_tables/Mn+11_lines_NIST.txt b/src/sunbather/RT_tables/Mn+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+11_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+11_lines_NIST.txt diff --git a/src/RT_tables/Mn+12_levels_NIST.txt b/src/sunbather/RT_tables/Mn+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+12_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+12_levels_NIST.txt diff --git a/src/RT_tables/Mn+12_levels_processed.txt b/src/sunbather/RT_tables/Mn+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+12_levels_processed.txt rename to src/sunbather/RT_tables/Mn+12_levels_processed.txt diff --git a/src/RT_tables/Mn+12_lines_NIST.txt b/src/sunbather/RT_tables/Mn+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+12_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+12_lines_NIST.txt diff --git a/src/RT_tables/Mn+2_levels_NIST.txt b/src/sunbather/RT_tables/Mn+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+2_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+2_levels_NIST.txt diff --git a/src/RT_tables/Mn+2_lines_NIST.txt b/src/sunbather/RT_tables/Mn+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+2_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+2_lines_NIST.txt diff --git a/src/RT_tables/Mn+3_levels_NIST.txt b/src/sunbather/RT_tables/Mn+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+3_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+3_levels_NIST.txt diff --git a/src/RT_tables/Mn+3_lines_NIST.txt b/src/sunbather/RT_tables/Mn+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+3_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+3_lines_NIST.txt diff --git a/src/RT_tables/Mn+4_levels_NIST.txt b/src/sunbather/RT_tables/Mn+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+4_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+4_levels_NIST.txt diff --git a/src/RT_tables/Mn+4_levels_processed.txt b/src/sunbather/RT_tables/Mn+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+4_levels_processed.txt rename to src/sunbather/RT_tables/Mn+4_levels_processed.txt diff --git a/src/RT_tables/Mn+4_lines_NIST.txt b/src/sunbather/RT_tables/Mn+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+4_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+4_lines_NIST.txt diff --git a/src/RT_tables/Mn+5_levels_NIST.txt b/src/sunbather/RT_tables/Mn+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+5_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+5_levels_NIST.txt diff --git a/src/RT_tables/Mn+5_lines_NIST.txt b/src/sunbather/RT_tables/Mn+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+5_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+5_lines_NIST.txt diff --git a/src/RT_tables/Mn+6_levels_NIST.txt b/src/sunbather/RT_tables/Mn+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+6_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+6_levels_NIST.txt diff --git a/src/RT_tables/Mn+6_lines_NIST.txt b/src/sunbather/RT_tables/Mn+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+6_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+6_lines_NIST.txt diff --git a/src/RT_tables/Mn+7_levels_NIST.txt b/src/sunbather/RT_tables/Mn+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+7_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+7_levels_NIST.txt diff --git a/src/RT_tables/Mn+7_levels_processed.txt b/src/sunbather/RT_tables/Mn+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+7_levels_processed.txt rename to src/sunbather/RT_tables/Mn+7_levels_processed.txt diff --git a/src/RT_tables/Mn+7_lines_NIST.txt b/src/sunbather/RT_tables/Mn+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+7_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+7_lines_NIST.txt diff --git a/src/RT_tables/Mn+8_levels_NIST.txt b/src/sunbather/RT_tables/Mn+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+8_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+8_levels_NIST.txt diff --git a/src/RT_tables/Mn+8_levels_processed.txt b/src/sunbather/RT_tables/Mn+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+8_levels_processed.txt rename to src/sunbather/RT_tables/Mn+8_levels_processed.txt diff --git a/src/RT_tables/Mn+8_lines_NIST.txt b/src/sunbather/RT_tables/Mn+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+8_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+8_lines_NIST.txt diff --git a/src/RT_tables/Mn+9_levels_NIST.txt b/src/sunbather/RT_tables/Mn+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+9_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+9_levels_NIST.txt diff --git a/src/RT_tables/Mn+9_levels_processed.txt b/src/sunbather/RT_tables/Mn+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+9_levels_processed.txt rename to src/sunbather/RT_tables/Mn+9_levels_processed.txt diff --git a/src/RT_tables/Mn+9_lines_NIST.txt b/src/sunbather/RT_tables/Mn+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+9_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+9_lines_NIST.txt diff --git a/src/RT_tables/Mn+_levels_NIST.txt b/src/sunbather/RT_tables/Mn+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn+_levels_NIST.txt rename to src/sunbather/RT_tables/Mn+_levels_NIST.txt diff --git a/src/RT_tables/Mn+_levels_processed.txt b/src/sunbather/RT_tables/Mn+_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn+_levels_processed.txt rename to src/sunbather/RT_tables/Mn+_levels_processed.txt diff --git a/src/RT_tables/Mn+_lines_NIST.txt b/src/sunbather/RT_tables/Mn+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn+_lines_NIST.txt rename to src/sunbather/RT_tables/Mn+_lines_NIST.txt diff --git a/src/RT_tables/Mn_levels_NIST.txt b/src/sunbather/RT_tables/Mn_levels_NIST.txt similarity index 100% rename from src/RT_tables/Mn_levels_NIST.txt rename to src/sunbather/RT_tables/Mn_levels_NIST.txt diff --git a/src/RT_tables/Mn_levels_processed.txt b/src/sunbather/RT_tables/Mn_levels_processed.txt similarity index 100% rename from src/RT_tables/Mn_levels_processed.txt rename to src/sunbather/RT_tables/Mn_levels_processed.txt diff --git a/src/RT_tables/Mn_lines_NIST.txt b/src/sunbather/RT_tables/Mn_lines_NIST.txt similarity index 100% rename from src/RT_tables/Mn_lines_NIST.txt rename to src/sunbather/RT_tables/Mn_lines_NIST.txt diff --git a/src/RT_tables/N+2_levels_NIST.txt b/src/sunbather/RT_tables/N+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/N+2_levels_NIST.txt rename to src/sunbather/RT_tables/N+2_levels_NIST.txt diff --git a/src/RT_tables/N+2_levels_processed.txt b/src/sunbather/RT_tables/N+2_levels_processed.txt similarity index 100% rename from src/RT_tables/N+2_levels_processed.txt rename to src/sunbather/RT_tables/N+2_levels_processed.txt diff --git a/src/RT_tables/N+2_lines_NIST.txt b/src/sunbather/RT_tables/N+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/N+2_lines_NIST.txt rename to src/sunbather/RT_tables/N+2_lines_NIST.txt diff --git a/src/RT_tables/N+3_levels_NIST.txt b/src/sunbather/RT_tables/N+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/N+3_levels_NIST.txt rename to src/sunbather/RT_tables/N+3_levels_NIST.txt diff --git a/src/RT_tables/N+3_levels_processed.txt b/src/sunbather/RT_tables/N+3_levels_processed.txt similarity index 100% rename from src/RT_tables/N+3_levels_processed.txt rename to src/sunbather/RT_tables/N+3_levels_processed.txt diff --git a/src/RT_tables/N+3_lines_NIST.txt b/src/sunbather/RT_tables/N+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/N+3_lines_NIST.txt rename to src/sunbather/RT_tables/N+3_lines_NIST.txt diff --git a/src/RT_tables/N+4_levels_NIST.txt b/src/sunbather/RT_tables/N+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/N+4_levels_NIST.txt rename to src/sunbather/RT_tables/N+4_levels_NIST.txt diff --git a/src/RT_tables/N+4_levels_processed.txt b/src/sunbather/RT_tables/N+4_levels_processed.txt similarity index 100% rename from src/RT_tables/N+4_levels_processed.txt rename to src/sunbather/RT_tables/N+4_levels_processed.txt diff --git a/src/RT_tables/N+4_lines_NIST.txt b/src/sunbather/RT_tables/N+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/N+4_lines_NIST.txt rename to src/sunbather/RT_tables/N+4_lines_NIST.txt diff --git a/src/RT_tables/N+5_levels_NIST.txt b/src/sunbather/RT_tables/N+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/N+5_levels_NIST.txt rename to src/sunbather/RT_tables/N+5_levels_NIST.txt diff --git a/src/RT_tables/N+5_levels_processed.txt b/src/sunbather/RT_tables/N+5_levels_processed.txt similarity index 100% rename from src/RT_tables/N+5_levels_processed.txt rename to src/sunbather/RT_tables/N+5_levels_processed.txt diff --git a/src/RT_tables/N+5_lines_NIST.txt b/src/sunbather/RT_tables/N+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/N+5_lines_NIST.txt rename to src/sunbather/RT_tables/N+5_lines_NIST.txt diff --git a/src/RT_tables/N+6_levels_NIST.txt b/src/sunbather/RT_tables/N+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/N+6_levels_NIST.txt rename to src/sunbather/RT_tables/N+6_levels_NIST.txt diff --git a/src/RT_tables/N+6_levels_processed.txt b/src/sunbather/RT_tables/N+6_levels_processed.txt similarity index 100% rename from src/RT_tables/N+6_levels_processed.txt rename to src/sunbather/RT_tables/N+6_levels_processed.txt diff --git a/src/RT_tables/N+6_lines_NIST.txt b/src/sunbather/RT_tables/N+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/N+6_lines_NIST.txt rename to src/sunbather/RT_tables/N+6_lines_NIST.txt diff --git a/src/RT_tables/N+_levels_NIST.txt b/src/sunbather/RT_tables/N+_levels_NIST.txt similarity index 100% rename from src/RT_tables/N+_levels_NIST.txt rename to src/sunbather/RT_tables/N+_levels_NIST.txt diff --git a/src/RT_tables/N+_levels_processed.txt b/src/sunbather/RT_tables/N+_levels_processed.txt similarity index 100% rename from src/RT_tables/N+_levels_processed.txt rename to src/sunbather/RT_tables/N+_levels_processed.txt diff --git a/src/RT_tables/N+_lines_NIST.txt b/src/sunbather/RT_tables/N+_lines_NIST.txt similarity index 100% rename from src/RT_tables/N+_lines_NIST.txt rename to src/sunbather/RT_tables/N+_lines_NIST.txt diff --git a/src/RT_tables/N_levels_NIST.txt b/src/sunbather/RT_tables/N_levels_NIST.txt similarity index 100% rename from src/RT_tables/N_levels_NIST.txt rename to src/sunbather/RT_tables/N_levels_NIST.txt diff --git a/src/RT_tables/N_levels_processed.txt b/src/sunbather/RT_tables/N_levels_processed.txt similarity index 100% rename from src/RT_tables/N_levels_processed.txt rename to src/sunbather/RT_tables/N_levels_processed.txt diff --git a/src/RT_tables/N_lines_NIST.txt b/src/sunbather/RT_tables/N_lines_NIST.txt similarity index 100% rename from src/RT_tables/N_lines_NIST.txt rename to src/sunbather/RT_tables/N_lines_NIST.txt diff --git a/src/RT_tables/Na+10_levels_NIST.txt b/src/sunbather/RT_tables/Na+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+10_levels_NIST.txt rename to src/sunbather/RT_tables/Na+10_levels_NIST.txt diff --git a/src/RT_tables/Na+10_levels_processed.txt b/src/sunbather/RT_tables/Na+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+10_levels_processed.txt rename to src/sunbather/RT_tables/Na+10_levels_processed.txt diff --git a/src/RT_tables/Na+10_lines_NIST.txt b/src/sunbather/RT_tables/Na+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+10_lines_NIST.txt rename to src/sunbather/RT_tables/Na+10_lines_NIST.txt diff --git a/src/RT_tables/Na+2_levels_NIST.txt b/src/sunbather/RT_tables/Na+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+2_levels_NIST.txt rename to src/sunbather/RT_tables/Na+2_levels_NIST.txt diff --git a/src/RT_tables/Na+2_levels_processed.txt b/src/sunbather/RT_tables/Na+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+2_levels_processed.txt rename to src/sunbather/RT_tables/Na+2_levels_processed.txt diff --git a/src/RT_tables/Na+2_lines_NIST.txt b/src/sunbather/RT_tables/Na+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+2_lines_NIST.txt rename to src/sunbather/RT_tables/Na+2_lines_NIST.txt diff --git a/src/RT_tables/Na+3_levels_NIST.txt b/src/sunbather/RT_tables/Na+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+3_levels_NIST.txt rename to src/sunbather/RT_tables/Na+3_levels_NIST.txt diff --git a/src/RT_tables/Na+3_levels_processed.txt b/src/sunbather/RT_tables/Na+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+3_levels_processed.txt rename to src/sunbather/RT_tables/Na+3_levels_processed.txt diff --git a/src/RT_tables/Na+3_lines_NIST.txt b/src/sunbather/RT_tables/Na+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+3_lines_NIST.txt rename to src/sunbather/RT_tables/Na+3_lines_NIST.txt diff --git a/src/RT_tables/Na+4_levels_NIST.txt b/src/sunbather/RT_tables/Na+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+4_levels_NIST.txt rename to src/sunbather/RT_tables/Na+4_levels_NIST.txt diff --git a/src/RT_tables/Na+4_levels_processed.txt b/src/sunbather/RT_tables/Na+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+4_levels_processed.txt rename to src/sunbather/RT_tables/Na+4_levels_processed.txt diff --git a/src/RT_tables/Na+4_lines_NIST.txt b/src/sunbather/RT_tables/Na+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+4_lines_NIST.txt rename to src/sunbather/RT_tables/Na+4_lines_NIST.txt diff --git a/src/RT_tables/Na+5_levels_NIST.txt b/src/sunbather/RT_tables/Na+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+5_levels_NIST.txt rename to src/sunbather/RT_tables/Na+5_levels_NIST.txt diff --git a/src/RT_tables/Na+5_levels_processed.txt b/src/sunbather/RT_tables/Na+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+5_levels_processed.txt rename to src/sunbather/RT_tables/Na+5_levels_processed.txt diff --git a/src/RT_tables/Na+5_lines_NIST.txt b/src/sunbather/RT_tables/Na+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+5_lines_NIST.txt rename to src/sunbather/RT_tables/Na+5_lines_NIST.txt diff --git a/src/RT_tables/Na+6_levels_NIST.txt b/src/sunbather/RT_tables/Na+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+6_levels_NIST.txt rename to src/sunbather/RT_tables/Na+6_levels_NIST.txt diff --git a/src/RT_tables/Na+6_levels_processed.txt b/src/sunbather/RT_tables/Na+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+6_levels_processed.txt rename to src/sunbather/RT_tables/Na+6_levels_processed.txt diff --git a/src/RT_tables/Na+6_lines_NIST.txt b/src/sunbather/RT_tables/Na+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+6_lines_NIST.txt rename to src/sunbather/RT_tables/Na+6_lines_NIST.txt diff --git a/src/RT_tables/Na+7_levels_NIST.txt b/src/sunbather/RT_tables/Na+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+7_levels_NIST.txt rename to src/sunbather/RT_tables/Na+7_levels_NIST.txt diff --git a/src/RT_tables/Na+7_levels_processed.txt b/src/sunbather/RT_tables/Na+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+7_levels_processed.txt rename to src/sunbather/RT_tables/Na+7_levels_processed.txt diff --git a/src/RT_tables/Na+7_lines_NIST.txt b/src/sunbather/RT_tables/Na+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+7_lines_NIST.txt rename to src/sunbather/RT_tables/Na+7_lines_NIST.txt diff --git a/src/RT_tables/Na+8_levels_NIST.txt b/src/sunbather/RT_tables/Na+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+8_levels_NIST.txt rename to src/sunbather/RT_tables/Na+8_levels_NIST.txt diff --git a/src/RT_tables/Na+8_levels_processed.txt b/src/sunbather/RT_tables/Na+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+8_levels_processed.txt rename to src/sunbather/RT_tables/Na+8_levels_processed.txt diff --git a/src/RT_tables/Na+8_lines_NIST.txt b/src/sunbather/RT_tables/Na+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+8_lines_NIST.txt rename to src/sunbather/RT_tables/Na+8_lines_NIST.txt diff --git a/src/RT_tables/Na+9_levels_NIST.txt b/src/sunbather/RT_tables/Na+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+9_levels_NIST.txt rename to src/sunbather/RT_tables/Na+9_levels_NIST.txt diff --git a/src/RT_tables/Na+9_levels_processed.txt b/src/sunbather/RT_tables/Na+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+9_levels_processed.txt rename to src/sunbather/RT_tables/Na+9_levels_processed.txt diff --git a/src/RT_tables/Na+9_lines_NIST.txt b/src/sunbather/RT_tables/Na+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+9_lines_NIST.txt rename to src/sunbather/RT_tables/Na+9_lines_NIST.txt diff --git a/src/RT_tables/Na+_levels_NIST.txt b/src/sunbather/RT_tables/Na+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na+_levels_NIST.txt rename to src/sunbather/RT_tables/Na+_levels_NIST.txt diff --git a/src/RT_tables/Na+_levels_processed.txt b/src/sunbather/RT_tables/Na+_levels_processed.txt similarity index 100% rename from src/RT_tables/Na+_levels_processed.txt rename to src/sunbather/RT_tables/Na+_levels_processed.txt diff --git a/src/RT_tables/Na+_lines_NIST.txt b/src/sunbather/RT_tables/Na+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na+_lines_NIST.txt rename to src/sunbather/RT_tables/Na+_lines_NIST.txt diff --git a/src/RT_tables/Na_levels_NIST.txt b/src/sunbather/RT_tables/Na_levels_NIST.txt similarity index 100% rename from src/RT_tables/Na_levels_NIST.txt rename to src/sunbather/RT_tables/Na_levels_NIST.txt diff --git a/src/RT_tables/Na_levels_processed.txt b/src/sunbather/RT_tables/Na_levels_processed.txt similarity index 100% rename from src/RT_tables/Na_levels_processed.txt rename to src/sunbather/RT_tables/Na_levels_processed.txt diff --git a/src/RT_tables/Na_lines_NIST.txt b/src/sunbather/RT_tables/Na_lines_NIST.txt similarity index 100% rename from src/RT_tables/Na_lines_NIST.txt rename to src/sunbather/RT_tables/Na_lines_NIST.txt diff --git a/src/RT_tables/Ne+2_levels_NIST.txt b/src/sunbather/RT_tables/Ne+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+2_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+2_levels_NIST.txt diff --git a/src/RT_tables/Ne+2_levels_processed.txt b/src/sunbather/RT_tables/Ne+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+2_levels_processed.txt rename to src/sunbather/RT_tables/Ne+2_levels_processed.txt diff --git a/src/RT_tables/Ne+2_lines_NIST.txt b/src/sunbather/RT_tables/Ne+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+2_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+2_lines_NIST.txt diff --git a/src/RT_tables/Ne+3_levels_NIST.txt b/src/sunbather/RT_tables/Ne+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+3_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+3_levels_NIST.txt diff --git a/src/RT_tables/Ne+3_levels_processed.txt b/src/sunbather/RT_tables/Ne+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+3_levels_processed.txt rename to src/sunbather/RT_tables/Ne+3_levels_processed.txt diff --git a/src/RT_tables/Ne+3_lines_NIST.txt b/src/sunbather/RT_tables/Ne+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+3_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+3_lines_NIST.txt diff --git a/src/RT_tables/Ne+4_levels_NIST.txt b/src/sunbather/RT_tables/Ne+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+4_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+4_levels_NIST.txt diff --git a/src/RT_tables/Ne+4_levels_processed.txt b/src/sunbather/RT_tables/Ne+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+4_levels_processed.txt rename to src/sunbather/RT_tables/Ne+4_levels_processed.txt diff --git a/src/RT_tables/Ne+4_lines_NIST.txt b/src/sunbather/RT_tables/Ne+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+4_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+4_lines_NIST.txt diff --git a/src/RT_tables/Ne+5_levels_NIST.txt b/src/sunbather/RT_tables/Ne+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+5_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+5_levels_NIST.txt diff --git a/src/RT_tables/Ne+5_levels_processed.txt b/src/sunbather/RT_tables/Ne+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+5_levels_processed.txt rename to src/sunbather/RT_tables/Ne+5_levels_processed.txt diff --git a/src/RT_tables/Ne+5_lines_NIST.txt b/src/sunbather/RT_tables/Ne+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+5_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+5_lines_NIST.txt diff --git a/src/RT_tables/Ne+6_levels_NIST.txt b/src/sunbather/RT_tables/Ne+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+6_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+6_levels_NIST.txt diff --git a/src/RT_tables/Ne+6_levels_processed.txt b/src/sunbather/RT_tables/Ne+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+6_levels_processed.txt rename to src/sunbather/RT_tables/Ne+6_levels_processed.txt diff --git a/src/RT_tables/Ne+6_lines_NIST.txt b/src/sunbather/RT_tables/Ne+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+6_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+6_lines_NIST.txt diff --git a/src/RT_tables/Ne+7_levels_NIST.txt b/src/sunbather/RT_tables/Ne+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+7_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+7_levels_NIST.txt diff --git a/src/RT_tables/Ne+7_levels_processed.txt b/src/sunbather/RT_tables/Ne+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+7_levels_processed.txt rename to src/sunbather/RT_tables/Ne+7_levels_processed.txt diff --git a/src/RT_tables/Ne+7_lines_NIST.txt b/src/sunbather/RT_tables/Ne+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+7_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+7_lines_NIST.txt diff --git a/src/RT_tables/Ne+8_levels_NIST.txt b/src/sunbather/RT_tables/Ne+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+8_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+8_levels_NIST.txt diff --git a/src/RT_tables/Ne+8_levels_processed.txt b/src/sunbather/RT_tables/Ne+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+8_levels_processed.txt rename to src/sunbather/RT_tables/Ne+8_levels_processed.txt diff --git a/src/RT_tables/Ne+8_lines_NIST.txt b/src/sunbather/RT_tables/Ne+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+8_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+8_lines_NIST.txt diff --git a/src/RT_tables/Ne+9_levels_NIST.txt b/src/sunbather/RT_tables/Ne+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+9_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+9_levels_NIST.txt diff --git a/src/RT_tables/Ne+9_levels_processed.txt b/src/sunbather/RT_tables/Ne+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+9_levels_processed.txt rename to src/sunbather/RT_tables/Ne+9_levels_processed.txt diff --git a/src/RT_tables/Ne+9_lines_NIST.txt b/src/sunbather/RT_tables/Ne+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+9_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+9_lines_NIST.txt diff --git a/src/RT_tables/Ne+_levels_NIST.txt b/src/sunbather/RT_tables/Ne+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne+_levels_NIST.txt rename to src/sunbather/RT_tables/Ne+_levels_NIST.txt diff --git a/src/RT_tables/Ne+_levels_processed.txt b/src/sunbather/RT_tables/Ne+_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne+_levels_processed.txt rename to src/sunbather/RT_tables/Ne+_levels_processed.txt diff --git a/src/RT_tables/Ne+_lines_NIST.txt b/src/sunbather/RT_tables/Ne+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne+_lines_NIST.txt rename to src/sunbather/RT_tables/Ne+_lines_NIST.txt diff --git a/src/RT_tables/Ne_levels_NIST.txt b/src/sunbather/RT_tables/Ne_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ne_levels_NIST.txt rename to src/sunbather/RT_tables/Ne_levels_NIST.txt diff --git a/src/RT_tables/Ne_levels_processed.txt b/src/sunbather/RT_tables/Ne_levels_processed.txt similarity index 100% rename from src/RT_tables/Ne_levels_processed.txt rename to src/sunbather/RT_tables/Ne_levels_processed.txt diff --git a/src/RT_tables/Ne_lines_NIST.txt b/src/sunbather/RT_tables/Ne_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ne_lines_NIST.txt rename to src/sunbather/RT_tables/Ne_lines_NIST.txt diff --git a/src/RT_tables/Ni+10_levels_NIST.txt b/src/sunbather/RT_tables/Ni+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+10_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+10_levels_NIST.txt diff --git a/src/RT_tables/Ni+10_levels_processed.txt b/src/sunbather/RT_tables/Ni+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+10_levels_processed.txt rename to src/sunbather/RT_tables/Ni+10_levels_processed.txt diff --git a/src/RT_tables/Ni+10_lines_NIST.txt b/src/sunbather/RT_tables/Ni+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+10_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+10_lines_NIST.txt diff --git a/src/RT_tables/Ni+11_levels_NIST.txt b/src/sunbather/RT_tables/Ni+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+11_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+11_levels_NIST.txt diff --git a/src/RT_tables/Ni+11_levels_processed.txt b/src/sunbather/RT_tables/Ni+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+11_levels_processed.txt rename to src/sunbather/RT_tables/Ni+11_levels_processed.txt diff --git a/src/RT_tables/Ni+11_lines_NIST.txt b/src/sunbather/RT_tables/Ni+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+11_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+11_lines_NIST.txt diff --git a/src/RT_tables/Ni+12_levels_NIST.txt b/src/sunbather/RT_tables/Ni+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+12_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+12_levels_NIST.txt diff --git a/src/RT_tables/Ni+12_levels_processed.txt b/src/sunbather/RT_tables/Ni+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+12_levels_processed.txt rename to src/sunbather/RT_tables/Ni+12_levels_processed.txt diff --git a/src/RT_tables/Ni+12_lines_NIST.txt b/src/sunbather/RT_tables/Ni+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+12_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+12_lines_NIST.txt diff --git a/src/RT_tables/Ni+2_levels_NIST.txt b/src/sunbather/RT_tables/Ni+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+2_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+2_levels_NIST.txt diff --git a/src/RT_tables/Ni+2_levels_processed.txt b/src/sunbather/RT_tables/Ni+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+2_levels_processed.txt rename to src/sunbather/RT_tables/Ni+2_levels_processed.txt diff --git a/src/RT_tables/Ni+2_lines_NIST.txt b/src/sunbather/RT_tables/Ni+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+2_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+2_lines_NIST.txt diff --git a/src/RT_tables/Ni+3_levels_NIST.txt b/src/sunbather/RT_tables/Ni+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+3_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+3_levels_NIST.txt diff --git a/src/RT_tables/Ni+3_levels_processed.txt b/src/sunbather/RT_tables/Ni+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+3_levels_processed.txt rename to src/sunbather/RT_tables/Ni+3_levels_processed.txt diff --git a/src/RT_tables/Ni+3_lines_NIST.txt b/src/sunbather/RT_tables/Ni+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+3_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+3_lines_NIST.txt diff --git a/src/RT_tables/Ni+4_levels_NIST.txt b/src/sunbather/RT_tables/Ni+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+4_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+4_levels_NIST.txt diff --git a/src/RT_tables/Ni+4_levels_processed.txt b/src/sunbather/RT_tables/Ni+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+4_levels_processed.txt rename to src/sunbather/RT_tables/Ni+4_levels_processed.txt diff --git a/src/RT_tables/Ni+4_lines_NIST.txt b/src/sunbather/RT_tables/Ni+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+4_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+4_lines_NIST.txt diff --git a/src/RT_tables/Ni+5_levels_NIST.txt b/src/sunbather/RT_tables/Ni+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+5_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+5_levels_NIST.txt diff --git a/src/RT_tables/Ni+5_lines_NIST.txt b/src/sunbather/RT_tables/Ni+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+5_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+5_lines_NIST.txt diff --git a/src/RT_tables/Ni+6_levels_NIST.txt b/src/sunbather/RT_tables/Ni+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+6_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+6_levels_NIST.txt diff --git a/src/RT_tables/Ni+6_levels_processed.txt b/src/sunbather/RT_tables/Ni+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+6_levels_processed.txt rename to src/sunbather/RT_tables/Ni+6_levels_processed.txt diff --git a/src/RT_tables/Ni+6_lines_NIST.txt b/src/sunbather/RT_tables/Ni+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+6_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+6_lines_NIST.txt diff --git a/src/RT_tables/Ni+7_levels_NIST.txt b/src/sunbather/RT_tables/Ni+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+7_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+7_levels_NIST.txt diff --git a/src/RT_tables/Ni+7_levels_processed.txt b/src/sunbather/RT_tables/Ni+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+7_levels_processed.txt rename to src/sunbather/RT_tables/Ni+7_levels_processed.txt diff --git a/src/RT_tables/Ni+7_lines_NIST.txt b/src/sunbather/RT_tables/Ni+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+7_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+7_lines_NIST.txt diff --git a/src/RT_tables/Ni+8_levels_NIST.txt b/src/sunbather/RT_tables/Ni+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+8_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+8_levels_NIST.txt diff --git a/src/RT_tables/Ni+8_levels_processed.txt b/src/sunbather/RT_tables/Ni+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+8_levels_processed.txt rename to src/sunbather/RT_tables/Ni+8_levels_processed.txt diff --git a/src/RT_tables/Ni+8_lines_NIST.txt b/src/sunbather/RT_tables/Ni+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+8_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+8_lines_NIST.txt diff --git a/src/RT_tables/Ni+9_levels_NIST.txt b/src/sunbather/RT_tables/Ni+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+9_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+9_levels_NIST.txt diff --git a/src/RT_tables/Ni+9_levels_processed.txt b/src/sunbather/RT_tables/Ni+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+9_levels_processed.txt rename to src/sunbather/RT_tables/Ni+9_levels_processed.txt diff --git a/src/RT_tables/Ni+9_lines_NIST.txt b/src/sunbather/RT_tables/Ni+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+9_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+9_lines_NIST.txt diff --git a/src/RT_tables/Ni+_levels_NIST.txt b/src/sunbather/RT_tables/Ni+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni+_levels_NIST.txt rename to src/sunbather/RT_tables/Ni+_levels_NIST.txt diff --git a/src/RT_tables/Ni+_levels_processed.txt b/src/sunbather/RT_tables/Ni+_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni+_levels_processed.txt rename to src/sunbather/RT_tables/Ni+_levels_processed.txt diff --git a/src/RT_tables/Ni+_lines_NIST.txt b/src/sunbather/RT_tables/Ni+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni+_lines_NIST.txt rename to src/sunbather/RT_tables/Ni+_lines_NIST.txt diff --git a/src/RT_tables/Ni_levels_NIST.txt b/src/sunbather/RT_tables/Ni_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ni_levels_NIST.txt rename to src/sunbather/RT_tables/Ni_levels_NIST.txt diff --git a/src/RT_tables/Ni_levels_processed.txt b/src/sunbather/RT_tables/Ni_levels_processed.txt similarity index 100% rename from src/RT_tables/Ni_levels_processed.txt rename to src/sunbather/RT_tables/Ni_levels_processed.txt diff --git a/src/RT_tables/Ni_lines_NIST.txt b/src/sunbather/RT_tables/Ni_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ni_lines_NIST.txt rename to src/sunbather/RT_tables/Ni_lines_NIST.txt diff --git a/src/RT_tables/O+2_levels_NIST.txt b/src/sunbather/RT_tables/O+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+2_levels_NIST.txt rename to src/sunbather/RT_tables/O+2_levels_NIST.txt diff --git a/src/RT_tables/O+2_levels_processed.txt b/src/sunbather/RT_tables/O+2_levels_processed.txt similarity index 100% rename from src/RT_tables/O+2_levels_processed.txt rename to src/sunbather/RT_tables/O+2_levels_processed.txt diff --git a/src/RT_tables/O+2_lines_NIST.txt b/src/sunbather/RT_tables/O+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+2_lines_NIST.txt rename to src/sunbather/RT_tables/O+2_lines_NIST.txt diff --git a/src/RT_tables/O+3_levels_NIST.txt b/src/sunbather/RT_tables/O+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+3_levels_NIST.txt rename to src/sunbather/RT_tables/O+3_levels_NIST.txt diff --git a/src/RT_tables/O+3_levels_processed.txt b/src/sunbather/RT_tables/O+3_levels_processed.txt similarity index 100% rename from src/RT_tables/O+3_levels_processed.txt rename to src/sunbather/RT_tables/O+3_levels_processed.txt diff --git a/src/RT_tables/O+3_lines_NIST.txt b/src/sunbather/RT_tables/O+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+3_lines_NIST.txt rename to src/sunbather/RT_tables/O+3_lines_NIST.txt diff --git a/src/RT_tables/O+4_levels_NIST.txt b/src/sunbather/RT_tables/O+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+4_levels_NIST.txt rename to src/sunbather/RT_tables/O+4_levels_NIST.txt diff --git a/src/RT_tables/O+4_levels_processed.txt b/src/sunbather/RT_tables/O+4_levels_processed.txt similarity index 100% rename from src/RT_tables/O+4_levels_processed.txt rename to src/sunbather/RT_tables/O+4_levels_processed.txt diff --git a/src/RT_tables/O+4_lines_NIST.txt b/src/sunbather/RT_tables/O+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+4_lines_NIST.txt rename to src/sunbather/RT_tables/O+4_lines_NIST.txt diff --git a/src/RT_tables/O+5_levels_NIST.txt b/src/sunbather/RT_tables/O+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+5_levels_NIST.txt rename to src/sunbather/RT_tables/O+5_levels_NIST.txt diff --git a/src/RT_tables/O+5_levels_processed.txt b/src/sunbather/RT_tables/O+5_levels_processed.txt similarity index 100% rename from src/RT_tables/O+5_levels_processed.txt rename to src/sunbather/RT_tables/O+5_levels_processed.txt diff --git a/src/RT_tables/O+5_lines_NIST.txt b/src/sunbather/RT_tables/O+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+5_lines_NIST.txt rename to src/sunbather/RT_tables/O+5_lines_NIST.txt diff --git a/src/RT_tables/O+6_levels_NIST.txt b/src/sunbather/RT_tables/O+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+6_levels_NIST.txt rename to src/sunbather/RT_tables/O+6_levels_NIST.txt diff --git a/src/RT_tables/O+6_levels_processed.txt b/src/sunbather/RT_tables/O+6_levels_processed.txt similarity index 100% rename from src/RT_tables/O+6_levels_processed.txt rename to src/sunbather/RT_tables/O+6_levels_processed.txt diff --git a/src/RT_tables/O+6_lines_NIST.txt b/src/sunbather/RT_tables/O+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+6_lines_NIST.txt rename to src/sunbather/RT_tables/O+6_lines_NIST.txt diff --git a/src/RT_tables/O+7_levels_NIST.txt b/src/sunbather/RT_tables/O+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+7_levels_NIST.txt rename to src/sunbather/RT_tables/O+7_levels_NIST.txt diff --git a/src/RT_tables/O+7_levels_processed.txt b/src/sunbather/RT_tables/O+7_levels_processed.txt similarity index 100% rename from src/RT_tables/O+7_levels_processed.txt rename to src/sunbather/RT_tables/O+7_levels_processed.txt diff --git a/src/RT_tables/O+7_lines_NIST.txt b/src/sunbather/RT_tables/O+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+7_lines_NIST.txt rename to src/sunbather/RT_tables/O+7_lines_NIST.txt diff --git a/src/RT_tables/O+_levels_NIST.txt b/src/sunbather/RT_tables/O+_levels_NIST.txt similarity index 100% rename from src/RT_tables/O+_levels_NIST.txt rename to src/sunbather/RT_tables/O+_levels_NIST.txt diff --git a/src/RT_tables/O+_levels_processed.txt b/src/sunbather/RT_tables/O+_levels_processed.txt similarity index 100% rename from src/RT_tables/O+_levels_processed.txt rename to src/sunbather/RT_tables/O+_levels_processed.txt diff --git a/src/RT_tables/O+_lines_NIST.txt b/src/sunbather/RT_tables/O+_lines_NIST.txt similarity index 100% rename from src/RT_tables/O+_lines_NIST.txt rename to src/sunbather/RT_tables/O+_lines_NIST.txt diff --git a/src/RT_tables/O_levels_NIST.txt b/src/sunbather/RT_tables/O_levels_NIST.txt similarity index 100% rename from src/RT_tables/O_levels_NIST.txt rename to src/sunbather/RT_tables/O_levels_NIST.txt diff --git a/src/RT_tables/O_levels_processed.txt b/src/sunbather/RT_tables/O_levels_processed.txt similarity index 100% rename from src/RT_tables/O_levels_processed.txt rename to src/sunbather/RT_tables/O_levels_processed.txt diff --git a/src/RT_tables/O_lines_NIST.txt b/src/sunbather/RT_tables/O_lines_NIST.txt similarity index 100% rename from src/RT_tables/O_lines_NIST.txt rename to src/sunbather/RT_tables/O_lines_NIST.txt diff --git a/src/RT_tables/P+10_levels_NIST.txt b/src/sunbather/RT_tables/P+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+10_levels_NIST.txt rename to src/sunbather/RT_tables/P+10_levels_NIST.txt diff --git a/src/RT_tables/P+10_levels_processed.txt b/src/sunbather/RT_tables/P+10_levels_processed.txt similarity index 100% rename from src/RT_tables/P+10_levels_processed.txt rename to src/sunbather/RT_tables/P+10_levels_processed.txt diff --git a/src/RT_tables/P+10_lines_NIST.txt b/src/sunbather/RT_tables/P+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+10_lines_NIST.txt rename to src/sunbather/RT_tables/P+10_lines_NIST.txt diff --git a/src/RT_tables/P+11_levels_NIST.txt b/src/sunbather/RT_tables/P+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+11_levels_NIST.txt rename to src/sunbather/RT_tables/P+11_levels_NIST.txt diff --git a/src/RT_tables/P+11_levels_processed.txt b/src/sunbather/RT_tables/P+11_levels_processed.txt similarity index 100% rename from src/RT_tables/P+11_levels_processed.txt rename to src/sunbather/RT_tables/P+11_levels_processed.txt diff --git a/src/RT_tables/P+11_lines_NIST.txt b/src/sunbather/RT_tables/P+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+11_lines_NIST.txt rename to src/sunbather/RT_tables/P+11_lines_NIST.txt diff --git a/src/RT_tables/P+12_levels_NIST.txt b/src/sunbather/RT_tables/P+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+12_levels_NIST.txt rename to src/sunbather/RT_tables/P+12_levels_NIST.txt diff --git a/src/RT_tables/P+12_levels_processed.txt b/src/sunbather/RT_tables/P+12_levels_processed.txt similarity index 100% rename from src/RT_tables/P+12_levels_processed.txt rename to src/sunbather/RT_tables/P+12_levels_processed.txt diff --git a/src/RT_tables/P+12_lines_NIST.txt b/src/sunbather/RT_tables/P+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+12_lines_NIST.txt rename to src/sunbather/RT_tables/P+12_lines_NIST.txt diff --git a/src/RT_tables/P+2_levels_NIST.txt b/src/sunbather/RT_tables/P+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+2_levels_NIST.txt rename to src/sunbather/RT_tables/P+2_levels_NIST.txt diff --git a/src/RT_tables/P+2_levels_processed.txt b/src/sunbather/RT_tables/P+2_levels_processed.txt similarity index 100% rename from src/RT_tables/P+2_levels_processed.txt rename to src/sunbather/RT_tables/P+2_levels_processed.txt diff --git a/src/RT_tables/P+2_lines_NIST.txt b/src/sunbather/RT_tables/P+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+2_lines_NIST.txt rename to src/sunbather/RT_tables/P+2_lines_NIST.txt diff --git a/src/RT_tables/P+3_levels_NIST.txt b/src/sunbather/RT_tables/P+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+3_levels_NIST.txt rename to src/sunbather/RT_tables/P+3_levels_NIST.txt diff --git a/src/RT_tables/P+3_levels_processed.txt b/src/sunbather/RT_tables/P+3_levels_processed.txt similarity index 100% rename from src/RT_tables/P+3_levels_processed.txt rename to src/sunbather/RT_tables/P+3_levels_processed.txt diff --git a/src/RT_tables/P+3_lines_NIST.txt b/src/sunbather/RT_tables/P+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+3_lines_NIST.txt rename to src/sunbather/RT_tables/P+3_lines_NIST.txt diff --git a/src/RT_tables/P+4_levels_NIST.txt b/src/sunbather/RT_tables/P+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+4_levels_NIST.txt rename to src/sunbather/RT_tables/P+4_levels_NIST.txt diff --git a/src/RT_tables/P+4_levels_processed.txt b/src/sunbather/RT_tables/P+4_levels_processed.txt similarity index 100% rename from src/RT_tables/P+4_levels_processed.txt rename to src/sunbather/RT_tables/P+4_levels_processed.txt diff --git a/src/RT_tables/P+4_lines_NIST.txt b/src/sunbather/RT_tables/P+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+4_lines_NIST.txt rename to src/sunbather/RT_tables/P+4_lines_NIST.txt diff --git a/src/RT_tables/P+5_levels_NIST.txt b/src/sunbather/RT_tables/P+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+5_levels_NIST.txt rename to src/sunbather/RT_tables/P+5_levels_NIST.txt diff --git a/src/RT_tables/P+5_levels_processed.txt b/src/sunbather/RT_tables/P+5_levels_processed.txt similarity index 100% rename from src/RT_tables/P+5_levels_processed.txt rename to src/sunbather/RT_tables/P+5_levels_processed.txt diff --git a/src/RT_tables/P+5_lines_NIST.txt b/src/sunbather/RT_tables/P+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+5_lines_NIST.txt rename to src/sunbather/RT_tables/P+5_lines_NIST.txt diff --git a/src/RT_tables/P+6_levels_NIST.txt b/src/sunbather/RT_tables/P+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+6_levels_NIST.txt rename to src/sunbather/RT_tables/P+6_levels_NIST.txt diff --git a/src/RT_tables/P+6_levels_processed.txt b/src/sunbather/RT_tables/P+6_levels_processed.txt similarity index 100% rename from src/RT_tables/P+6_levels_processed.txt rename to src/sunbather/RT_tables/P+6_levels_processed.txt diff --git a/src/RT_tables/P+6_lines_NIST.txt b/src/sunbather/RT_tables/P+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+6_lines_NIST.txt rename to src/sunbather/RT_tables/P+6_lines_NIST.txt diff --git a/src/RT_tables/P+7_levels_NIST.txt b/src/sunbather/RT_tables/P+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+7_levels_NIST.txt rename to src/sunbather/RT_tables/P+7_levels_NIST.txt diff --git a/src/RT_tables/P+7_levels_processed.txt b/src/sunbather/RT_tables/P+7_levels_processed.txt similarity index 100% rename from src/RT_tables/P+7_levels_processed.txt rename to src/sunbather/RT_tables/P+7_levels_processed.txt diff --git a/src/RT_tables/P+7_lines_NIST.txt b/src/sunbather/RT_tables/P+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+7_lines_NIST.txt rename to src/sunbather/RT_tables/P+7_lines_NIST.txt diff --git a/src/RT_tables/P+8_levels_NIST.txt b/src/sunbather/RT_tables/P+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+8_levels_NIST.txt rename to src/sunbather/RT_tables/P+8_levels_NIST.txt diff --git a/src/RT_tables/P+8_levels_processed.txt b/src/sunbather/RT_tables/P+8_levels_processed.txt similarity index 100% rename from src/RT_tables/P+8_levels_processed.txt rename to src/sunbather/RT_tables/P+8_levels_processed.txt diff --git a/src/RT_tables/P+8_lines_NIST.txt b/src/sunbather/RT_tables/P+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+8_lines_NIST.txt rename to src/sunbather/RT_tables/P+8_lines_NIST.txt diff --git a/src/RT_tables/P+9_levels_NIST.txt b/src/sunbather/RT_tables/P+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+9_levels_NIST.txt rename to src/sunbather/RT_tables/P+9_levels_NIST.txt diff --git a/src/RT_tables/P+9_levels_processed.txt b/src/sunbather/RT_tables/P+9_levels_processed.txt similarity index 100% rename from src/RT_tables/P+9_levels_processed.txt rename to src/sunbather/RT_tables/P+9_levels_processed.txt diff --git a/src/RT_tables/P+9_lines_NIST.txt b/src/sunbather/RT_tables/P+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+9_lines_NIST.txt rename to src/sunbather/RT_tables/P+9_lines_NIST.txt diff --git a/src/RT_tables/P+_levels_NIST.txt b/src/sunbather/RT_tables/P+_levels_NIST.txt similarity index 100% rename from src/RT_tables/P+_levels_NIST.txt rename to src/sunbather/RT_tables/P+_levels_NIST.txt diff --git a/src/RT_tables/P+_levels_processed.txt b/src/sunbather/RT_tables/P+_levels_processed.txt similarity index 100% rename from src/RT_tables/P+_levels_processed.txt rename to src/sunbather/RT_tables/P+_levels_processed.txt diff --git a/src/RT_tables/P+_lines_NIST.txt b/src/sunbather/RT_tables/P+_lines_NIST.txt similarity index 100% rename from src/RT_tables/P+_lines_NIST.txt rename to src/sunbather/RT_tables/P+_lines_NIST.txt diff --git a/src/RT_tables/P_levels_NIST.txt b/src/sunbather/RT_tables/P_levels_NIST.txt similarity index 100% rename from src/RT_tables/P_levels_NIST.txt rename to src/sunbather/RT_tables/P_levels_NIST.txt diff --git a/src/RT_tables/P_levels_processed.txt b/src/sunbather/RT_tables/P_levels_processed.txt similarity index 100% rename from src/RT_tables/P_levels_processed.txt rename to src/sunbather/RT_tables/P_levels_processed.txt diff --git a/src/RT_tables/P_lines_NIST.txt b/src/sunbather/RT_tables/P_lines_NIST.txt similarity index 100% rename from src/RT_tables/P_lines_NIST.txt rename to src/sunbather/RT_tables/P_lines_NIST.txt diff --git a/src/RT_tables/S+10_levels_NIST.txt b/src/sunbather/RT_tables/S+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+10_levels_NIST.txt rename to src/sunbather/RT_tables/S+10_levels_NIST.txt diff --git a/src/RT_tables/S+10_levels_processed.txt b/src/sunbather/RT_tables/S+10_levels_processed.txt similarity index 100% rename from src/RT_tables/S+10_levels_processed.txt rename to src/sunbather/RT_tables/S+10_levels_processed.txt diff --git a/src/RT_tables/S+10_lines_NIST.txt b/src/sunbather/RT_tables/S+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+10_lines_NIST.txt rename to src/sunbather/RT_tables/S+10_lines_NIST.txt diff --git a/src/RT_tables/S+11_levels_NIST.txt b/src/sunbather/RT_tables/S+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+11_levels_NIST.txt rename to src/sunbather/RT_tables/S+11_levels_NIST.txt diff --git a/src/RT_tables/S+11_levels_processed.txt b/src/sunbather/RT_tables/S+11_levels_processed.txt similarity index 100% rename from src/RT_tables/S+11_levels_processed.txt rename to src/sunbather/RT_tables/S+11_levels_processed.txt diff --git a/src/RT_tables/S+11_lines_NIST.txt b/src/sunbather/RT_tables/S+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+11_lines_NIST.txt rename to src/sunbather/RT_tables/S+11_lines_NIST.txt diff --git a/src/RT_tables/S+12_levels_NIST.txt b/src/sunbather/RT_tables/S+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+12_levels_NIST.txt rename to src/sunbather/RT_tables/S+12_levels_NIST.txt diff --git a/src/RT_tables/S+12_levels_processed.txt b/src/sunbather/RT_tables/S+12_levels_processed.txt similarity index 100% rename from src/RT_tables/S+12_levels_processed.txt rename to src/sunbather/RT_tables/S+12_levels_processed.txt diff --git a/src/RT_tables/S+12_lines_NIST.txt b/src/sunbather/RT_tables/S+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+12_lines_NIST.txt rename to src/sunbather/RT_tables/S+12_lines_NIST.txt diff --git a/src/RT_tables/S+2_levels_NIST.txt b/src/sunbather/RT_tables/S+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+2_levels_NIST.txt rename to src/sunbather/RT_tables/S+2_levels_NIST.txt diff --git a/src/RT_tables/S+2_levels_processed.txt b/src/sunbather/RT_tables/S+2_levels_processed.txt similarity index 100% rename from src/RT_tables/S+2_levels_processed.txt rename to src/sunbather/RT_tables/S+2_levels_processed.txt diff --git a/src/RT_tables/S+2_lines_NIST.txt b/src/sunbather/RT_tables/S+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+2_lines_NIST.txt rename to src/sunbather/RT_tables/S+2_lines_NIST.txt diff --git a/src/RT_tables/S+3_levels_NIST.txt b/src/sunbather/RT_tables/S+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+3_levels_NIST.txt rename to src/sunbather/RT_tables/S+3_levels_NIST.txt diff --git a/src/RT_tables/S+3_levels_processed.txt b/src/sunbather/RT_tables/S+3_levels_processed.txt similarity index 100% rename from src/RT_tables/S+3_levels_processed.txt rename to src/sunbather/RT_tables/S+3_levels_processed.txt diff --git a/src/RT_tables/S+3_lines_NIST.txt b/src/sunbather/RT_tables/S+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+3_lines_NIST.txt rename to src/sunbather/RT_tables/S+3_lines_NIST.txt diff --git a/src/RT_tables/S+4_levels_NIST.txt b/src/sunbather/RT_tables/S+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+4_levels_NIST.txt rename to src/sunbather/RT_tables/S+4_levels_NIST.txt diff --git a/src/RT_tables/S+4_levels_processed.txt b/src/sunbather/RT_tables/S+4_levels_processed.txt similarity index 100% rename from src/RT_tables/S+4_levels_processed.txt rename to src/sunbather/RT_tables/S+4_levels_processed.txt diff --git a/src/RT_tables/S+4_lines_NIST.txt b/src/sunbather/RT_tables/S+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+4_lines_NIST.txt rename to src/sunbather/RT_tables/S+4_lines_NIST.txt diff --git a/src/RT_tables/S+5_levels_NIST.txt b/src/sunbather/RT_tables/S+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+5_levels_NIST.txt rename to src/sunbather/RT_tables/S+5_levels_NIST.txt diff --git a/src/RT_tables/S+5_levels_processed.txt b/src/sunbather/RT_tables/S+5_levels_processed.txt similarity index 100% rename from src/RT_tables/S+5_levels_processed.txt rename to src/sunbather/RT_tables/S+5_levels_processed.txt diff --git a/src/RT_tables/S+5_lines_NIST.txt b/src/sunbather/RT_tables/S+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+5_lines_NIST.txt rename to src/sunbather/RT_tables/S+5_lines_NIST.txt diff --git a/src/RT_tables/S+6_levels_NIST.txt b/src/sunbather/RT_tables/S+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+6_levels_NIST.txt rename to src/sunbather/RT_tables/S+6_levels_NIST.txt diff --git a/src/RT_tables/S+6_levels_processed.txt b/src/sunbather/RT_tables/S+6_levels_processed.txt similarity index 100% rename from src/RT_tables/S+6_levels_processed.txt rename to src/sunbather/RT_tables/S+6_levels_processed.txt diff --git a/src/RT_tables/S+6_lines_NIST.txt b/src/sunbather/RT_tables/S+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+6_lines_NIST.txt rename to src/sunbather/RT_tables/S+6_lines_NIST.txt diff --git a/src/RT_tables/S+7_levels_NIST.txt b/src/sunbather/RT_tables/S+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+7_levels_NIST.txt rename to src/sunbather/RT_tables/S+7_levels_NIST.txt diff --git a/src/RT_tables/S+7_levels_processed.txt b/src/sunbather/RT_tables/S+7_levels_processed.txt similarity index 100% rename from src/RT_tables/S+7_levels_processed.txt rename to src/sunbather/RT_tables/S+7_levels_processed.txt diff --git a/src/RT_tables/S+7_lines_NIST.txt b/src/sunbather/RT_tables/S+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+7_lines_NIST.txt rename to src/sunbather/RT_tables/S+7_lines_NIST.txt diff --git a/src/RT_tables/S+8_levels_NIST.txt b/src/sunbather/RT_tables/S+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+8_levels_NIST.txt rename to src/sunbather/RT_tables/S+8_levels_NIST.txt diff --git a/src/RT_tables/S+8_levels_processed.txt b/src/sunbather/RT_tables/S+8_levels_processed.txt similarity index 100% rename from src/RT_tables/S+8_levels_processed.txt rename to src/sunbather/RT_tables/S+8_levels_processed.txt diff --git a/src/RT_tables/S+8_lines_NIST.txt b/src/sunbather/RT_tables/S+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+8_lines_NIST.txt rename to src/sunbather/RT_tables/S+8_lines_NIST.txt diff --git a/src/RT_tables/S+9_levels_NIST.txt b/src/sunbather/RT_tables/S+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+9_levels_NIST.txt rename to src/sunbather/RT_tables/S+9_levels_NIST.txt diff --git a/src/RT_tables/S+9_levels_processed.txt b/src/sunbather/RT_tables/S+9_levels_processed.txt similarity index 100% rename from src/RT_tables/S+9_levels_processed.txt rename to src/sunbather/RT_tables/S+9_levels_processed.txt diff --git a/src/RT_tables/S+9_lines_NIST.txt b/src/sunbather/RT_tables/S+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+9_lines_NIST.txt rename to src/sunbather/RT_tables/S+9_lines_NIST.txt diff --git a/src/RT_tables/S+_levels_NIST.txt b/src/sunbather/RT_tables/S+_levels_NIST.txt similarity index 100% rename from src/RT_tables/S+_levels_NIST.txt rename to src/sunbather/RT_tables/S+_levels_NIST.txt diff --git a/src/RT_tables/S+_levels_processed.txt b/src/sunbather/RT_tables/S+_levels_processed.txt similarity index 100% rename from src/RT_tables/S+_levels_processed.txt rename to src/sunbather/RT_tables/S+_levels_processed.txt diff --git a/src/RT_tables/S+_lines_NIST.txt b/src/sunbather/RT_tables/S+_lines_NIST.txt similarity index 100% rename from src/RT_tables/S+_lines_NIST.txt rename to src/sunbather/RT_tables/S+_lines_NIST.txt diff --git a/src/RT_tables/S_levels_NIST.txt b/src/sunbather/RT_tables/S_levels_NIST.txt similarity index 100% rename from src/RT_tables/S_levels_NIST.txt rename to src/sunbather/RT_tables/S_levels_NIST.txt diff --git a/src/RT_tables/S_levels_processed.txt b/src/sunbather/RT_tables/S_levels_processed.txt similarity index 100% rename from src/RT_tables/S_levels_processed.txt rename to src/sunbather/RT_tables/S_levels_processed.txt diff --git a/src/RT_tables/S_lines_NIST.txt b/src/sunbather/RT_tables/S_lines_NIST.txt similarity index 100% rename from src/RT_tables/S_lines_NIST.txt rename to src/sunbather/RT_tables/S_lines_NIST.txt diff --git a/src/RT_tables/Sc+10_levels_NIST.txt b/src/sunbather/RT_tables/Sc+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+10_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+10_levels_NIST.txt diff --git a/src/RT_tables/Sc+10_levels_processed.txt b/src/sunbather/RT_tables/Sc+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+10_levels_processed.txt rename to src/sunbather/RT_tables/Sc+10_levels_processed.txt diff --git a/src/RT_tables/Sc+10_lines_NIST.txt b/src/sunbather/RT_tables/Sc+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+10_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+10_lines_NIST.txt diff --git a/src/RT_tables/Sc+11_levels_NIST.txt b/src/sunbather/RT_tables/Sc+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+11_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+11_levels_NIST.txt diff --git a/src/RT_tables/Sc+11_levels_processed.txt b/src/sunbather/RT_tables/Sc+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+11_levels_processed.txt rename to src/sunbather/RT_tables/Sc+11_levels_processed.txt diff --git a/src/RT_tables/Sc+11_lines_NIST.txt b/src/sunbather/RT_tables/Sc+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+11_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+11_lines_NIST.txt diff --git a/src/RT_tables/Sc+12_levels_NIST.txt b/src/sunbather/RT_tables/Sc+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+12_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+12_levels_NIST.txt diff --git a/src/RT_tables/Sc+12_levels_processed.txt b/src/sunbather/RT_tables/Sc+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+12_levels_processed.txt rename to src/sunbather/RT_tables/Sc+12_levels_processed.txt diff --git a/src/RT_tables/Sc+12_lines_NIST.txt b/src/sunbather/RT_tables/Sc+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+12_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+12_lines_NIST.txt diff --git a/src/RT_tables/Sc+2_levels_NIST.txt b/src/sunbather/RT_tables/Sc+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+2_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+2_levels_NIST.txt diff --git a/src/RT_tables/Sc+2_levels_processed.txt b/src/sunbather/RT_tables/Sc+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+2_levels_processed.txt rename to src/sunbather/RT_tables/Sc+2_levels_processed.txt diff --git a/src/RT_tables/Sc+2_lines_NIST.txt b/src/sunbather/RT_tables/Sc+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+2_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+2_lines_NIST.txt diff --git a/src/RT_tables/Sc+3_levels_NIST.txt b/src/sunbather/RT_tables/Sc+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+3_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+3_levels_NIST.txt diff --git a/src/RT_tables/Sc+3_levels_processed.txt b/src/sunbather/RT_tables/Sc+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+3_levels_processed.txt rename to src/sunbather/RT_tables/Sc+3_levels_processed.txt diff --git a/src/RT_tables/Sc+3_lines_NIST.txt b/src/sunbather/RT_tables/Sc+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+3_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+3_lines_NIST.txt diff --git a/src/RT_tables/Sc+4_levels_NIST.txt b/src/sunbather/RT_tables/Sc+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+4_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+4_levels_NIST.txt diff --git a/src/RT_tables/Sc+4_levels_processed.txt b/src/sunbather/RT_tables/Sc+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+4_levels_processed.txt rename to src/sunbather/RT_tables/Sc+4_levels_processed.txt diff --git a/src/RT_tables/Sc+4_lines_NIST.txt b/src/sunbather/RT_tables/Sc+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+4_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+4_lines_NIST.txt diff --git a/src/RT_tables/Sc+5_levels_NIST.txt b/src/sunbather/RT_tables/Sc+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+5_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+5_levels_NIST.txt diff --git a/src/RT_tables/Sc+5_levels_processed.txt b/src/sunbather/RT_tables/Sc+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+5_levels_processed.txt rename to src/sunbather/RT_tables/Sc+5_levels_processed.txt diff --git a/src/RT_tables/Sc+5_lines_NIST.txt b/src/sunbather/RT_tables/Sc+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+5_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+5_lines_NIST.txt diff --git a/src/RT_tables/Sc+6_levels_NIST.txt b/src/sunbather/RT_tables/Sc+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+6_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+6_levels_NIST.txt diff --git a/src/RT_tables/Sc+6_levels_processed.txt b/src/sunbather/RT_tables/Sc+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+6_levels_processed.txt rename to src/sunbather/RT_tables/Sc+6_levels_processed.txt diff --git a/src/RT_tables/Sc+6_lines_NIST.txt b/src/sunbather/RT_tables/Sc+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+6_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+6_lines_NIST.txt diff --git a/src/RT_tables/Sc+7_levels_NIST.txt b/src/sunbather/RT_tables/Sc+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+7_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+7_levels_NIST.txt diff --git a/src/RT_tables/Sc+7_levels_processed.txt b/src/sunbather/RT_tables/Sc+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+7_levels_processed.txt rename to src/sunbather/RT_tables/Sc+7_levels_processed.txt diff --git a/src/RT_tables/Sc+7_lines_NIST.txt b/src/sunbather/RT_tables/Sc+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+7_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+7_lines_NIST.txt diff --git a/src/RT_tables/Sc+8_levels_NIST.txt b/src/sunbather/RT_tables/Sc+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+8_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+8_levels_NIST.txt diff --git a/src/RT_tables/Sc+8_levels_processed.txt b/src/sunbather/RT_tables/Sc+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+8_levels_processed.txt rename to src/sunbather/RT_tables/Sc+8_levels_processed.txt diff --git a/src/RT_tables/Sc+8_lines_NIST.txt b/src/sunbather/RT_tables/Sc+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+8_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+8_lines_NIST.txt diff --git a/src/RT_tables/Sc+9_levels_NIST.txt b/src/sunbather/RT_tables/Sc+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+9_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+9_levels_NIST.txt diff --git a/src/RT_tables/Sc+9_levels_processed.txt b/src/sunbather/RT_tables/Sc+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+9_levels_processed.txt rename to src/sunbather/RT_tables/Sc+9_levels_processed.txt diff --git a/src/RT_tables/Sc+9_lines_NIST.txt b/src/sunbather/RT_tables/Sc+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+9_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+9_lines_NIST.txt diff --git a/src/RT_tables/Sc+_levels_NIST.txt b/src/sunbather/RT_tables/Sc+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc+_levels_NIST.txt rename to src/sunbather/RT_tables/Sc+_levels_NIST.txt diff --git a/src/RT_tables/Sc+_levels_processed.txt b/src/sunbather/RT_tables/Sc+_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc+_levels_processed.txt rename to src/sunbather/RT_tables/Sc+_levels_processed.txt diff --git a/src/RT_tables/Sc+_lines_NIST.txt b/src/sunbather/RT_tables/Sc+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc+_lines_NIST.txt rename to src/sunbather/RT_tables/Sc+_lines_NIST.txt diff --git a/src/RT_tables/Sc_levels_NIST.txt b/src/sunbather/RT_tables/Sc_levels_NIST.txt similarity index 100% rename from src/RT_tables/Sc_levels_NIST.txt rename to src/sunbather/RT_tables/Sc_levels_NIST.txt diff --git a/src/RT_tables/Sc_levels_processed.txt b/src/sunbather/RT_tables/Sc_levels_processed.txt similarity index 100% rename from src/RT_tables/Sc_levels_processed.txt rename to src/sunbather/RT_tables/Sc_levels_processed.txt diff --git a/src/RT_tables/Sc_lines_NIST.txt b/src/sunbather/RT_tables/Sc_lines_NIST.txt similarity index 100% rename from src/RT_tables/Sc_lines_NIST.txt rename to src/sunbather/RT_tables/Sc_lines_NIST.txt diff --git a/src/RT_tables/Si+10_levels_NIST.txt b/src/sunbather/RT_tables/Si+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+10_levels_NIST.txt rename to src/sunbather/RT_tables/Si+10_levels_NIST.txt diff --git a/src/RT_tables/Si+10_levels_processed.txt b/src/sunbather/RT_tables/Si+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+10_levels_processed.txt rename to src/sunbather/RT_tables/Si+10_levels_processed.txt diff --git a/src/RT_tables/Si+10_lines_NIST.txt b/src/sunbather/RT_tables/Si+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+10_lines_NIST.txt rename to src/sunbather/RT_tables/Si+10_lines_NIST.txt diff --git a/src/RT_tables/Si+11_levels_NIST.txt b/src/sunbather/RT_tables/Si+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+11_levels_NIST.txt rename to src/sunbather/RT_tables/Si+11_levels_NIST.txt diff --git a/src/RT_tables/Si+11_levels_processed.txt b/src/sunbather/RT_tables/Si+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+11_levels_processed.txt rename to src/sunbather/RT_tables/Si+11_levels_processed.txt diff --git a/src/RT_tables/Si+11_lines_NIST.txt b/src/sunbather/RT_tables/Si+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+11_lines_NIST.txt rename to src/sunbather/RT_tables/Si+11_lines_NIST.txt diff --git a/src/RT_tables/Si+12_levels_NIST.txt b/src/sunbather/RT_tables/Si+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+12_levels_NIST.txt rename to src/sunbather/RT_tables/Si+12_levels_NIST.txt diff --git a/src/RT_tables/Si+12_levels_processed.txt b/src/sunbather/RT_tables/Si+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+12_levels_processed.txt rename to src/sunbather/RT_tables/Si+12_levels_processed.txt diff --git a/src/RT_tables/Si+12_lines_NIST.txt b/src/sunbather/RT_tables/Si+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+12_lines_NIST.txt rename to src/sunbather/RT_tables/Si+12_lines_NIST.txt diff --git a/src/RT_tables/Si+2_levels_NIST.txt b/src/sunbather/RT_tables/Si+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+2_levels_NIST.txt rename to src/sunbather/RT_tables/Si+2_levels_NIST.txt diff --git a/src/RT_tables/Si+2_levels_processed.txt b/src/sunbather/RT_tables/Si+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+2_levels_processed.txt rename to src/sunbather/RT_tables/Si+2_levels_processed.txt diff --git a/src/RT_tables/Si+2_lines_NIST.txt b/src/sunbather/RT_tables/Si+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+2_lines_NIST.txt rename to src/sunbather/RT_tables/Si+2_lines_NIST.txt diff --git a/src/RT_tables/Si+3_levels_NIST.txt b/src/sunbather/RT_tables/Si+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+3_levels_NIST.txt rename to src/sunbather/RT_tables/Si+3_levels_NIST.txt diff --git a/src/RT_tables/Si+3_levels_processed.txt b/src/sunbather/RT_tables/Si+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+3_levels_processed.txt rename to src/sunbather/RT_tables/Si+3_levels_processed.txt diff --git a/src/RT_tables/Si+3_lines_NIST.txt b/src/sunbather/RT_tables/Si+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+3_lines_NIST.txt rename to src/sunbather/RT_tables/Si+3_lines_NIST.txt diff --git a/src/RT_tables/Si+4_levels_NIST.txt b/src/sunbather/RT_tables/Si+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+4_levels_NIST.txt rename to src/sunbather/RT_tables/Si+4_levels_NIST.txt diff --git a/src/RT_tables/Si+4_levels_processed.txt b/src/sunbather/RT_tables/Si+4_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+4_levels_processed.txt rename to src/sunbather/RT_tables/Si+4_levels_processed.txt diff --git a/src/RT_tables/Si+4_lines_NIST.txt b/src/sunbather/RT_tables/Si+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+4_lines_NIST.txt rename to src/sunbather/RT_tables/Si+4_lines_NIST.txt diff --git a/src/RT_tables/Si+5_levels_NIST.txt b/src/sunbather/RT_tables/Si+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+5_levels_NIST.txt rename to src/sunbather/RT_tables/Si+5_levels_NIST.txt diff --git a/src/RT_tables/Si+5_levels_processed.txt b/src/sunbather/RT_tables/Si+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+5_levels_processed.txt rename to src/sunbather/RT_tables/Si+5_levels_processed.txt diff --git a/src/RT_tables/Si+5_lines_NIST.txt b/src/sunbather/RT_tables/Si+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+5_lines_NIST.txt rename to src/sunbather/RT_tables/Si+5_lines_NIST.txt diff --git a/src/RT_tables/Si+6_levels_NIST.txt b/src/sunbather/RT_tables/Si+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+6_levels_NIST.txt rename to src/sunbather/RT_tables/Si+6_levels_NIST.txt diff --git a/src/RT_tables/Si+6_levels_processed.txt b/src/sunbather/RT_tables/Si+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+6_levels_processed.txt rename to src/sunbather/RT_tables/Si+6_levels_processed.txt diff --git a/src/RT_tables/Si+6_lines_NIST.txt b/src/sunbather/RT_tables/Si+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+6_lines_NIST.txt rename to src/sunbather/RT_tables/Si+6_lines_NIST.txt diff --git a/src/RT_tables/Si+7_levels_NIST.txt b/src/sunbather/RT_tables/Si+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+7_levels_NIST.txt rename to src/sunbather/RT_tables/Si+7_levels_NIST.txt diff --git a/src/RT_tables/Si+7_levels_processed.txt b/src/sunbather/RT_tables/Si+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+7_levels_processed.txt rename to src/sunbather/RT_tables/Si+7_levels_processed.txt diff --git a/src/RT_tables/Si+7_lines_NIST.txt b/src/sunbather/RT_tables/Si+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+7_lines_NIST.txt rename to src/sunbather/RT_tables/Si+7_lines_NIST.txt diff --git a/src/RT_tables/Si+8_levels_NIST.txt b/src/sunbather/RT_tables/Si+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+8_levels_NIST.txt rename to src/sunbather/RT_tables/Si+8_levels_NIST.txt diff --git a/src/RT_tables/Si+8_levels_processed.txt b/src/sunbather/RT_tables/Si+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+8_levels_processed.txt rename to src/sunbather/RT_tables/Si+8_levels_processed.txt diff --git a/src/RT_tables/Si+8_lines_NIST.txt b/src/sunbather/RT_tables/Si+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+8_lines_NIST.txt rename to src/sunbather/RT_tables/Si+8_lines_NIST.txt diff --git a/src/RT_tables/Si+9_levels_NIST.txt b/src/sunbather/RT_tables/Si+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+9_levels_NIST.txt rename to src/sunbather/RT_tables/Si+9_levels_NIST.txt diff --git a/src/RT_tables/Si+9_levels_processed.txt b/src/sunbather/RT_tables/Si+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+9_levels_processed.txt rename to src/sunbather/RT_tables/Si+9_levels_processed.txt diff --git a/src/RT_tables/Si+9_lines_NIST.txt b/src/sunbather/RT_tables/Si+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+9_lines_NIST.txt rename to src/sunbather/RT_tables/Si+9_lines_NIST.txt diff --git a/src/RT_tables/Si+_levels_NIST.txt b/src/sunbather/RT_tables/Si+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si+_levels_NIST.txt rename to src/sunbather/RT_tables/Si+_levels_NIST.txt diff --git a/src/RT_tables/Si+_levels_processed.txt b/src/sunbather/RT_tables/Si+_levels_processed.txt similarity index 100% rename from src/RT_tables/Si+_levels_processed.txt rename to src/sunbather/RT_tables/Si+_levels_processed.txt diff --git a/src/RT_tables/Si+_lines_NIST.txt b/src/sunbather/RT_tables/Si+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si+_lines_NIST.txt rename to src/sunbather/RT_tables/Si+_lines_NIST.txt diff --git a/src/RT_tables/Si_levels_NIST.txt b/src/sunbather/RT_tables/Si_levels_NIST.txt similarity index 100% rename from src/RT_tables/Si_levels_NIST.txt rename to src/sunbather/RT_tables/Si_levels_NIST.txt diff --git a/src/RT_tables/Si_levels_processed.txt b/src/sunbather/RT_tables/Si_levels_processed.txt similarity index 100% rename from src/RT_tables/Si_levels_processed.txt rename to src/sunbather/RT_tables/Si_levels_processed.txt diff --git a/src/RT_tables/Si_lines_NIST.txt b/src/sunbather/RT_tables/Si_lines_NIST.txt similarity index 100% rename from src/RT_tables/Si_lines_NIST.txt rename to src/sunbather/RT_tables/Si_lines_NIST.txt diff --git a/src/RT_tables/Ti+10_levels_NIST.txt b/src/sunbather/RT_tables/Ti+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+10_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+10_levels_NIST.txt diff --git a/src/RT_tables/Ti+10_levels_processed.txt b/src/sunbather/RT_tables/Ti+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+10_levels_processed.txt rename to src/sunbather/RT_tables/Ti+10_levels_processed.txt diff --git a/src/RT_tables/Ti+10_lines_NIST.txt b/src/sunbather/RT_tables/Ti+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+10_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+10_lines_NIST.txt diff --git a/src/RT_tables/Ti+11_levels_NIST.txt b/src/sunbather/RT_tables/Ti+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+11_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+11_levels_NIST.txt diff --git a/src/RT_tables/Ti+11_levels_processed.txt b/src/sunbather/RT_tables/Ti+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+11_levels_processed.txt rename to src/sunbather/RT_tables/Ti+11_levels_processed.txt diff --git a/src/RT_tables/Ti+11_lines_NIST.txt b/src/sunbather/RT_tables/Ti+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+11_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+11_lines_NIST.txt diff --git a/src/RT_tables/Ti+12_levels_NIST.txt b/src/sunbather/RT_tables/Ti+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+12_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+12_levels_NIST.txt diff --git a/src/RT_tables/Ti+12_levels_processed.txt b/src/sunbather/RT_tables/Ti+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+12_levels_processed.txt rename to src/sunbather/RT_tables/Ti+12_levels_processed.txt diff --git a/src/RT_tables/Ti+12_lines_NIST.txt b/src/sunbather/RT_tables/Ti+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+12_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+12_lines_NIST.txt diff --git a/src/RT_tables/Ti+2_levels_NIST.txt b/src/sunbather/RT_tables/Ti+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+2_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+2_levels_NIST.txt diff --git a/src/RT_tables/Ti+2_levels_processed.txt b/src/sunbather/RT_tables/Ti+2_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+2_levels_processed.txt rename to src/sunbather/RT_tables/Ti+2_levels_processed.txt diff --git a/src/RT_tables/Ti+2_lines_NIST.txt b/src/sunbather/RT_tables/Ti+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+2_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+2_lines_NIST.txt diff --git a/src/RT_tables/Ti+3_levels_NIST.txt b/src/sunbather/RT_tables/Ti+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+3_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+3_levels_NIST.txt diff --git a/src/RT_tables/Ti+3_levels_processed.txt b/src/sunbather/RT_tables/Ti+3_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+3_levels_processed.txt rename to src/sunbather/RT_tables/Ti+3_levels_processed.txt diff --git a/src/RT_tables/Ti+3_lines_NIST.txt b/src/sunbather/RT_tables/Ti+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+3_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+3_lines_NIST.txt diff --git a/src/RT_tables/Ti+4_levels_NIST.txt b/src/sunbather/RT_tables/Ti+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+4_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+4_levels_NIST.txt diff --git a/src/RT_tables/Ti+4_lines_NIST.txt b/src/sunbather/RT_tables/Ti+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+4_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+4_lines_NIST.txt diff --git a/src/RT_tables/Ti+5_levels_NIST.txt b/src/sunbather/RT_tables/Ti+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+5_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+5_levels_NIST.txt diff --git a/src/RT_tables/Ti+5_levels_processed.txt b/src/sunbather/RT_tables/Ti+5_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+5_levels_processed.txt rename to src/sunbather/RT_tables/Ti+5_levels_processed.txt diff --git a/src/RT_tables/Ti+5_lines_NIST.txt b/src/sunbather/RT_tables/Ti+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+5_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+5_lines_NIST.txt diff --git a/src/RT_tables/Ti+6_levels_NIST.txt b/src/sunbather/RT_tables/Ti+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+6_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+6_levels_NIST.txt diff --git a/src/RT_tables/Ti+6_levels_processed.txt b/src/sunbather/RT_tables/Ti+6_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+6_levels_processed.txt rename to src/sunbather/RT_tables/Ti+6_levels_processed.txt diff --git a/src/RT_tables/Ti+6_lines_NIST.txt b/src/sunbather/RT_tables/Ti+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+6_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+6_lines_NIST.txt diff --git a/src/RT_tables/Ti+7_levels_NIST.txt b/src/sunbather/RT_tables/Ti+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+7_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+7_levels_NIST.txt diff --git a/src/RT_tables/Ti+7_levels_processed.txt b/src/sunbather/RT_tables/Ti+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+7_levels_processed.txt rename to src/sunbather/RT_tables/Ti+7_levels_processed.txt diff --git a/src/RT_tables/Ti+7_lines_NIST.txt b/src/sunbather/RT_tables/Ti+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+7_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+7_lines_NIST.txt diff --git a/src/RT_tables/Ti+8_levels_NIST.txt b/src/sunbather/RT_tables/Ti+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+8_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+8_levels_NIST.txt diff --git a/src/RT_tables/Ti+8_levels_processed.txt b/src/sunbather/RT_tables/Ti+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+8_levels_processed.txt rename to src/sunbather/RT_tables/Ti+8_levels_processed.txt diff --git a/src/RT_tables/Ti+8_lines_NIST.txt b/src/sunbather/RT_tables/Ti+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+8_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+8_lines_NIST.txt diff --git a/src/RT_tables/Ti+9_levels_NIST.txt b/src/sunbather/RT_tables/Ti+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+9_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+9_levels_NIST.txt diff --git a/src/RT_tables/Ti+9_levels_processed.txt b/src/sunbather/RT_tables/Ti+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+9_levels_processed.txt rename to src/sunbather/RT_tables/Ti+9_levels_processed.txt diff --git a/src/RT_tables/Ti+9_lines_NIST.txt b/src/sunbather/RT_tables/Ti+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+9_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+9_lines_NIST.txt diff --git a/src/RT_tables/Ti+_levels_NIST.txt b/src/sunbather/RT_tables/Ti+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti+_levels_NIST.txt rename to src/sunbather/RT_tables/Ti+_levels_NIST.txt diff --git a/src/RT_tables/Ti+_levels_processed.txt b/src/sunbather/RT_tables/Ti+_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti+_levels_processed.txt rename to src/sunbather/RT_tables/Ti+_levels_processed.txt diff --git a/src/RT_tables/Ti+_lines_NIST.txt b/src/sunbather/RT_tables/Ti+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti+_lines_NIST.txt rename to src/sunbather/RT_tables/Ti+_lines_NIST.txt diff --git a/src/RT_tables/Ti_levels_NIST.txt b/src/sunbather/RT_tables/Ti_levels_NIST.txt similarity index 100% rename from src/RT_tables/Ti_levels_NIST.txt rename to src/sunbather/RT_tables/Ti_levels_NIST.txt diff --git a/src/RT_tables/Ti_levels_processed.txt b/src/sunbather/RT_tables/Ti_levels_processed.txt similarity index 100% rename from src/RT_tables/Ti_levels_processed.txt rename to src/sunbather/RT_tables/Ti_levels_processed.txt diff --git a/src/RT_tables/Ti_lines_NIST.txt b/src/sunbather/RT_tables/Ti_lines_NIST.txt similarity index 100% rename from src/RT_tables/Ti_lines_NIST.txt rename to src/sunbather/RT_tables/Ti_lines_NIST.txt diff --git a/src/RT_tables/V+10_levels_NIST.txt b/src/sunbather/RT_tables/V+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+10_levels_NIST.txt rename to src/sunbather/RT_tables/V+10_levels_NIST.txt diff --git a/src/RT_tables/V+10_levels_processed.txt b/src/sunbather/RT_tables/V+10_levels_processed.txt similarity index 100% rename from src/RT_tables/V+10_levels_processed.txt rename to src/sunbather/RT_tables/V+10_levels_processed.txt diff --git a/src/RT_tables/V+10_lines_NIST.txt b/src/sunbather/RT_tables/V+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+10_lines_NIST.txt rename to src/sunbather/RT_tables/V+10_lines_NIST.txt diff --git a/src/RT_tables/V+11_levels_NIST.txt b/src/sunbather/RT_tables/V+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+11_levels_NIST.txt rename to src/sunbather/RT_tables/V+11_levels_NIST.txt diff --git a/src/RT_tables/V+11_levels_processed.txt b/src/sunbather/RT_tables/V+11_levels_processed.txt similarity index 100% rename from src/RT_tables/V+11_levels_processed.txt rename to src/sunbather/RT_tables/V+11_levels_processed.txt diff --git a/src/RT_tables/V+11_lines_NIST.txt b/src/sunbather/RT_tables/V+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+11_lines_NIST.txt rename to src/sunbather/RT_tables/V+11_lines_NIST.txt diff --git a/src/RT_tables/V+12_levels_NIST.txt b/src/sunbather/RT_tables/V+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+12_levels_NIST.txt rename to src/sunbather/RT_tables/V+12_levels_NIST.txt diff --git a/src/RT_tables/V+12_levels_processed.txt b/src/sunbather/RT_tables/V+12_levels_processed.txt similarity index 100% rename from src/RT_tables/V+12_levels_processed.txt rename to src/sunbather/RT_tables/V+12_levels_processed.txt diff --git a/src/RT_tables/V+12_lines_NIST.txt b/src/sunbather/RT_tables/V+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+12_lines_NIST.txt rename to src/sunbather/RT_tables/V+12_lines_NIST.txt diff --git a/src/RT_tables/V+2_levels_NIST.txt b/src/sunbather/RT_tables/V+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+2_levels_NIST.txt rename to src/sunbather/RT_tables/V+2_levels_NIST.txt diff --git a/src/RT_tables/V+2_levels_processed.txt b/src/sunbather/RT_tables/V+2_levels_processed.txt similarity index 100% rename from src/RT_tables/V+2_levels_processed.txt rename to src/sunbather/RT_tables/V+2_levels_processed.txt diff --git a/src/RT_tables/V+2_lines_NIST.txt b/src/sunbather/RT_tables/V+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+2_lines_NIST.txt rename to src/sunbather/RT_tables/V+2_lines_NIST.txt diff --git a/src/RT_tables/V+3_levels_NIST.txt b/src/sunbather/RT_tables/V+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+3_levels_NIST.txt rename to src/sunbather/RT_tables/V+3_levels_NIST.txt diff --git a/src/RT_tables/V+3_levels_processed.txt b/src/sunbather/RT_tables/V+3_levels_processed.txt similarity index 100% rename from src/RT_tables/V+3_levels_processed.txt rename to src/sunbather/RT_tables/V+3_levels_processed.txt diff --git a/src/RT_tables/V+3_lines_NIST.txt b/src/sunbather/RT_tables/V+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+3_lines_NIST.txt rename to src/sunbather/RT_tables/V+3_lines_NIST.txt diff --git a/src/RT_tables/V+4_levels_NIST.txt b/src/sunbather/RT_tables/V+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+4_levels_NIST.txt rename to src/sunbather/RT_tables/V+4_levels_NIST.txt diff --git a/src/RT_tables/V+4_lines_NIST.txt b/src/sunbather/RT_tables/V+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+4_lines_NIST.txt rename to src/sunbather/RT_tables/V+4_lines_NIST.txt diff --git a/src/RT_tables/V+5_levels_NIST.txt b/src/sunbather/RT_tables/V+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+5_levels_NIST.txt rename to src/sunbather/RT_tables/V+5_levels_NIST.txt diff --git a/src/RT_tables/V+5_lines_NIST.txt b/src/sunbather/RT_tables/V+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+5_lines_NIST.txt rename to src/sunbather/RT_tables/V+5_lines_NIST.txt diff --git a/src/RT_tables/V+6_levels_NIST.txt b/src/sunbather/RT_tables/V+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+6_levels_NIST.txt rename to src/sunbather/RT_tables/V+6_levels_NIST.txt diff --git a/src/RT_tables/V+6_levels_processed.txt b/src/sunbather/RT_tables/V+6_levels_processed.txt similarity index 100% rename from src/RT_tables/V+6_levels_processed.txt rename to src/sunbather/RT_tables/V+6_levels_processed.txt diff --git a/src/RT_tables/V+6_lines_NIST.txt b/src/sunbather/RT_tables/V+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+6_lines_NIST.txt rename to src/sunbather/RT_tables/V+6_lines_NIST.txt diff --git a/src/RT_tables/V+7_levels_NIST.txt b/src/sunbather/RT_tables/V+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+7_levels_NIST.txt rename to src/sunbather/RT_tables/V+7_levels_NIST.txt diff --git a/src/RT_tables/V+7_levels_processed.txt b/src/sunbather/RT_tables/V+7_levels_processed.txt similarity index 100% rename from src/RT_tables/V+7_levels_processed.txt rename to src/sunbather/RT_tables/V+7_levels_processed.txt diff --git a/src/RT_tables/V+7_lines_NIST.txt b/src/sunbather/RT_tables/V+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+7_lines_NIST.txt rename to src/sunbather/RT_tables/V+7_lines_NIST.txt diff --git a/src/RT_tables/V+8_levels_NIST.txt b/src/sunbather/RT_tables/V+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+8_levels_NIST.txt rename to src/sunbather/RT_tables/V+8_levels_NIST.txt diff --git a/src/RT_tables/V+8_levels_processed.txt b/src/sunbather/RT_tables/V+8_levels_processed.txt similarity index 100% rename from src/RT_tables/V+8_levels_processed.txt rename to src/sunbather/RT_tables/V+8_levels_processed.txt diff --git a/src/RT_tables/V+8_lines_NIST.txt b/src/sunbather/RT_tables/V+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+8_lines_NIST.txt rename to src/sunbather/RT_tables/V+8_lines_NIST.txt diff --git a/src/RT_tables/V+9_levels_NIST.txt b/src/sunbather/RT_tables/V+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+9_levels_NIST.txt rename to src/sunbather/RT_tables/V+9_levels_NIST.txt diff --git a/src/RT_tables/V+9_levels_processed.txt b/src/sunbather/RT_tables/V+9_levels_processed.txt similarity index 100% rename from src/RT_tables/V+9_levels_processed.txt rename to src/sunbather/RT_tables/V+9_levels_processed.txt diff --git a/src/RT_tables/V+9_lines_NIST.txt b/src/sunbather/RT_tables/V+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+9_lines_NIST.txt rename to src/sunbather/RT_tables/V+9_lines_NIST.txt diff --git a/src/RT_tables/V+_levels_NIST.txt b/src/sunbather/RT_tables/V+_levels_NIST.txt similarity index 100% rename from src/RT_tables/V+_levels_NIST.txt rename to src/sunbather/RT_tables/V+_levels_NIST.txt diff --git a/src/RT_tables/V+_levels_processed.txt b/src/sunbather/RT_tables/V+_levels_processed.txt similarity index 100% rename from src/RT_tables/V+_levels_processed.txt rename to src/sunbather/RT_tables/V+_levels_processed.txt diff --git a/src/RT_tables/V+_lines_NIST.txt b/src/sunbather/RT_tables/V+_lines_NIST.txt similarity index 100% rename from src/RT_tables/V+_lines_NIST.txt rename to src/sunbather/RT_tables/V+_lines_NIST.txt diff --git a/src/RT_tables/V_levels_NIST.txt b/src/sunbather/RT_tables/V_levels_NIST.txt similarity index 100% rename from src/RT_tables/V_levels_NIST.txt rename to src/sunbather/RT_tables/V_levels_NIST.txt diff --git a/src/RT_tables/V_levels_processed.txt b/src/sunbather/RT_tables/V_levels_processed.txt similarity index 100% rename from src/RT_tables/V_levels_processed.txt rename to src/sunbather/RT_tables/V_levels_processed.txt diff --git a/src/RT_tables/V_lines_NIST.txt b/src/sunbather/RT_tables/V_lines_NIST.txt similarity index 100% rename from src/RT_tables/V_lines_NIST.txt rename to src/sunbather/RT_tables/V_lines_NIST.txt diff --git a/src/RT_tables/Zn+10_levels_NIST.txt b/src/sunbather/RT_tables/Zn+10_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+10_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+10_levels_NIST.txt diff --git a/src/RT_tables/Zn+10_levels_processed.txt b/src/sunbather/RT_tables/Zn+10_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn+10_levels_processed.txt rename to src/sunbather/RT_tables/Zn+10_levels_processed.txt diff --git a/src/RT_tables/Zn+10_lines_NIST.txt b/src/sunbather/RT_tables/Zn+10_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+10_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+10_lines_NIST.txt diff --git a/src/RT_tables/Zn+11_levels_NIST.txt b/src/sunbather/RT_tables/Zn+11_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+11_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+11_levels_NIST.txt diff --git a/src/RT_tables/Zn+11_levels_processed.txt b/src/sunbather/RT_tables/Zn+11_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn+11_levels_processed.txt rename to src/sunbather/RT_tables/Zn+11_levels_processed.txt diff --git a/src/RT_tables/Zn+11_lines_NIST.txt b/src/sunbather/RT_tables/Zn+11_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+11_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+11_lines_NIST.txt diff --git a/src/RT_tables/Zn+12_levels_NIST.txt b/src/sunbather/RT_tables/Zn+12_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+12_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+12_levels_NIST.txt diff --git a/src/RT_tables/Zn+12_levels_processed.txt b/src/sunbather/RT_tables/Zn+12_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn+12_levels_processed.txt rename to src/sunbather/RT_tables/Zn+12_levels_processed.txt diff --git a/src/RT_tables/Zn+12_lines_NIST.txt b/src/sunbather/RT_tables/Zn+12_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+12_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+12_lines_NIST.txt diff --git a/src/RT_tables/Zn+2_levels_NIST.txt b/src/sunbather/RT_tables/Zn+2_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+2_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+2_levels_NIST.txt diff --git a/src/RT_tables/Zn+2_lines_NIST.txt b/src/sunbather/RT_tables/Zn+2_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+2_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+2_lines_NIST.txt diff --git a/src/RT_tables/Zn+3_levels_NIST.txt b/src/sunbather/RT_tables/Zn+3_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+3_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+3_levels_NIST.txt diff --git a/src/RT_tables/Zn+3_lines_NIST.txt b/src/sunbather/RT_tables/Zn+3_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+3_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+3_lines_NIST.txt diff --git a/src/RT_tables/Zn+4_levels_NIST.txt b/src/sunbather/RT_tables/Zn+4_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+4_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+4_levels_NIST.txt diff --git a/src/RT_tables/Zn+4_lines_NIST.txt b/src/sunbather/RT_tables/Zn+4_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+4_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+4_lines_NIST.txt diff --git a/src/RT_tables/Zn+5_levels_NIST.txt b/src/sunbather/RT_tables/Zn+5_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+5_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+5_levels_NIST.txt diff --git a/src/RT_tables/Zn+5_lines_NIST.txt b/src/sunbather/RT_tables/Zn+5_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+5_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+5_lines_NIST.txt diff --git a/src/RT_tables/Zn+6_levels_NIST.txt b/src/sunbather/RT_tables/Zn+6_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+6_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+6_levels_NIST.txt diff --git a/src/RT_tables/Zn+6_lines_NIST.txt b/src/sunbather/RT_tables/Zn+6_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+6_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+6_lines_NIST.txt diff --git a/src/RT_tables/Zn+7_levels_NIST.txt b/src/sunbather/RT_tables/Zn+7_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+7_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+7_levels_NIST.txt diff --git a/src/RT_tables/Zn+7_levels_processed.txt b/src/sunbather/RT_tables/Zn+7_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn+7_levels_processed.txt rename to src/sunbather/RT_tables/Zn+7_levels_processed.txt diff --git a/src/RT_tables/Zn+7_lines_NIST.txt b/src/sunbather/RT_tables/Zn+7_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+7_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+7_lines_NIST.txt diff --git a/src/RT_tables/Zn+8_levels_NIST.txt b/src/sunbather/RT_tables/Zn+8_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+8_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+8_levels_NIST.txt diff --git a/src/RT_tables/Zn+8_levels_processed.txt b/src/sunbather/RT_tables/Zn+8_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn+8_levels_processed.txt rename to src/sunbather/RT_tables/Zn+8_levels_processed.txt diff --git a/src/RT_tables/Zn+8_lines_NIST.txt b/src/sunbather/RT_tables/Zn+8_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+8_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+8_lines_NIST.txt diff --git a/src/RT_tables/Zn+9_levels_NIST.txt b/src/sunbather/RT_tables/Zn+9_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+9_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+9_levels_NIST.txt diff --git a/src/RT_tables/Zn+9_levels_processed.txt b/src/sunbather/RT_tables/Zn+9_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn+9_levels_processed.txt rename to src/sunbather/RT_tables/Zn+9_levels_processed.txt diff --git a/src/RT_tables/Zn+9_lines_NIST.txt b/src/sunbather/RT_tables/Zn+9_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+9_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+9_lines_NIST.txt diff --git a/src/RT_tables/Zn+_levels_NIST.txt b/src/sunbather/RT_tables/Zn+_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn+_levels_NIST.txt rename to src/sunbather/RT_tables/Zn+_levels_NIST.txt diff --git a/src/RT_tables/Zn+_lines_NIST.txt b/src/sunbather/RT_tables/Zn+_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn+_lines_NIST.txt rename to src/sunbather/RT_tables/Zn+_lines_NIST.txt diff --git a/src/RT_tables/Zn_levels_NIST.txt b/src/sunbather/RT_tables/Zn_levels_NIST.txt similarity index 100% rename from src/RT_tables/Zn_levels_NIST.txt rename to src/sunbather/RT_tables/Zn_levels_NIST.txt diff --git a/src/RT_tables/Zn_levels_processed.txt b/src/sunbather/RT_tables/Zn_levels_processed.txt similarity index 100% rename from src/RT_tables/Zn_levels_processed.txt rename to src/sunbather/RT_tables/Zn_levels_processed.txt diff --git a/src/RT_tables/Zn_lines_NIST.txt b/src/sunbather/RT_tables/Zn_lines_NIST.txt similarity index 100% rename from src/RT_tables/Zn_lines_NIST.txt rename to src/sunbather/RT_tables/Zn_lines_NIST.txt diff --git a/src/RT_tables/clean_H_lines.py b/src/sunbather/RT_tables/clean_H_lines.py similarity index 100% rename from src/RT_tables/clean_H_lines.py rename to src/sunbather/RT_tables/clean_H_lines.py diff --git a/src/sunbather/__init__.py b/src/sunbather/__init__.py new file mode 100644 index 0000000..e48c7ed --- /dev/null +++ b/src/sunbather/__init__.py @@ -0,0 +1,94 @@ +""" +Initialize sunbather +""" +import os +import pathlib +import shutil + +import sunbather.tools +from sunbather.install_cloudy import GetCloudy + + +def check_cloudy(quiet=False, cloudy_version="23.01"): + """ + Checks if Cloudy executable exists, and if not, prompts to download and build it. + :quiet: bool, if True, does not ask for input + :cloudy_version: str, Cloudy version (default: "23.01", environment variable + CLOUDY_VERSION overrides this) + """ + try: + cloudy_version = os.environ["CLOUDY_VERSION"] + except KeyError: + pass + sunbatherpath = os.path.dirname( + os.path.abspath(__file__) + ) # the absolute path where this code lives + try: + # the path where Cloudy is installed + cloudypath = os.environ["cloudy_path"] + except KeyError: + cloudypath = f"{sunbatherpath}/cloudy/c{cloudy_version}" + if not os.path.exists(f"{cloudypath}/source/cloudy.exe"): + if not quiet: + q = input( + f"Cloudy not found and CLOUDY_PATH is not set. Do you want to install " + f"Cloudy {cloudy_version} now in the sunbather path? (y/n) " + ) + while q.lower() not in ["y", "n"]: + q = input("Please enter 'y' or 'n'") + if q == "n": + raise KeyError( + "Cloudy not found, and the environment variable 'CLOUDY_PATH' is " + "not set. Please set this variable in your .bashrc/.zshrc file " + "to the path where the Cloudy installation is located. " + "Do not point it to the /source/ subfolder, but to the main folder." + ) + installer = GetCloudy(version=cloudy_version) + installer.download() + installer.extract() + installer.compile() + installer.test() + installer.copy_data() + + +def make_workingdir(workingdir=None, quiet=False): + """ + Checks if the SUNBATHER_PROJECT_PATH environment variable has been set and + asks for input if not. + If quiet is True and the working dir is not set, the current dir is used. + Copies the planets.txt file to the working dir if it does not exist.. + + :workingdir: str, path to the working dir. If None, checks the + SUNBATHER_PROJECT_PATH environment variable, and asks for input if this is + not set. (default: None) + :quiet: bool, if True, does not ask for input (default: False) + """ + if workingdir is None: + try: + workingdir = os.environ["SUNBATHER_PROJECT_PATH"] + except KeyError: + if not quiet: + workingdir = input("Enter the working dir for Sunbather: ") + else: + # if quiet, use the current dir (absolute path) + workingdir = os.path.abspath(".") + os.environ["SUNBATHER_PROJECT_PATH"] = workingdir + print( + f"Environment variable SUNBATHER_PROJECT_PATH set to {workingdir}" + ) + if not os.path.exists(f"{workingdir}/planets.txt"): + sunbatherpath = f"{pathlib.Path(__file__).parent.resolve()}" + shutil.copyfile( + f"{sunbatherpath}/data/workingdir/planets.txt", + f"{workingdir}/planets.txt", + ) + + +def firstrun(quiet=False, workingdir=None, cloudy_version="23.01"): + """ + Runs 'check_cloudy()' and 'make_workingdir()'. + """ + check_cloudy(quiet=quiet, cloudy_version=cloudy_version) + make_workingdir(quiet=quiet, workingdir=workingdir) + + print("Sunbather is ready to go!") diff --git a/src/sunbather/construct_parker.py b/src/sunbather/construct_parker.py new file mode 100644 index 0000000..cb0b294 --- /dev/null +++ b/src/sunbather/construct_parker.py @@ -0,0 +1,1245 @@ +""" +Functions to construct parker +""" + +# other imports +import sys +import os +import time +import argparse +import multiprocessing +import traceback +import warnings +from shutil import copyfile +import numpy as np + +# import matplotlib.pyplot as plt +import astropy.units as u + +# from p_winds import tools as pw_tools +from p_winds import parker as pw_parker +from p_winds import hydrogen as pw_hydrogen +from scipy.integrate import simpson, trapezoid +from scipy.interpolate import interp1d + +# sunbather imports +from sunbather import tools + + +def cloudy_spec_to_pwinds(SEDfilename, dist_SED, dist_planet): + """ + Reads a spectrum file in the format that we give it to Cloudy, namely + angstroms and monochromatic flux (i.e., nu*F_nu or lambda*F_lambda) units. + and converts it to a spectrum dictionary that p-winds uses. + This is basically an equivalent of the + p_winds.parker.make_spectrum_from_file() function. + + Parameters + ---------- + SEDfilename : str + Full path + filename of the SED file. SED file must be in the sunbather/Cloudy + standard units, namely wavelengths in Å and lambda*F_lambda flux units. + dist_SED : numeric + Distance from the source at which the SED is defined (typically 1 AU). + Must have the same units as dist_planet. + dist_planet : numeric + Distance from the source to which the SED must be scaled + (typically semi-major axis - total atmospheric height). Must have the + same units as dist_SED. + + Returns + ------- + spectrum : dict + SED at the planet distance in the dictionary format that p-winds expects. + """ + + with open(SEDfilename, "r", encoding="utf-8") as f: + for line in f: + if not line.startswith("#"): # skip through the comments at the top + assert ("angstrom" in line) or ("Angstrom" in line) # verify the units + assert "nuFnu" in line # verify the units + first_spec_point = np.array(line.split(" ")[:2]).astype(float) + break + rest_data = np.genfromtxt(f, skip_header=1) + + SED = np.concatenate( + ([first_spec_point], rest_data) + ) # rejoin with the first spectrum point that we read separately + + flux = SED[:, 1] / SED[:, 0] # from nuFnu = wavFwav to Fwav in erg s-1 cm-2 A-1 + flux = flux * (dist_SED / dist_planet) ** 2 # scale to planet distance + + assert SED[1, 0] > SED[0, 0] # check ascending wavelengths + + # make a dictionary like p_winds expects it + spectrum = { + "wavelength": SED[:, 0], + "flux_lambda": flux, + "wavelength_unit": u.angstrom, + "flux_unit": u.erg / u.s / u.cm**2 / u.angstrom, + "sed_name": SEDfilename.split("/")[-1][:-5], + } # SEDname added by me (without extension) + + return spectrum + + +def calc_neutral_mu(zdict): + """Calculates the mean particle mass assuming a completely neutral (i.e., atomic) + gas, for a given composition (specified through elemental scale factors that + can be converted into abundances). + + Parameters + ---------- + zdict : dict + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + + Returns + ------- + neutral_mu : float + Mean particle mass in units of amu. + """ + + abundances = tools.get_abundances(zdict) + neutral_mu = tools.calc_mu( + 1.0, 0.0, abundances=abundances + ) # set ne=0 so completely neutral + + return neutral_mu + + +def save_plain_parker_profile( + planet, + mdot, + temp, + spectrum, + h_fraction=0.9, + pdir="fH_0.9", + overwrite=False, + no_tidal=False, + altmax=20, +): + """ + Uses the p-winds code (dos Santos et al. 2022). + Runs p-winds and saves a 'pprof' txt file with the r, rho, v, mu structure. + This function uses p-winds standalone and can thus only calculate H/He atmospheres. + Most of this code is taken from the p-winds tutorial found via the github: + https://colab.research.google.com/drive/1mTh6_YEgCRl6DAKqnmRp2XMOW8CTCvm7?usp=sharing + + Sometimes when the solver cannot find a solution, you may want to change + initial_f_ion to 0.5 or 1.0. + + Parameters + ---------- + planet : tools.Planet + Planet parameters. + mdot : str or numeric + log of the mass-loss rate in units of g s-1. + temp : str or numeric + Temperature in units of K. + spectrum : dict + SED at the planet distance in the dictionary format that p-winds expects. + Can be made with cloudy_spec_to_pwinds(). + h_fraction : float, optional + Hydrogen abundance expressed as a fraction of the total, by default 0.9 + pdir : str, optional + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. By default 'fH_0.9'. + overwrite : bool, optional + Whether to overwrite existing models, by default False. + notidal : bool, optional + Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et + al. (2024). See also Appendix D of Vissapragada et al. (2022) for the + p-winds implementation. Default is False, i.e. tidal gravity included. + altmax : int, optional + Maximum altitude of the profile in units of the planet radius. By default 20. + """ + + mdot = float(mdot) + temp = int(temp) + + projectpath = tools.get_sunbather_project_path() + + save_dir = f"{projectpath}/parker_profiles/{planet.name}/{pdir}" + save_name = ( + f"{save_dir}/pprof_{planet.name}_T={str(temp)}_M={mdot:.3f}.txt" + ) + if os.path.exists(save_name) and not overwrite: + print( + "Parker profile already exists and overwrite = False:", + planet.name, + pdir, + f"{mdot:.3f}", + temp, + ) + # this quits the function but if we're running a grid, it doesn't quit + # the whole Python code + return + + R_pl = planet.R / tools.RJ # convert from cm to Rjup + M_pl = planet.M / tools.MJ # convert from g to Mjup + + m_dot = 10**mdot # Total atmospheric escape rate in g / s + r = np.logspace( + 0, np.log10(altmax), 1000 + ) # Radial distance profile in unit of planetary radii + + # A few assumptions about the planet's atmosphere + he_fraction = 1 - h_fraction # He number fraction + he_h_fraction = he_fraction / h_fraction + mean_f_ion = ( + 0.0 # Mean ionization fraction (will be self-consistently calculated later) + ) + mu_0 = (1 + 4 * he_h_fraction) / (1 + he_h_fraction + mean_f_ion) + # mu_0 is the constant mean molecular weight (assumed for now, will be + # updated later) + + initial_f_ion = 0.0 + f_r, mu_bar = pw_hydrogen.ion_fraction( + r, + R_pl, + temp, + h_fraction, + m_dot, + M_pl, + mu_0, + spectrum_at_planet=spectrum, + exact_phi=True, + initial_f_ion=initial_f_ion, + relax_solution=True, + return_mu=True, + atol=1e-8, + rtol=1e-5, + ) + + vs = pw_parker.sound_speed( + temp, mu_bar + ) # Speed of sound (km/s, assumed to be constant) + if no_tidal: + rs = pw_parker.radius_sonic_point( + M_pl, vs + ) # Radius at the sonic point (jupiterRad) + rhos = pw_parker.density_sonic_point( + m_dot, rs, vs + ) # Density at the sonic point (g/cm^3) + r_array = r * R_pl / rs + v_array, rho_array = pw_parker.structure(r_array) + else: + Mstar = planet.Mstar / tools.Msun # convert from g to Msun + a = planet.a / tools.AU # convert from cm to AU + rs = pw_parker.radius_sonic_point_tidal( + M_pl, vs, Mstar, a + ) # radius at the sonic point (jupiterRad) + rhos = pw_parker.density_sonic_point( + m_dot, rs, vs + ) # Density at the sonic point (g/cm^3) + r_array = r * R_pl / rs + v_array, rho_array = pw_parker.structure_tidal(r_array, vs, rs, M_pl, Mstar, a) + mu_array = ((1 - h_fraction) * 4.0 + h_fraction) / ( + h_fraction * (1 + f_r) + (1 - h_fraction) + ) # this assumes no Helium ionization + + save_array = np.column_stack( + (r * planet.R, rho_array * rhos, v_array * vs * 1e5, mu_array) + ) + os.makedirs(save_dir, exist_ok=True) + np.savetxt( + save_name, + save_array, + delimiter="\t", + header=f"hydrogen fraction: {h_fraction:.3f}\nalt rho v mu", + ) + print("Parker wind profile done:", save_name) + + launch_velocity = v_array[0] # velocity at Rp in units of sonic speed + + if launch_velocity > 1: + warnings.warn( + f"This Parker wind profile is supersonic already at Rp: {save_name}" + ) + + +def save_temp_parker_profile( + planet, + mdot, + temp, + spectrum, + zdict, + pdir, + mu_bar=None, + mu_struc=None, + no_tidal=False, + altmax=20, + projectpath=None, +): + """ + Uses the p-winds code (dos Santos et al. 2022) + Runs p_winds and saves a 'pprof' txt file with the r, rho, v, mu structure. + The difference with save_plain_parker_profile() is that this function can + be given a mu_bar value (e.g. from what Cloudy reports) and calculate a + Parker wind profile based on that. + Most of this code is taken from the tutorial found via the github: + https://colab.research.google.com/drive/1mTh6_YEgCRl6DAKqnmRp2XMOW8CTCvm7?usp=sharing + + Parameters + ---------- + planet : tools.Planet + Object storing the planet parameters. + mdot : str or numeric + log of the mass-loss rate in units of g s-1. + temp : str or numeric + Temperature in units of K. + spectrum : dict + SED at the planet distance in the dictionary format that p-winds expects. + Can be made with cloudy_spec_to_pwinds(). + zdict : dict + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. + mu_bar : float, optional + Weighted mean of the mean particle mass. Based on Eq. A.3 of Lampon et + al. (2020). If None, p-winds will calculate mu(r) and the associated + mu_bar. By default None. + mu_struc : numpy.ndarray, optional + Mean particle mass profile, must be provided if mu_bar is None. + Typically, this is a mu(r)-profile as given by Cloudy. By default None. + no_tidal : bool, optional + Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et + al. (2024). See also Appendix D of Vissapragada et al. (2022) for the + p-winds implementation. Default is False, i.e. tidal gravity included. + altmax : int, optional + Maximum altitude of the profile in units of the planet radius. By default 20. + + Returns + ------- + save_name : str + Full path + filename of the saved Parker wind profile file. + mu_bar : float + Weighted mean of the mean particle mass. Based on Eq. A.3 of Lampon et + al. (2020). If the input mu_bar was None, this will return the value + as calculated by p-winds. If the input mu_bar was not None, this will + return that same value. + launch_velocity : float + Velocity at the planet radius in units of the sonic speed. If it is + larger than 1, the wind is "launched" already supersonic, and hence the + assumption of a transonic wind is not valid anymore. + """ + + mdot = float(mdot) + temp = int(temp) + + # convert from cm to Rjup + R_pl = planet.R / tools.RJ + # convert from g to Mjup + M_pl = planet.M / tools.MJ + + m_dot = 10**mdot # Total atmospheric escape rate in g / s + r = np.logspace( + 0, np.log10(altmax), 1000 + ) # Radial distance profile in unit of planetary radii + + if ( + mu_bar is None + ): + # if not given by a Cloudy run, let p-winds calculate it (used the + # first iteration) pretend that the metals don't exist and just + # calculate the h_fraction with only H and He abundances + abundances = tools.get_abundances(zdict) # solar abundances + h_fraction = abundances["H"] / ( + abundances["H"] + abundances["He"] + ) # approximate it by this for now, later Cloudy will give mu + + # A few assumptions about the planet's atmosphere + he_fraction = 1 - h_fraction # He number fraction + he_h_fraction = he_fraction / h_fraction + mean_f_ion = ( + 0.0 # Mean ionization fraction (will be self-consistently calculated later) + ) + mu_0 = (1 + 4 * he_h_fraction) / (1 + he_h_fraction + mean_f_ion) + # mu_0 is the constant mean molecular weight (assumed for now, will be + # updated later) + + initial_f_ion = 0.0 + + f_r, mu_bar = pw_hydrogen.ion_fraction( + r, + R_pl, + temp, + h_fraction, + m_dot, + M_pl, + mu_0, + spectrum_at_planet=spectrum, + exact_phi=True, + initial_f_ion=initial_f_ion, + relax_solution=True, + return_mu=True, + atol=1e-8, + rtol=1e-5, + convergence=0.0001, + max_n_relax=30, + ) # I personally think we can use more than 0.01 convergence + + mu_array = ((1 - h_fraction) * 4.0 + h_fraction) / ( + h_fraction * (1 + f_r) + (1 - h_fraction) + ) # this assumes no Helium ionization + + else: # used later iterations + assert ( + np.abs(mu_struc[0, 0] - 1.0) < 0.03 + and np.abs(mu_struc[-1, 0] - altmax) < 0.0001 + ), "Looks like Cloudy didn't simulate to 1Rp: " + str( + mu_struc[0, 0] + ) # ensure safe extrapolation + mu_array = interp1d(mu_struc[:, 0], mu_struc[:, 1], fill_value="extrapolate")(r) + + vs = pw_parker.sound_speed( + temp, mu_bar + ) # Speed of sound (km/s, assumed to be constant) + if no_tidal: + rs = pw_parker.radius_sonic_point( + M_pl, vs + ) # Radius at the sonic point (jupiterRad) + rhos = pw_parker.density_sonic_point( + m_dot, rs, vs + ) # Density at the sonic point (g/cm^3) + r_array = r * R_pl / rs + v_array, rho_array = pw_parker.structure(r_array) + else: + Mstar = planet.Mstar / tools.Msun # convert from g to Msun + a = planet.a / tools.AU # convert from cm to AU + rs = pw_parker.radius_sonic_point_tidal( + M_pl, vs, Mstar, a + ) # radius at the sonic point (jupiterRad) + rhos = pw_parker.density_sonic_point( + m_dot, rs, vs + ) # Density at the sonic point (g/cm^3) + r_array = r * R_pl / rs + v_array, rho_array = pw_parker.structure_tidal(r_array, vs, rs, M_pl, Mstar, a) + + save_array = np.column_stack( + (r * planet.R, rho_array * rhos, v_array * vs * 1e5, mu_array) + ) + save_dir = f"{projectpath}/parker_profiles/{planet.name}/{pdir}/temp" + save_name = ( + f"{save_dir}/pprof_{planet.name}_T={str(temp)}_M={mdot:.3f}.txt" + ) + zdictstr = "abundance scale factors relative to solar:" + for sp in zdict.keys(): + zdictstr += f" {sp}={zdict[sp]:.1f}" + os.makedirs(save_dir, exist_ok=True) + np.savetxt( + save_name, save_array, delimiter="\t", header=zdictstr + "\nalt rho v mu" + ) + + launch_velocity = v_array[0] # velocity at Rp in units of sonic speed + + return save_name, mu_bar, launch_velocity + + +def run_parker_with_cloudy(filename, temp, planet, zdict): + """ + Runs an isothermal Parker wind profile through Cloudy, using the isothermal + temperature profile. + + Parameters + ---------- + filename : str + Full path + filename of the isothermal Parker wind profile. + Typically $SUNBATHER_PROJECT_PATH/parker_profiles/*planetname*/*pdir*/*filename* + temp : numeric + Isothermal temperature value. + planet : tools.Planet + Object storing the planet parameters. + zdict : dict + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + + Returns + ------- + simname : str + Full path + name of the Cloudy simulation file without file extension. + pprof : pandas.DataFrame + Radial density, velocity and mean particle mass profiles of the + isothermal Parker wind profile. + """ + + pprof = tools.read_parker("", "", "", "", filename=filename) + + altmax = ( + pprof.alt.iloc[-1] / planet.R + ) # maximum altitude of the profile in units of Rp + alt = pprof.alt.values + hden = tools.rho_to_hden(pprof.rho.values, abundances=tools.get_abundances(zdict)) + dlaw = tools.alt_array_to_Cloudy(alt, hden, altmax, planet.R, 1000, log=True) + + nuFnu_1AU_linear, Ryd = tools.get_SED_norm_1AU(planet.SEDname) + nuFnu_a_log = np.log10( + nuFnu_1AU_linear / ((planet.a - altmax * planet.R) / tools.AU) ** 2 + ) + + simname = filename.split(".txt")[0] + tools.write_Cloudy_in( + simname, + title="Simulation of " + filename, + overwrite=True, + flux_scaling=[nuFnu_a_log, Ryd], + SED=planet.SEDname, + dlaw=dlaw, + double_tau=True, + cosmic_rays=True, + zdict=zdict, + constant_temp=temp, + outfiles=[".ovr"], + ) + + tools.run_Cloudy(simname) + + return simname, pprof + + +def calc_mu_bar(sim): + """ + Calculates the weighted mean of the radial mean particle mass profile, + according to Eq. A.3 of Lampon et al. (2020). Code adapted from + p_winds.parker.average_molecular_weight(). + + Parameters + ---------- + sim : tools.Sim + Cloudy simulation output object. + + Returns + ------- + mu_bar : float + Weighted mean of the mean particle mass. + """ + + # Converting units + m_planet = sim.p.M / 1000.0 # planet mass in kg + r = sim.ovr.alt.values[::-1] / 100.0 # Radius profile in m + v_r = sim.ovr.v.values[::-1] / 100.0 # Velocity profile in unit of m / s + temperature = sim.ovr.Te.values[0] # (Isothermal) temperature in units of K + + # Physical constants + k_b = 1.380649e-23 # Boltzmann's constant in J / K + grav = 6.6743e-11 # Gravitational constant in m ** 3 / kg / s ** 2 + + # Mean molecular weight in function of radial distance r + mu_r = sim.ovr.mu.values[::-1] + + # Eq. A.3 of Lampón et al. 2020 is a combination of several integrals, which + # we calculate here + int_1 = simpson(mu_r / r**2, x=r) + int_2 = simpson(mu_r * v_r, x=v_r) + int_3 = trapezoid(mu_r, 1 / mu_r) + int_4 = simpson(1 / r**2, x=r) + int_5 = simpson(v_r, x=v_r) + int_6 = 1 / mu_r[-1] - 1 / mu_r[0] + term_1 = grav * m_planet * int_1 + int_2 + k_b * temperature * int_3 + term_2 = grav * m_planet * int_4 + int_5 + k_b * temperature * int_6 + mu_bar = term_1 / term_2 + + return mu_bar + + +def save_cloudy_parker_profile( + planet, + mdot, + temp, + spectrum, + zdict, + pdir, + convergence=0.01, + maxit=7, + cleantemp=False, + overwrite=False, + verbose=False, + avoid_pwinds_mubar=False, + no_tidal=False, + altmax=20, +): + """ + Calculates an isothermal Parker wind profile with any composition by iteratively + running the p-winds code (dos Santos et al. 2022) and Cloudy (Ferland et + al. 1998; 2017, Chatziokos et al. 2023). This function works iteratively as + follows: + p_winds calculates a density profile, Cloudy calculates the mean particle + mass profile, we calculate the associated mu_bar value, which is passed to + p-winds to calculate a new density profile, until mu_bar has converged to a + stable value. Saves a 'pprof' txt file with the r, rho, v, mu structure. + + Parameters + ---------- + planet : tools.Planet + Object storing the planet parameters. + mdot : str or numeric + log of the mass-loss rate in units of g s-1. + temp : str or numeric + Temperature in units of K. + spectrum : dict + SED at the planet distance in the dictionary format that p-winds expects. + Can be made with cloudy_spec_to_pwinds(). + zdict : dict + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. + convergence : float, optional + Convergence threshold expressed as the relative change in mu_bar + between iterations, by default 0.01 + maxit : int, optional + Maximum number of iterations, by default 7 + cleantemp : bool, optional + Whether to remove the temporary files in the /temp folder. These files store + the intermediate profiles during the iterative process to find mu_bar. + By default False. + overwrite : bool, optional + Whether to overwrite existing models, by default False. + verbose : bool, optional + Whether to print diagnostics about the convergence of mu_bar, by default False + avoid_pwinds_mubar : bool, optional + Whether to avoid using p-winds to calculate mu_bar during the first + iteration. If True, we guess the mu_bar of the first iteration based + on a completely neutral atmosphere. This can be helpful in cases where + p-winds solver cannot find a solution, but Cloudy typically can. By + default False. + no_tidal : bool, optional + Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et + al. (2024). See also Appendix D of Vissapragada et al. (2022) for the + p-winds implementation. Default is False, i.e. tidal gravity included. + altmax : int, optional + Maximum altitude of the profile in units of the planet radius. By default 20. + """ + + projectpath = tools.get_sunbather_project_path() + save_name = ( + f"{projectpath}/parker_profiles/{planet.name}/{pdir}/pprof_{planet.name}" + f"_T={str(temp)}_M={mdot:.3f}.txt" + ) + if os.path.exists(save_name) and not overwrite: + print( + f"Parker profile already exists and overwrite = False:" + f"{planet.name} {pdir} {mdot:.3f} {temp}", + ) + # returning here quits the function but if we're running a grid, it + # doesn't quit the whole Python code + return + + if avoid_pwinds_mubar: + tools.verbose_print( + "Making initial parker profile while assuming a completely neutral " + "mu_bar...", + verbose=verbose, + ) + neutral_mu_bar = calc_neutral_mu(zdict) + neutral_mu_struc = np.array( + [[1.0, neutral_mu_bar], [altmax, neutral_mu_bar]] + ) # set up an array with constant mu(r) at the neutral value + filename, previous_mu_bar, launch_velocity = save_temp_parker_profile( + planet, + mdot, + temp, + spectrum, + zdict, + pdir, + mu_bar=neutral_mu_bar, + mu_struc=neutral_mu_struc, + no_tidal=no_tidal, + altmax=altmax, + projectpath=projectpath, + ) + tools.verbose_print( + f"Saved temp parker profile with neutral mu_bar: {previous_mu_bar}", + verbose=verbose, + ) + else: + tools.verbose_print( + "Making initial parker profile with p-winds...", verbose=verbose + ) + filename, previous_mu_bar, launch_velocity = save_temp_parker_profile( + planet, + mdot, + temp, + spectrum, + zdict, + pdir, + mu_bar=None, + no_tidal=no_tidal, + altmax=altmax, + projectpath=projectpath, + ) + tools.verbose_print( + f"Saved temp parker profile with p-winds's mu_bar: {previous_mu_bar}", + verbose=verbose, + ) + + for itno in range(maxit): + tools.verbose_print(f"Iteration number: {itno+1}", verbose=verbose) + + tools.verbose_print("Running parker profile through Cloudy...", verbose=verbose) + simname, pprof = run_parker_with_cloudy(filename, temp, planet, zdict) + tools.verbose_print("Cloudy run done.", verbose=verbose) + + sim = tools.Sim(simname, altmax=altmax, planet=planet) + sim.addv( + pprof.alt, pprof.v + ) # add the velocity structure to the sim, so that calc_mu_bar() works. + + mu_bar = calc_mu_bar(sim) + tools.verbose_print( + f"Making new parker profile with p-winds based on Cloudy's reported " + f"mu_bar: {mu_bar}", + verbose=verbose, + ) + mu_struc = np.column_stack( + (sim.ovr.alt.values[::-1] / planet.R, sim.ovr.mu[::-1].values) + ) # pass Cloudy's mu structure to save in the pprof + filename, mu_bar, launch_velocity = save_temp_parker_profile( + planet, + mdot, + temp, + spectrum, + zdict, + pdir, + mu_bar=mu_bar, + mu_struc=mu_struc, + no_tidal=no_tidal, + altmax=altmax, + projectpath=projectpath, + ) + tools.verbose_print("Saved temp parker profile.", verbose=verbose) + + if np.abs(mu_bar - previous_mu_bar) / previous_mu_bar < convergence: + print("mu_bar converged:", save_name) + if launch_velocity > 1: + warnings.warn( + f"This Parker wind profile is supersonic already at Rp: {save_name}" + ) + break + previous_mu_bar = mu_bar + + copyfile(filename, filename.split("temp/")[0] + filename.split("temp/")[1]) + tools.verbose_print( + "Copied final parker profile from temp to parent folder.", verbose=verbose + ) + + if cleantemp: # then we remove the temp files + os.remove(simname + ".in") + os.remove(simname + ".out") + os.remove(simname + ".ovr") + os.remove(filename) + tools.verbose_print("Temporary files removed.", verbose=verbose) + + +def run( + plname=None, + pdir=None, + mdot=None, + temp=None, + sed_name="real", + fraction_hydrogen=None, + zdict=None, + mu_conv=0.01, + mu_maxit=7, + overwrite=False, + verbose=False, + avoid_pwinds_mubar=False, + no_tidal=False, +): + """ + Calculates a single isothermal Parker wind profile. + + Parameters + ---------- + plname : str + Planet name (must have parameters stored in + $SUNBATHER_PROJECT_PATH/planets.txt). + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. + mdot : str or numeric + log of the mass-loss rate in units of g s-1. + temp : str or numeric + Temperature in units of K. + sed_name : str + Name of SED file to use. If sed_name is 'real', we use the name as + given in the planets.txt file, but if sed_name is something else, + we advice to use a separate pdir folder for this. + fraction_hydrogen : float or None + Hydrogen abundance expressed as a fraction of the total. If a value is given, + Parker wind profiles will be calculated using p-winds standalone with a H/He + composition. If None is given, Parker wind profiles will be calculated + using the p-winds/Cloudy iterative method and the composition is + specified via the zdict argument. + zdict : dict + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + Will only be used if fH is None, in which case the p-winds/Cloudy + iterative method is applied. + mu_conv : float + Convergence threshold expressed as the relative change in mu_bar + between iterations. Will only be used if fH is None, in which case the + p-winds/Cloudy iterative method is applied. + mu_maxit : int + Maximum number of iterations for the p-winds/Cloudy iterative method. Will only + be used if fH is None. + overwrite : bool + Whether to overwrite existing models. + verbose : bool + Whether to print diagnostics about the convergence of mu_bar. + avoid_pwinds_mubar : bool + Whether to avoid using p-winds to calculate mu_bar during the first iteration, + when using the p-winds/Cloudy iterative method. Will only be used if fH + is None. If True, we guess the mu_bar of the first iteration based on + a completely neutral atmosphere. This can be helpful in cases where + p-winds solver cannot find a solution, but Cloudy typically can. + no_tidal : bool + Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et + al. (2024). See also Appendix D of Vissapragada et al. (2022) for the + p-winds implementation. + """ + + p = tools.Planet(plname) + if sed_name != "real": + p.set_var(SEDname=sed_name) + altmax = min( + 20, int((p.a - p.Rstar) / p.R) + ) # solve profile up to 20 Rp, unless the star is closer than that + spectrum = cloudy_spec_to_pwinds( + tools.get_cloudy_path() + "/data/SED/" + p.SEDname, + 1.0, + (p.a - altmax * p.R) / tools.AU, + ) # assumes SED is at 1 AU + + if fraction_hydrogen is not None: # then run p_winds standalone + save_plain_parker_profile( + p, + mdot, + temp, + spectrum, + h_fraction=fraction_hydrogen, + pdir=pdir, + overwrite=overwrite, + no_tidal=no_tidal, + altmax=altmax, + ) + else: # then run p_winds/Cloudy iterative scheme + save_cloudy_parker_profile( + p, + mdot, + temp, + spectrum, + zdict, + pdir, + convergence=mu_conv, + maxit=mu_maxit, + cleantemp=True, + overwrite=overwrite, + verbose=verbose, + avoid_pwinds_mubar=avoid_pwinds_mubar, + no_tidal=no_tidal, + altmax=altmax, + ) + + +def catch_errors_run(*args): + """ + Executes the run() function with provided arguments, while catching + errors more gracefully. + """ + + try: + run(*args) + except Exception: + traceback.print_exc() + + +def run_models( + plname=None, + pdir=None, + cores=1, + mdot_list=None, + temp_list=None, + sed_name="real", + fraction_hydrogen=None, + zdict=None, + mu_conv=None, + mu_maxit=None, + overwrite=False, + verbose=False, + avoid_pwinds_mubar=False, + no_tidal=False, +): + """ + Calculates a grid of isothermal Parker wind models, by executing the + run() function in parallel. + + Parameters + ---------- + plname : str + Planet name (must have parameters stored in + $SUNBATHER_PROJECT_PATH/planets.txt). + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. + cores : int + Number of parallel processes to spawn (i.e., number of CPU cores). + mdot_list : list + The log10(mass-loss rate) grid in units of g s-1. + temp_list : list + The temperature grid in units of K. + sed_name : str + Name of SED file to use. If sed_name is 'real', we use the name as + given in the planets.txt file, but if sed_name is something else, + we advice to use a separate pdir folder for this. + fraction_hydrogen : float or None + Hydrogen abundance expressed as a fraction of the total. If a value is given, + Parker wind profiles will be calculated using p-winds standalone with a H/He + composition. If None is given, Parker wind profiles will be calculated + using the p-winds/Cloudy iterative method and the composition is + specified via the zdict argument. + zdict : dict + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + Will only be used if fraction_hydrogen is None, in which case the + p-winds/Cloudy iterative method is applied. + mu_conv : float + Convergence threshold expressed as the relative change in mu_bar + between iterations. Will only be used if fraction_hydrogen is None, in + which case the p-winds/Cloudy iterative method is applied. + mu_maxit : int + Maximum number of iterations for the p-winds/Cloudy iterative method. Will only + be used if fraction_hydrogen is None. + overwrite : bool + Whether to overwrite existing models. + verbose : bool + Whether to print diagnostics about the convergence of mu_bar. + avoid_pwinds_mubar : bool + Whether to avoid using p-winds to calculate mu_bar during the first iteration, + when using the p-winds/Cloudy iterative method. Will only be used if + fraction_hydrogen is None. + If True, we guess the mu_bar of the first iteration based on a + completely neutral atmosphere. This can be helpful in cases where + p-winds solver cannot find a solution, but Cloudy typically can. + no_tidal : bool + Whether to neglect tidal gravity - fourth term of Eq. 4 of Linssen et + al. (2024). See also Appendix D of Vissapragada et al. (2022) for the + p-winds implementation. + """ + + pars = [] + for mdot in mdot_list: + for temp in temp_list: + pars.append( + ( + plname, + pdir, + mdot, + temp, + sed_name, + fraction_hydrogen, + zdict, + mu_conv, + mu_maxit, + overwrite, + verbose, + avoid_pwinds_mubar, + no_tidal, + ) + ) + + with multiprocessing.Pool(cores) as p: + p.starmap(catch_errors_run, pars) + p.close() + p.join() + + +def new_argument_parser(): + """ + Creates an argument parser for the main function. + """ + parser = argparse.ArgumentParser( + description="Creates 1D Parker profile(s) using the p_winds code and Cloudy.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + class AddDictAction(argparse.Action): + """ + Custom class to add an argparse argument to a dictionary. + """ + + def __call__(self, parser, namespace, values, option_string=None): + if ( + not hasattr(namespace, self.dest) + or getattr(namespace, self.dest) is None + ): + setattr(namespace, self.dest, {}) + for value in values: + key, val = value.split("=") + getattr(namespace, self.dest)[key] = float(val) + + parser.add_argument( + "-p", + "--plname", + required=True, + help="planet name (must be in planets.txt)" + ) + parser.add_argument( + "--pdir", + required=True, + help=( + "directory where the profiles are saved. It is advised to choose a name " + "that somehow represents the chosen parameters, e.g. 'fH_0.9' or 'z=10'. " + "The path will be $SUNBATHER_PROJECT_PATH/parker_profiles/pdir/" + ), + ) + parser.add_argument( + "-m", + "--mdot_lower", + required=True, + type=float, + help=( + "log10(mass-loss rate) (lower limit, in log10(g s-1)). " + "Will be rounded to three decimal places." + ), + ) + parser.add_argument( + "-M", + "--mdot_upper", + default=None, + type=float, + help=( + "log10(mass-loss rate) (upper limit, in log10(g s-1)). " + "Will be rounded to three decimal places." + ), + ) + parser.add_argument( + "--mdot_step", + default=None, + type=float, + help=( + "step size to take for the mass loss rate grid (in log10(g s-1))." + ), + ) + parser.add_argument( + "-t", + "--temp_lower", + required=True, + type=float, + help=( + "temperature lower limit, in K" + ), + ) + parser.add_argument( + "-T", + "--temp_upper", + default=None, + type=float, + help=( + "temperature upper limit, in K" + ), + ) + parser.add_argument( + "--temp_step", + default=None, + type=float, + help=( + "temperature step size, in K" + ), + ) + parser.add_argument( + "--sed_name", + type=str, + default="real", + help=( + "name of SED to use. Must be in Cloudy's data/SED/ folder " + "[default=SEDname set in planet.txt file]" + ), + ) + parser.add_argument( + "--overwrite", + action="store_true", + help="overwrite existing profile if passed [default=False]", + ) + composition_group = parser.add_mutually_exclusive_group(required=True) + composition_group.add_argument( + "--fH", + dest="fraction_hydrogen", + type=float, + help=( + "hydrogen fraction by number. Using this command results in running " + "standalone p_winds without invoking Cloudy." + ), + ) + composition_group.add_argument( + "-z", + "--metallicity", + type=float, + help=( + "metallicity (=scale factor relative to solar for all elements except H " + "and He). Using this command results in running p_winds in an iterative " + "scheme where Cloudy updates the mu parameter." + ), + ) + parser.add_argument( + "--zelem", + action=AddDictAction, + nargs="+", + default={}, + help=( + "abundance scale factor for specific elements, e.g. -zelem Fe=10 -zelem " + "He=0.01. Can also be used to toggle elements off, e.g. -zelem Ca=0. " + "Combines with -z argument. Using this command results in running p_winds " + "in an iterative scheme where Cloudy updates the mu parameter." + ), + ) + parser.add_argument( + "--cores", type=int, default=1, help="number of parallel runs" + ) + parser.add_argument( + "--mu_conv", + type=float, + default=0.01, + help=( + "relative change in mu allowed for convergence, when using p_winds/Cloudy " + "iterative scheme" + ), + ) + parser.add_argument( + "--mu_maxit", + type=int, + default=7, + help=( + "maximum number of iterations the p_winds/Cloudy iterative scheme is ran " + "if convergence is not reached" + ), + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="print out mu-bar values of each iteration", + ) + parser.add_argument( + "--avoid_pwinds_mubar", + action="store_true", + help=( + "avoid using the mu-bar value predicted by p-winds for the first " + "iteration. Instead, start with a mu_bar of a completely neutral " + "atmosphere. Helps to avoid the p-winds 'solve_ivp' errors. You may need " + "to use a -mu_maxit higher than 7 when toggling this on." + ), + ) + parser.add_argument( + "--no_tidal", + action="store_true", + help="neglect the stellar tidal gravity term", + ) + return parser + + +def main(**kwargs): + """ + Main function to construct a Parker profile. + """ + t0 = time.time() + parser = new_argument_parser() + if not kwargs: + args = parser.parse_args(sys.argv[1:]) + else: + args = kwargs + + if args.z is not None: + zdict = tools.get_zdict(z=args.z, zelem=args.zelem) + else: # if z==None we should not pass that to the tools.get_zdict function + zdict = tools.get_zdict(zelem=args.zelem) + + if args.fraction_hydrogen is not None and ( + args.zelem != {} + or args.mu_conv != 0.01 + or args.mu_maxit != 7 + or args.avoid_pwinds_mubar + ): + warnings.warn( + "The 'zelem', 'mu_conv', 'mu_maxit', and 'avoid_pwinds_mubar' arguments " + "only combine with 'z', not with 'fraction_hydrogen', so I will " + "ignore their input." + ) + + # set up the folder structure if it doesn't exist yet + projectpath = tools.get_sunbather_project_path() + if not os.path.isdir(projectpath + "/parker_profiles/"): + os.mkdir(projectpath + "/parker_profiles") + if not os.path.isdir(projectpath + "/parker_profiles/" + args.plname + "/"): + os.mkdir(projectpath + "/parker_profiles/" + args.plname) + if not os.path.isdir( + f"{projectpath}/parker_profiles/{args.plname}/{args.pdir}/" + ): + os.mkdir( + f"{projectpath}/parker_profiles/{args.plname}/{args.pdir}/" + ) + if (args.fH is None) and ( + not os.path.isdir( + f"{projectpath}/parker_profiles/{args.plname}/{args.pdir}/temp/" + ) + ): + os.mkdir( + f"{projectpath}/parker_profiles/{args.plname}/{args.pdir}/temp" + ) + + mdot = np.arange( + args.mdot_lower, args.mdot_upper + 1e-6, args.mdot_step + ) # 1e-6 so that upper bound is inclusive + temp = np.arange( + args.temp_lower, args.temp_upper + 1e-6, args.temp_step + ).astype(int) + + run_models( + args.plname, + args.pdir, + args.cores, + mdot, + temp, + args.sed_name, + args.fraction_hydrogen, + zdict, + args.mu_conv, + args.mu_maxit, + args.overwrite, + args.verbose, + args.avoid_pwinds_mubar, + args.no_tidal, + ) + + print( + "\nCalculations took", + int(time.time() - t0) // 3600, + "hours, ", + (int(time.time() - t0) % 3600) // 60, + "minutes and ", + (int(time.time() - t0) % 60), + "seconds.\n", + ) + + +if __name__ == "__main__": + main() diff --git a/src/sunbather/convergeT_parker.py b/src/sunbather/convergeT_parker.py new file mode 100644 index 0000000..e286e92 --- /dev/null +++ b/src/sunbather/convergeT_parker.py @@ -0,0 +1,869 @@ +""" +ConvergeT_parker module of sunbather +""" +import sys +import multiprocessing +from shutil import copyfile +import time +import os +import re +import argparse +import traceback +import pandas as pd +import numpy as np + +# sunbather imports +from sunbather import tools, solveT + + +def find_close_model(parentfolder, T, Mdot, tolT=2000, tolMdot=1.0): + """ + Takes a parent folder where multiple 1D parker profiles have been ran, + and for given T and Mdot it looks for another model that is already + finished and closest to the given model, so that we can start our new + simulation from that converged temperature structure. It returns the T and + Mdot of the close converged folder, or None if there aren't any (within the + tolerance). + + Parameters + ---------- + parentfolder : str + Parent folder containing sunbather simulations within folders with the + parker_*T0*_*Mdot* name format. + T : numeric + Target isothermal temperature in units of K. + Mdot : numeric + log of the target mass-loss rate in units of g s-1. + tolT : numeric, optional + Maximum T0 difference with the target temperature, by default 2000 K + tolMdot : numeric, optional + Maximum log10(Mdot) difference with the target mass-loss rate, by + default 1 dex + + Returns + ------- + clconv : list + [T0, Mdot] of the closest found finished model, or [None, None] if none + were found within the tolerance. + """ + + pattern = re.compile( + r"parker_\d+_\d+\.\d{3}$" + ) # this is how folder names should be + all_files_and_folders = os.listdir(parentfolder) + allfolders = [ + os.path.join(parentfolder, folder) + "/" + for folder in all_files_and_folders + if pattern.match(folder) and os.path.isdir(os.path.join(parentfolder, folder)) + ] + + convergedfolders = ( + [] + ) # stores the T and Mdot values of all folders with 0.out files + for folder in allfolders: + if os.path.isfile(folder + "converged.out"): + folderparams = folder.split("/")[-2].split("_") + convergedfolders.append([int(folderparams[1]), float(folderparams[2])]) + + if [ + int(T), + float(Mdot), + ] in convergedfolders: # if the current folder is found, remove it + convergedfolders.remove([int(T), float(Mdot)]) + + if not convergedfolders: # then we default to constant starting value + clconv = [None, None] + else: # find closest converged profile + dist = ( + lambda x, y: (x[0] - y[0]) ** 2 + (2000 * (x[1] - y[1])) ** 2 + ) # 1 order of magnitude Mdot is now 'equal weighted' to 2000K + clconv = min( + convergedfolders, key=lambda fol: dist(fol, [int(T), float(Mdot)]) + ) # closest converged [T, Mdot] + if (np.abs(clconv[0] - int(T)) > tolT) or ( + np.abs(clconv[1] - float(Mdot)) > tolMdot + ): + clconv = [None, None] + + return clconv + + +def run_s( + plname, + Mdot, + T, + itno, + fc, + workingdir, + SEDname, + overwrite, + startT, + pdir, + zdict=None, + altmax=8, + save_sp=None, + constantT=False, + maxit=16, +): + """ + Solves for a nonisothermal temperature profile of a single isothermal + Parker wind (density and velocity) profile. + + Parameters + ---------- + plname : str + Planet name (must have parameters stored in + $SUNBATHER_PROJECT_PATH/planets.txt). + Mdot : str or numeric + log of the mass-loss rate in units of g s-1. + T : str or int + Temperature in units of g s-1. + itno : int + Iteration number to start from (can only be different from 1 + if this same model has been ran before, and then also + overwrite = True needs to be set). If value is 0, will automatically + look for the highest iteration number to start from. + fc : numeric + H/C convergence factor, see Linssen et al. (2024). A sensible value is 1.1. + workingdir : str + Directory as $SUNBATHER_PROJECT_PATH/sims/1D/planetname/*workingdir*/ + where the temperature profile will be solved. A folder named + parker_*T*_*Mdot*/ will be made there. + SEDname : str + Name of SED file to use. If SEDname='real', we use the name as + given in the planets.txt file, but if SEDname is something else, + we advice to use a separate dir folder for this. + overwrite : bool + Whether to overwrite if this simulation already exists. + startT : str + Either 'constant', 'free' or 'nearby'. Sets the initial + temperature profile guessed/used for the first iteration. + 'constant' sets it equal to the parker wind isothermal value. + 'free' lets Cloudy solve it, so you will get the radiative equilibrium + structure. 'nearby' looks in the workingdir folder for previously solved + Parker wind profiles and starts from a converged one. Then, if no + converged ones are available, uses 'free' instead. + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ + where we take the isothermal parker wind density and velocity profiles from. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. + zdict : dict, optional + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + Default is None, which results in a solar composition. + altmax : int, optional + Maximum altitude of the simulation in units of planet radius, by default 8 + save_sp : list, optional + A list of atomic/ionic species to let Cloudy save the number density profiles + for. Those are needed when doing radiative transfer to produce + transmission spectra. For example, to be able to make + metastable helium spectra, 'He' needs to be in the save_sp list. By default []. + constantT : bool, optional + If True, instead of sovling for a nonisothermal temperature profile, + the Parker wind profile is ran at the isothermal value. By default False. + maxit : int, optional + Maximum number of iterations, by default 16. + """ + if save_sp is None: + save_sp = [] + + Mdot = f"{float(Mdot):.3f}" # enforce this format to get standard file names. + T = str(T) + + # set up the planet object + planet = tools.Planet(plname) + if SEDname != "real": + planet.set_var(SEDname=SEDname) + + # set up the folder structure + projectpath = tools.get_sunbather_project_path() + pathTstruc = projectpath + "/sims/1D/" + planet.name + "/" + workingdir + "/" + path = pathTstruc + "parker_" + T + "_" + Mdot + "/" + + # check if this parker profile exists in the given pdir + try: + pprof = tools.read_parker(planet.name, T, Mdot, pdir) + except FileNotFoundError: + print( + "This parker profile does not exist:", + projectpath + + "/parker_profiles/" + + planet.name + + "/" + + pdir + + "/pprof_" + + planet.name + + "_T=" + + str(T) + + "_M=" + + Mdot + + ".txt", + ) + return # quit the run_s function but not the code + + # check for overwriting + if os.path.isdir(path): # the simulation exists already + if not overwrite: + print( + "Simulation already exists and overwrite = False:", + plname, workingdir, Mdot, T + ) + # this quits the function but if we're running a grid, it doesn't + # quit the whole Python code + return + else: + os.makedirs(path[:-1]) # make the folder + + # get profiles and parameters we need for the input file + alt = pprof.alt.values + hden = tools.rho_to_hden(pprof.rho.values, abundances=tools.get_abundances(zdict)) + dlaw = tools.alt_array_to_Cloudy(alt, hden, altmax, planet.R, 1000, log=True) + + nuFnu_1AU_linear, Ryd = tools.get_SED_norm_1AU(planet.SEDname) + nuFnu_a_log = np.log10( + nuFnu_1AU_linear / ((planet.a - altmax * planet.R) / tools.AU) ** 2 + ) + + comments = ( + "# plname=" + + planet.name + + "\n# parker_T=" + + str(T) + + "\n# parker_Mdot=" + + str(Mdot) + + "\n# parker_dir=" + + pdir + + "\n# altmax=" + + str(altmax) + ) + + # this will run the profile at the isothermal T value instead of converging + # a nonisothermal profile + if ( + constantT + ): + if save_sp == []: + tools.write_Cloudy_in( + path + "constantT", + title=planet.name + + " 1D Parker with T=" + + str(T) + + " and log(Mdot)=" + + str(Mdot), + flux_scaling=[nuFnu_a_log, Ryd], + SED=planet.SEDname, + dlaw=dlaw, + double_tau=True, + overwrite=overwrite, + cosmic_rays=True, + zdict=zdict, + comments=comments, + constantT=T, + ) + else: + tools.write_Cloudy_in( + path + "constantT", + title=planet.name + + " 1D Parker with T=" + + str(T) + + " and log(Mdot)=" + + str(Mdot), + flux_scaling=[nuFnu_a_log, Ryd], + SED=planet.SEDname, + dlaw=dlaw, + double_tau=True, + overwrite=overwrite, + cosmic_rays=True, + zdict=zdict, + comments=comments, + constantT=T, + outfiles=[".den", ".en"], + denspecies=save_sp, + selected_den_levels=True, + ) + + tools.run_Cloudy("constantT", folder=path) # run the Cloudy simulation + return + + # if we got to here, we are not doing a constantT simulation, so we set up + # the convergence scheme files + # write Cloudy template input file - each iteration will add their current + # temperature structure to this template + tools.write_Cloudy_in( + path + "template", + title=planet.name + + " 1D Parker with T=" + + str(T) + + " and log(Mdot)=" + + str(Mdot), + flux_scaling=[nuFnu_a_log, Ryd], + SED=planet.SEDname, + dlaw=dlaw, + double_tau=True, + overwrite=overwrite, + cosmic_rays=True, + zdict=zdict, + comments=comments, + ) + + if ( + itno == 0 + ): # this means we resume from the highest found previously ran iteration + pattern = ( + r"iteration(\d+)\.out" # search pattern: iteration followed by an integer + ) + max_iteration = -1 # set an impossible number + for filename in os.listdir(path): # loop through all files/folder in the path + if os.path.isfile( + os.path.join(path, filename) + ): # if it is a file (not a folder) + if re.search(pattern, filename): # if it matches the pattern + iteration_number = int( + re.search(pattern, filename).group(1) + ) # extract the iteration number + max_iteration = max(max_iteration, iteration_number) + if max_iteration == -1: # this means no files were found + print( + f"This folder does not contain any iteration files {path}, so I cannot " + f"resume from the highest one. Will instead start at itno = 1." + ) + itno = 1 + else: + print( + f"Found the highest iteration {path}iteration{max_iteration}, will " + f"resume at that same itno." + ) + itno = max_iteration + + if itno == 1: + # get starting temperature structure + clconv = find_close_model( + pathTstruc, T, Mdot + ) # find if there are any nearby models we can start from + if startT == "constant": # then we start with the isothermal value + tools.copyadd_Cloudy_in(path + "template", path + "iteration1", constantT=T) + + elif ( + clconv == [None, None] or startT == "free" + ): # then we start in free (=radiative eq.) mode + copyfile(path + "template.in", path + "iteration1.in") + + # then clconv cannot be [None, None] and we start from a previous + # converged T(r) + elif ( + startT == "nearby" + ): + print( + f"Model {path} starting from previously converged temperature profile: " + f"T0 = {clconv[0]}, Mdot = {clconv[1]}" + ) + prev_conv_T = pd.read_table( + pathTstruc + + "parker_" + + str(clconv[0]) + + "_" + + f"{clconv[1]:.3f}" + + "/converged.txt", + delimiter=" ", + ) + Cltlaw = tools.alt_array_to_Cloudy( + prev_conv_T.R * planet.R, prev_conv_T.Te, altmax, planet.R, 1000 + ) + tools.copyadd_Cloudy_in(path + "template", path + "iteration1", tlaw=Cltlaw) + + # with everything in order, run the actual temperature convergence scheme + solveT.run_loop(path, itno, fc, save_sp, maxit) + + +def run( + plname, + mdot=None, + temp=None, + itno=1, + fc=1.1, + workingdir=None, + sedname="real", + overwrite=False, + start_temp="nearby", + pdir=None, + z=None, + zelem=None, + altmax=8, + save_sp=None, + constant_temp=False, + maxit=20, +): + if zelem is None: + zelem = {} + zdict = tools.get_zdict(z=z, zelem=zelem) + run_s( + plname, + mdot, + temp, + itno, + fc, + workingdir, + sedname, + overwrite, + start_temp, + pdir, + zdict=zdict, + altmax=altmax, + save_sp=save_sp, + constantT=constant_temp, + maxit=maxit, + ) + + +def catch_errors_run_s(*args): + """ + Executes the run_s() function with provided arguments, while catching + errors more gracefully. + """ + + try: + run_s(*args) + except Exception as e: + traceback.print_exc() + + +def run_g( + plname, + cores, + Mdot_l, + Mdot_u, + Mdot_s, + T_l, + T_u, + T_s, + fc, + workingdir, + SEDname, + overwrite, + startT, + pdir, + zdict, + altmax, + save_sp, + constantT, + maxit, +): + """ + Solves for a nonisothermal temperature profile of a grid of isothermal + Parker wind models, by executing the run_s() function in parallel. + + Parameters + ---------- + plname : str + Planet name (must have parameters stored in + $SUNBATHER_PROJECT_PATH/planets.txt). + cores : int + Number of parallel processes to spawn (i.e., number of CPU cores). + Mdot_l : str or numeric + Lower bound on the log10(mass-loss rate) grid in units of g s-1. + Mdot_u : str or numeric + Upper bound on the log10(mass-loss rate) grid in units of g s-1. + Mdot_s : str or numeric + Step size of the log10(mass-loss rate) grid in units of g s-1. + T_l : str or numeric + Lower bound on the temperature grid in units of K. + T_u : str or numeric + Upper bound on the temperature grid in units of K. + T_s : str or numeric + Step size of the temperature grid in units of K. + fc : numeric + H/C convergence factor, see Linssen et al. (2024). A sensible value is 1.1. + workingdir : str + Directory as $SUNBATHER_PROJECT_PATH/sims/1D/planetname/*workingdir*/ + where the temperature profile will be solved. A folder named + parker_*T*_*Mdot*/ will be made there. + SEDname : str + Name of SED file to use. If SEDname is 'real', we use the name as + given in the planets.txt file, but if SEDname is something else, + we advice to use a separate dir folder for this. + overwrite : bool + Whether to overwrite if this simulation already exists. + startT : str + Either 'constant', 'free' or 'nearby'. Sets the initial + temperature profile guessed/used for the first iteration. + 'constant' sets it equal to the parker wind isothermal value. + 'free' lets Cloudy solve it, so you will get the radiative equilibrium + structure. + 'nearby' looks in the workingdir folder for previously solved + Parker wind profiles and starts from a converged one. Then, if no converged + ones are available, uses 'free' instead. + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/planetname/*pdir*/ + where we take the isothermal parker wind density and velocity profiles from. + Different folders may exist there for a given planet, to separate for + example profiles with different assumptions such as stellar + SED/semi-major axis/composition. + zdict : dict, optional + Dictionary with the scale factors of all elements relative + to the default solar composition. Can be easily created with tools.get_zdict(). + Default is None, which results in a solar composition. + altmax : int, optional + Maximum altitude of the simulation in units of planet radius, by default 8 + save_sp : list, optional + A list of atomic/ionic species to let Cloudy save the number density profiles + for. Those are needed when doing radiative transfer to produce + transmission spectra. For example, to be able to make + metastable helium spectra, 'He' needs to be in the save_sp list. By default []. + constantT : bool, optional + If True, instead of sovling for a nonisothermal temperature profile, + the Parker wind profile is ran at the isothermal value. By default False. + maxit : int, optional + Maximum number of iterations, by default 16. + """ + + with multiprocessing.Pool(processes=cores) as pool: + pars = [] + for Mdot in np.arange( + float(Mdot_l), float(Mdot_u) + 1e-6, float(Mdot_s) + ): # 1e-6 so that upper bound is inclusive + for T in np.arange(int(T_l), int(T_u) + 1e-6, int(T_s)).astype(int): + pars.append( + ( + plname, + Mdot, + T, + 1, + fc, + workingdir, + SEDname, + overwrite, + startT, + pdir, + zdict, + altmax, + save_sp, + constantT, + maxit, + ) + ) + pool.starmap(catch_errors_run_s, pars) + pool.close() + pool.join() + + +def new_argument_parser(): + """ + Creates a new argument parser. + """ + parser = argparse.ArgumentParser( + description="Runs the temperature convergence for 1D Parker profile(s).", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + class OneOrThreeAction(argparse.Action): + """ + Custom class for an argparse argument with exactly 1 or 3 values. + """ + + def __call__(self, parser, namespace, values, option_string=None): + if len(values) not in (1, 3): + parser.error("Exactly one or three values are required.") + setattr(namespace, self.dest, values) + + class AddDictAction(argparse.Action): + """ + Custom class to add an argparse argument to a dictionary. + """ + + def __call__(self, parser, namespace, values, option_string=None): + if ( + not hasattr(namespace, self.dest) + or getattr(namespace, self.dest) is None + ): + setattr(namespace, self.dest, {}) + for value in values: + key, val = value.split("=") + getattr(namespace, self.dest)[key] = float(val) + + parser.add_argument( + "-plname", required=True, help="planet name (must be in planets.txt)" + ) + parser.add_argument( + "-dir", + required=True, + type=str, + dest="workingdir", + help=( + "folder where the temperature structures are solved. e.g. Tstruc_fH_0.9 or " + "Tstruc_z_100_3xEUV etc." + ), + ) + parser.add_argument( + "-pdir", + required=True, + type=str, + help="parker profile folder/dir to use, e.g. fH_0.9 or z_100.", + ) + parser.add_argument( + "-Mdot", + required=True, + type=float, + nargs="+", + action=OneOrThreeAction, + help=( + "log10(mass-loss rate), or three values specifying a grid of " + "mass-loss rates: lowest, highest, stepsize. -Mdot will be rounded to " + "three decimal places." + ), + ) + parser.add_argument( + "-T", + required=True, + type=int, + nargs="+", + action=OneOrThreeAction, + help=( + "temperature, or three values specifying a grid of temperatures: lowest, " + "highest, stepsize." + ), + ) + parser.add_argument( + "-cores", type=int, default=1, help="number of parallel runs" + ) + parser.add_argument( + "-fc", + type=float, + default=1.1, + help="convergence factor (heat/cool should be below this value)", + ) + parser.add_argument( + "-startT", + choices=["nearby", "free", "constant"], + default="nearby", + help=( + "initial T structure, either 'constant', 'free' or 'nearby'" + ), + ) + parser.add_argument( + "-itno", + type=int, + default=1, + help=( + "starting iteration number (itno != 1 only works with -overwrite). As a " + "special use, you can pass -itno 0 which will automatically find the " + "highest previously ran iteration number" + ), + ) + parser.add_argument( + "-maxit", + type=int, + default=20, + help="maximum number of iterations", + ) + parser.add_argument( + "-SEDname", + type=str, + default="real", + help=( + "name of SED to use. Must be in Cloudy's data/SED/ folder" + ), + ) + parser.add_argument( + "-overwrite", + action="store_true", + help="overwrite existing simulation if passed", + ) + parser.add_argument( + "-z", + type=float, + default=1.0, + help=( + "metallicity (=scale factor relative to solar for all elements except H " + "and He)" + ), + ) + parser.add_argument( + "-zelem", + action=AddDictAction, + nargs="+", + default={}, + help=( + "abundance scale factor for specific elements, e.g. -zelem Fe=10 -zelem " + "He=0.01. Can also be used to toggle elements off, e.g. -zelem Ca=0. " + "Combines with -z argument. Using this command results in running p_winds " + "in an iterative scheme where Cloudy updates the mu parameter." + ), + ) + parser.add_argument( + "-altmax", + type=int, + default=8, + help="maximum altitude of the simulation in units of Rp.", + ) + parser.add_argument( + "-save_sp", + type=str, + nargs="+", + default=["all"], + help=( + "atomic or ionic species to save densities for (needed for radiative " + "transfer). You can add multiple as e.g. -save_sp He Ca+ Fe3+ Passing " + "'all' includes all species that weren't turned off. In that case, you can " + "set the maximum degree of ionization with the -save_sp_max_ion flag. " + ), + ) + parser.add_argument( + "-save_sp_max_ion", + type=int, + default=6, + help=( + "only used when you set -save_sp all This command sets the maximum " + "degree of ionization that will be saved. [default=6] but using lower " + "values saves significant file size if high ions are not needed. The " + "maximum number is 12, but such highly ionized species only occur at very " + "high XUV flux, such as in young systems." + ), + ) + parser.add_argument( + "-constantT", + action="store_true", + help=( + "run the profile at the isothermal temperature instead of converging upon " + "the temperature structure." + ), + ) + + return parser + + +def main(**kwargs): + """ + Main function for the convergeT_parker.py script + """ + + t0 = time.time() + + parser = new_argument_parser() + if not kwargs: + args = parser.parse_args(sys.argv[1:]) + else: + args = kwargs + + zdict = tools.get_zdict(z=args.z, zelem=args.zelem) + + if "all" in args.save_sp: + args.save_sp = tools.get_specieslist( + exclude_elements=[sp for sp, zval in zdict.items() if zval == 0.0], + max_ion=args.save_sp_max_ion, + ) + + # set up the folder structure if it doesn't exist yet + projectpath = tools.get_sunbather_project_path() + if not os.path.isdir(projectpath + "/sims/"): + os.mkdir(projectpath + "/sims") + if not os.path.isdir(projectpath + "/sims/1D/"): + os.mkdir(projectpath + "/sims/1D") + if not os.path.isdir(projectpath + "/sims/1D/" + args.plname + "/"): + os.mkdir(projectpath + "/sims/1D/" + args.plname) + if not os.path.isdir( + projectpath + "/sims/1D/" + args.plname + "/" + args.dir + "/" + ): + os.mkdir(projectpath + "/sims/1D/" + args.plname + "/" + args.dir) + + if len(args.T) == 1 and len(args.Mdot) == 1: # then we run a single model + run_s( + args.plname, + args.Mdot[0], + str(args.T[0]), + args.itno, + args.fc, + args.workingdir, + args.SEDname, + args.overwrite, + args.startT, + args.pdir, + zdict, + args.altmax, + args.save_sp, + args.constantT, + args.maxit, + ) + elif ( + len(args.T) == 3 and len(args.Mdot) == 3 + ): # then we run a grid over both parameters + run_g( + args.plname, + args.cores, + args.Mdot[0], + args.Mdot[1], + args.Mdot[2], + args.T[0], + args.T[1], + args.T[2], + args.fc, + args.workingdir, + args.SEDname, + args.overwrite, + args.startT, + args.pdir, + zdict, + args.altmax, + args.save_sp, + args.constantT, + args.maxit, + ) + elif len(args.T) == 3 and len(args.Mdot) == 1: # then we run a grid over only T + run_g( + args.plname, + args.cores, + args.Mdot[0], + args.Mdot[0], + args.Mdot[0], + args.T[0], + args.T[1], + args.T[2], + args.fc, + args.workingdir, + args.SEDname, + args.overwrite, + args.startT, + args.pdir, + zdict, + args.altmax, + args.save_sp, + args.constantT, + args.maxit, + ) + elif len(args.T) == 1 and len(args.Mdot) == 3: # then we run a grid over only Mdot + run_g( + args.plname, + args.cores, + args.Mdot[0], + args.Mdot[1], + args.Mdot[2], + args.T[0], + args.T[0], + args.T[0], + args.fc, + args.workingdir, + args.SEDname, + args.overwrite, + args.startT, + args.pdir, + zdict, + args.altmax, + args.save_sp, + args.constantT, + args.maxit, + ) + + print( + "\nCalculations took", + int(time.time() - t0) // 3600, + "hours, ", + (int(time.time() - t0) % 3600) // 60, + "minutes and ", + (int(time.time() - t0) % 60), + "seconds.\n", + ) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/stellar_SEDs/GJ1132_binned.spec b/src/sunbather/data/stellar_SEDs/GJ1132_binned.spec similarity index 100% rename from stellar_SEDs/GJ1132_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ1132_binned.spec diff --git a/stellar_SEDs/GJ1214_binned.spec b/src/sunbather/data/stellar_SEDs/GJ1214_binned.spec similarity index 100% rename from stellar_SEDs/GJ1214_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ1214_binned.spec diff --git a/stellar_SEDs/GJ15A_binned.spec b/src/sunbather/data/stellar_SEDs/GJ15A_binned.spec similarity index 100% rename from stellar_SEDs/GJ15A_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ15A_binned.spec diff --git a/stellar_SEDs/GJ163_binned.spec b/src/sunbather/data/stellar_SEDs/GJ163_binned.spec similarity index 100% rename from stellar_SEDs/GJ163_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ163_binned.spec diff --git a/stellar_SEDs/GJ176_binned.spec b/src/sunbather/data/stellar_SEDs/GJ176_binned.spec similarity index 100% rename from stellar_SEDs/GJ176_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ176_binned.spec diff --git a/stellar_SEDs/GJ436_binned.spec b/src/sunbather/data/stellar_SEDs/GJ436_binned.spec similarity index 100% rename from stellar_SEDs/GJ436_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ436_binned.spec diff --git a/stellar_SEDs/GJ581_binned.spec b/src/sunbather/data/stellar_SEDs/GJ581_binned.spec similarity index 100% rename from stellar_SEDs/GJ581_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ581_binned.spec diff --git a/stellar_SEDs/GJ649_binned.spec b/src/sunbather/data/stellar_SEDs/GJ649_binned.spec similarity index 100% rename from stellar_SEDs/GJ649_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ649_binned.spec diff --git a/stellar_SEDs/GJ667C_binned.spec b/src/sunbather/data/stellar_SEDs/GJ667C_binned.spec similarity index 100% rename from stellar_SEDs/GJ667C_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ667C_binned.spec diff --git a/stellar_SEDs/GJ674_binned.spec b/src/sunbather/data/stellar_SEDs/GJ674_binned.spec similarity index 100% rename from stellar_SEDs/GJ674_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ674_binned.spec diff --git a/stellar_SEDs/GJ676A_binned.spec b/src/sunbather/data/stellar_SEDs/GJ676A_binned.spec similarity index 100% rename from stellar_SEDs/GJ676A_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ676A_binned.spec diff --git a/stellar_SEDs/GJ699_binned.spec b/src/sunbather/data/stellar_SEDs/GJ699_binned.spec similarity index 100% rename from stellar_SEDs/GJ699_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ699_binned.spec diff --git a/stellar_SEDs/GJ729_binned.spec b/src/sunbather/data/stellar_SEDs/GJ729_binned.spec similarity index 100% rename from stellar_SEDs/GJ729_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ729_binned.spec diff --git a/stellar_SEDs/GJ832_binned.spec b/src/sunbather/data/stellar_SEDs/GJ832_binned.spec similarity index 100% rename from stellar_SEDs/GJ832_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ832_binned.spec diff --git a/stellar_SEDs/GJ849_binned.spec b/src/sunbather/data/stellar_SEDs/GJ849_binned.spec similarity index 100% rename from stellar_SEDs/GJ849_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ849_binned.spec diff --git a/stellar_SEDs/GJ876_binned.spec b/src/sunbather/data/stellar_SEDs/GJ876_binned.spec similarity index 100% rename from stellar_SEDs/GJ876_binned.spec rename to src/sunbather/data/stellar_SEDs/GJ876_binned.spec diff --git a/stellar_SEDs/HATP12_binned.spec b/src/sunbather/data/stellar_SEDs/HATP12_binned.spec similarity index 100% rename from stellar_SEDs/HATP12_binned.spec rename to src/sunbather/data/stellar_SEDs/HATP12_binned.spec diff --git a/stellar_SEDs/HATP26_binned.spec b/src/sunbather/data/stellar_SEDs/HATP26_binned.spec similarity index 100% rename from stellar_SEDs/HATP26_binned.spec rename to src/sunbather/data/stellar_SEDs/HATP26_binned.spec diff --git a/stellar_SEDs/HD149026_binned.spec b/src/sunbather/data/stellar_SEDs/HD149026_binned.spec similarity index 100% rename from stellar_SEDs/HD149026_binned.spec rename to src/sunbather/data/stellar_SEDs/HD149026_binned.spec diff --git a/stellar_SEDs/HD40307_binned.spec b/src/sunbather/data/stellar_SEDs/HD40307_binned.spec similarity index 100% rename from stellar_SEDs/HD40307_binned.spec rename to src/sunbather/data/stellar_SEDs/HD40307_binned.spec diff --git a/stellar_SEDs/HD85512_binned.spec b/src/sunbather/data/stellar_SEDs/HD85512_binned.spec similarity index 100% rename from stellar_SEDs/HD85512_binned.spec rename to src/sunbather/data/stellar_SEDs/HD85512_binned.spec diff --git a/stellar_SEDs/HD97658_binned.spec b/src/sunbather/data/stellar_SEDs/HD97658_binned.spec similarity index 100% rename from stellar_SEDs/HD97658_binned.spec rename to src/sunbather/data/stellar_SEDs/HD97658_binned.spec diff --git a/stellar_SEDs/K4_binned.spec b/src/sunbather/data/stellar_SEDs/K4_binned.spec similarity index 100% rename from stellar_SEDs/K4_binned.spec rename to src/sunbather/data/stellar_SEDs/K4_binned.spec diff --git a/stellar_SEDs/L-678-39_binned.spec b/src/sunbather/data/stellar_SEDs/L-678-39_binned.spec similarity index 100% rename from stellar_SEDs/L-678-39_binned.spec rename to src/sunbather/data/stellar_SEDs/L-678-39_binned.spec diff --git a/stellar_SEDs/L-98-59_binned.spec b/src/sunbather/data/stellar_SEDs/L-98-59_binned.spec similarity index 100% rename from stellar_SEDs/L-98-59_binned.spec rename to src/sunbather/data/stellar_SEDs/L-98-59_binned.spec diff --git a/stellar_SEDs/L-980-5_binned.spec b/src/sunbather/data/stellar_SEDs/L-980-5_binned.spec similarity index 100% rename from stellar_SEDs/L-980-5_binned.spec rename to src/sunbather/data/stellar_SEDs/L-980-5_binned.spec diff --git a/stellar_SEDs/LHS-2686_binned.spec b/src/sunbather/data/stellar_SEDs/LHS-2686_binned.spec similarity index 100% rename from stellar_SEDs/LHS-2686_binned.spec rename to src/sunbather/data/stellar_SEDs/LHS-2686_binned.spec diff --git a/stellar_SEDs/LP-791-18_binned.spec b/src/sunbather/data/stellar_SEDs/LP-791-18_binned.spec similarity index 100% rename from stellar_SEDs/LP-791-18_binned.spec rename to src/sunbather/data/stellar_SEDs/LP-791-18_binned.spec diff --git a/stellar_SEDs/TOI193_binned.spec b/src/sunbather/data/stellar_SEDs/TOI193_binned.spec similarity index 100% rename from stellar_SEDs/TOI193_binned.spec rename to src/sunbather/data/stellar_SEDs/TOI193_binned.spec diff --git a/stellar_SEDs/TOI2134.spec b/src/sunbather/data/stellar_SEDs/TOI2134.spec similarity index 100% rename from stellar_SEDs/TOI2134.spec rename to src/sunbather/data/stellar_SEDs/TOI2134.spec diff --git a/stellar_SEDs/TRAPPIST-1_binned.spec b/src/sunbather/data/stellar_SEDs/TRAPPIST-1_binned.spec similarity index 100% rename from stellar_SEDs/TRAPPIST-1_binned.spec rename to src/sunbather/data/stellar_SEDs/TRAPPIST-1_binned.spec diff --git a/stellar_SEDs/WASP127_binned.spec b/src/sunbather/data/stellar_SEDs/WASP127_binned.spec similarity index 100% rename from stellar_SEDs/WASP127_binned.spec rename to src/sunbather/data/stellar_SEDs/WASP127_binned.spec diff --git a/stellar_SEDs/WASP17_binned.spec b/src/sunbather/data/stellar_SEDs/WASP17_binned.spec similarity index 100% rename from stellar_SEDs/WASP17_binned.spec rename to src/sunbather/data/stellar_SEDs/WASP17_binned.spec diff --git a/stellar_SEDs/WASP43_binned.spec b/src/sunbather/data/stellar_SEDs/WASP43_binned.spec similarity index 100% rename from stellar_SEDs/WASP43_binned.spec rename to src/sunbather/data/stellar_SEDs/WASP43_binned.spec diff --git a/stellar_SEDs/WASP77A_binned.spec b/src/sunbather/data/stellar_SEDs/WASP77A_binned.spec similarity index 100% rename from stellar_SEDs/WASP77A_binned.spec rename to src/sunbather/data/stellar_SEDs/WASP77A_binned.spec diff --git a/stellar_SEDs/eps_Eri_binned.spec b/src/sunbather/data/stellar_SEDs/eps_Eri_binned.spec similarity index 100% rename from stellar_SEDs/eps_Eri_binned.spec rename to src/sunbather/data/stellar_SEDs/eps_Eri_binned.spec diff --git a/stellar_SEDs/solar.spec b/src/sunbather/data/stellar_SEDs/solar.spec similarity index 100% rename from stellar_SEDs/solar.spec rename to src/sunbather/data/stellar_SEDs/solar.spec diff --git a/planets.txt b/src/sunbather/data/workingdir/planets.txt similarity index 100% rename from planets.txt rename to src/sunbather/data/workingdir/planets.txt diff --git a/src/sunbather/install_cloudy.py b/src/sunbather/install_cloudy.py new file mode 100644 index 0000000..e4348dd --- /dev/null +++ b/src/sunbather/install_cloudy.py @@ -0,0 +1,108 @@ +""" +Functions to download and compile Cloudy +""" +import os +import pathlib +from urllib.error import HTTPError +import urllib.request +import tarfile +import subprocess +import shutil + + +class GetCloudy: + """ + Class to download and compile the Cloudy program + """ + + def __init__(self, version="23.01"): + self.version = version + self.path = "./" + major = version.split(".")[0] + self.url = f"https://data.nublado.org/cloudy_releases/c{major}/" + self.filename = f"c{self.version}.tar.gz" + self.sunbatherpath = f"{pathlib.Path(__file__).parent.resolve()}" + self.cloudypath = f"{self.sunbatherpath}/cloudy/" + + def download(self): + """ + Creates the cloudy directory and downloads the cloudy version specified. + """ + if not pathlib.Path(self.cloudypath).is_dir(): + os.mkdir(self.cloudypath) + else: + print("Directory already exists! Checking if download is still needed...") + if os.path.exists(self.cloudypath + self.filename): + print("Already downloaded, skipping ahead.") + return + os.chdir(self.cloudypath) + try: + with urllib.request.urlopen(f"{self.url}{self.filename}") as g: + with open(self.filename, "b+w") as f: + f.write(g.read()) + except HTTPError: + print(f"Could not download Cloudy from {self.url}{self.filename}...") + return + # Go to the v23 download page and download the "c23.01.tar.gz" file + return + + def extract(self): + """ + Extracts Cloudy. + """ + os.chdir(self.cloudypath) + with tarfile.open(self.filename, "r:gz") as tar: + tar.extractall(filter="data") + + def compile(self): + """ + Compiles Cloudy. + """ + # move the gitversion.sh file to gitversion.sh.bak + # otherwise, Cloudy will report the current git hash as the version and + # we don't want that! + os.rename( + f"{self.cloudypath}/c{self.version}/source/gitversion.sh", + f"{self.cloudypath}/c{self.version}/source/gitversion.sh.bak" + ) + os.chdir(f"{self.cloudypath}/c{self.version}/source/") + with subprocess.Popen( + [ + "make", + ] + ) as p: + p.wait() + + def test(self): + """ + Quickly test the Cloudy installation: in the source folder, run + ./cloudy.exe, type "test" and hit return twice. It should print "Cloudy + exited OK" at the end. + """ + os.chdir(f"{self.cloudypath}/c{self.version}/source/") + with subprocess.Popen( + [ + "./cloudy.exe", + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) as p: + cloudy_output = p.communicate(input=b"test\n\n")[0] + p.wait() + try: + assert b"Cloudy exited OK" in cloudy_output + except AssertionError: + print("Cloudy did not test OK...") + else: + print("Cloudy tested OK.") + + def copy_data(self): + """ + Copy the stellar SEDs to the Cloudy data folder + """ + shutil.copytree( + f"{self.sunbatherpath}/data/stellar_SEDs/", + f"{self.cloudypath}/c{self.version}/data/SED/", + dirs_exist_ok=True + ) diff --git a/src/sunbather/solveT.py b/src/sunbather/solveT.py new file mode 100644 index 0000000..fd410f8 --- /dev/null +++ b/src/sunbather/solveT.py @@ -0,0 +1,973 @@ +import os +import warnings + +# other imports +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.lines import Line2D +from scipy.optimize import minimize_scalar +from scipy.interpolate import interp1d +import scipy.stats as sps + +# sunbather imports +from sunbather import tools + + +def calc_expansion(r, rho, v, Te, mu): + """ + Calculates expansion cooling (Linssen et al. 2024 Eq. 3 second term). + + Parameters + ---------- + r : numpy.ndarray + Radius in units of cm + rho : numpy.ndarray + Density in units of g cm-3 + v : numpy.ndarray + Velocity in units of cm s-1 + Te : numpy.ndarray + Temperature in units of K + mu : numpy.ndarray + Mean particle mass in units of amu + + Returns + ------- + expansion : numpy.ndarray + Expansion cooling rate. + """ + + expansion = tools.k / tools.mH * Te * v / mu * np.gradient(rho, r) + assert ( + np.max(expansion) <= 0 + ), "Found positive expansion cooling rates (i.e., heating)." + + return expansion + + +def calc_advection(r, rho, v, Te, mu): + """ + Calculates advection heating/cooling (Linssen et al. 2024 Eq. 3 first term). + + Parameters + ---------- + r : numpy.ndarray + Radius in units of cm + rho : numpy.ndarray + Density in units of g cm-3 + v : numpy.ndarray + Velocity in units of cm s-1 + Te : numpy.ndarray + Temperature in units of K + mu : numpy.ndarray + Mean particle mass in units of amu + + Returns + ------- + advection : numpy.ndarray + Advection heating/cooling rate. + """ + + advection = -1 * tools.k / (tools.mH * 2 / 3) * rho * v * np.gradient(Te / mu, r) + + return advection + + +def simtogrid(sim, grid): + """ + Extracts various needed quantities from a Cloudy simulation and interpolates + them onto the provided radius grid. + + Parameters + ---------- + sim : tools.Sim + Cloudy simulation. + grid : numpy.ndarray + Radius grid in units of cm. + + Returns + ------- + Te : numpy.ndarray + Temperature in units of K. + mu : numpy.ndarray + Mean particle mass in units of amu. + rho : numpy.ndarray + Density in units of g cm-3. + v : numpy.ndarray + Velocity in units of cm s-1. + radheat : numpy.ndarray + Radiative heating rate in units of erg s-1 cm-3. + radcool : numpy.ndarray + Radiative cooling rate in units of erg s-1 cm-3, as positive values. + expcool : numpy.ndarray + Expansion cooling rate in units of erg s-1 cm-3, as positive values. + advheat : numpy.ndarray + Advection heating rate in units of erg s-1 cm-3. + advcool : numpy.ndarray + Advection cooling rate in units of erg s-1 cm-3, as positive values. + """ + + # get Cloudy quantities + Te = interp1d(sim.ovr.alt, sim.ovr.Te, fill_value="extrapolate")(grid) + mu = interp1d( + sim.ovr.alt[sim.ovr.alt < 0.999 * sim.altmax * sim.p.R], + sim.ovr.mu[sim.ovr.alt < 0.999 * sim.altmax * sim.p.R], + fill_value="extrapolate", + )(grid) + radheat = interp1d(sim.ovr.alt, sim.cool.htot, fill_value="extrapolate")(grid) + radcool = interp1d(sim.ovr.alt, sim.cool.ctot, fill_value="extrapolate")(grid) + + # get isothermal Parker wind quantities + rho = interp1d(sim.par.prof.alt, sim.par.prof.rho, fill_value="extrapolate")(grid) + v = interp1d(sim.par.prof.alt, sim.par.prof.v, fill_value="extrapolate")(grid) + + # calculate bulk terms + expcool = -1 * calc_expansion( + grid, rho, v, Te, mu + ) # minus sign to get expansion cooling rates as positive values + adv = calc_advection(grid, rho, v, Te, mu) + + # apply very slight smoothing because the Cloudy .ovr quantities have + # mediocre reported numerical precision + expcool = tools.smooth_gaus_savgol(expcool, fraction=0.01) + adv = tools.smooth_gaus_savgol(adv, fraction=0.01) + + advheat, advcool = np.copy(adv), -1 * np.copy(adv) + advheat[advheat < 0] = 0.0 + advcool[advcool < 0] = 0.0 + + return Te, mu, rho, v, radheat, radcool, expcool, advheat, advcool + + +def calc_HCratio(radheat, radcool, expcool, advheat, advcool): + """ + Calculates the ratio of total heating to total cooling. + + Parameters + ---------- + radheat : numpy.ndarray + Radiative heating rate in units of erg s-1 cm-3. + radcool : numpy.ndarray + Radiative cooling rate in units of erg s-1 cm-3, as positive values. + expcool : numpy.ndarray + Expansion cooling rate in units of erg s-1 cm-3, as positive values. + advheat : numpy.ndarray + Advection heating rate in units of erg s-1 cm-3. + advcool : numpy.ndarray + Advection cooling rate in units of erg s-1 cm-3, as positive values. + + Returns + ------- + HCratio : numpy.ndarray + Total heating rate H divided by total cooling rate C when H > C, + or -C / H when C > H. The absolute value of HCratio is always >=1, + and the sign indicates whether heating or cooling is stronger. + """ + + totheat = radheat + advheat + totcool = radcool + expcool + advcool # all cooling rates are positive values + nettotal = totheat - totcool + + HCratio = ( + np.sign(nettotal) * np.maximum(totheat, totcool) / np.minimum(totheat, totcool) + ) + + return HCratio + + +def get_new_Tstruc(old_Te, HCratio, fac): + """ + Returns a new temperature profile based on a previous non-converged + temperature profile and the associated heating/cooling imbalance. + + Parameters + ---------- + old_Te : numpy.ndarray + Previous temperature profile in units of K. + HCratio : numpy.ndarray + Heating/cooling imbalance, output of the calc_HCratio() function. + fac : numpy.ndarray + Scaling factor that sets how large the temperature adjustment is. + + Returns + ------- + newTe : numpy.ndarray + New temperature profile. + """ + + deltaT = ( + fac * np.sign(HCratio) * np.log10(np.abs(HCratio)) + ) # take log-based approach to deltaT + fT = np.copy(deltaT) # the temperature multiplication fraction + fT[deltaT < 0] = 1 + deltaT[deltaT < 0] + fT[deltaT > 0] = 1 / (1 - deltaT[deltaT > 0]) + fT = np.clip(fT, 0.5, 2) # max change is a factor 2 up or down in temperature + newTe = old_Te * fT + newTe = np.clip(newTe, 1e1, 1e6) # set minimum temperature to 10K + + return newTe + + +def calc_cloc(radheat, radcool, expcool, advheat, advcool, HCratio): + """ + Checks if there is a point in the atmosphere where we can use + the construction algorithm. It searches for two criteria: + 1. If there is a point from where on advection heating is stronger than + radiative heating, and the temperature profile is reasonably converged. + 2. If there is a point from where on radiative cooling is weak + compared to expansion and advection cooling. + + Parameters + ---------- + radheat : numpy.ndarray + Radiative heating rate in units of erg s-1 cm-3. + radcool : numpy.ndarray + Radiative cooling rate in units of erg s-1 cm-3, as positive values. + expcool : numpy.ndarray + Expansion cooling rate in units of erg s-1 cm-3, as positive values. + advheat : numpy.ndarray + Advection heating rate in units of erg s-1 cm-3. + advcool : numpy.ndarray + Advection cooling rate in units of erg s-1 cm-3, as positive values. + HCratio : numpy.ndarray + Heating/cooling imbalance, output of the calc_HCratio() function. + + Returns + ------- + cloc : int + Index of the grid from where to start the construction algorithm. + """ + + def first_true_index(arr): + """ + Return the index of the first True value in the array. + If there are no True in the array, returns 0 + """ + return np.argmax(arr) + + def last_true_index(arr): + """ + Return the index of the last True value in the array. + If there are no True in the array, returns len(arr)-1 + """ + return len(arr) - np.argmax(arr[::-1]) - 1 + + def last_false_index(arr): + """ + Return the index of the last False value in the array. + If there are no False in the array, returns len(arr)-1 + """ + return len(arr) - np.argmax(~arr[::-1]) - 1 + + # check for advection dominated regime + adv_cloc = len(HCratio) # start by setting a 'too high' value + advheat_dominates = ( + advheat > radheat + ) # boolean array where advection heating dominates + bothrad_dominate = ( + (radheat > advheat) & (radcool > advcool) & (radcool > expcool) + ) # boolean array where radiative heating dominates AND radiative cooling dominates + highest_r_above_which_no_bothrad_dominate = last_true_index(bothrad_dominate) + advheat_dominates[:highest_r_above_which_no_bothrad_dominate] = ( + False + # now the boolean array stores where advection heating dominates AND + # where there is no point at higher altitudes that is rad. heat and + # rad. cool dominated + ) + if ( + True in advheat_dominates + ): # if there is no such point, adv_cloc stays default value + advdomloc = first_true_index( + advheat_dominates + ) # get lowest altitude location where advection dominates + advheat_unimportant = ( + advheat < 0.25 * radheat + ) # boolean array where advection heating is relatively unimportant + advunimploc = last_true_index( + advheat_unimportant[:advdomloc] + ) + # first point at lower altitude where advection becomes unimportant (if + # no point exists, it will become advdomloc) then walk to higher + # altitude again to find converged point. We are more lax with H/C + # ratio if advection dominates more. + almost_converged = np.abs(HCratio[advunimploc:]) < 1.3 * np.clip( + (advheat[advunimploc:] / radheat[advunimploc:]) ** (2.0 / 3.0), 1, 10 + ) + if True in almost_converged: # otherwise it stays default value + adv_cloc = advunimploc + first_true_index(almost_converged) + + # check for regime where radiative cooling is weak. Usually this means that + # expansion cooling dominates, but advection cooling can contribute in some + # cases + exp_cloc = len(HCratio) # start by setting a 'too high' value + expcool_dominates = radcool / (radcool + expcool + advcool) < 0.2 + if True in expcool_dominates and False in expcool_dominates: + exp_cloc = last_false_index( + expcool_dominates + ) + # this way of evaluating it guarantees that all entries after this one + # are True + elif False not in expcool_dominates: # if they are all True + exp_cloc = 0 + + cloc = min(adv_cloc, exp_cloc) # use the lowest radius point + + return cloc + + +def relaxTstruc(grid, path, itno, Te, HCratio): + """ + Proposes a new temperature profile using a 'relaxation' algorithm. + + Parameters + ---------- + grid : numpy.ndarray + Radius grid in units of cm. + path : str + Full path to the folder where the simulations are saved and ran. + itno : int + Iteration number. + Te : numpy.ndarray + Temperature profile of the last iteration at the 'grid' radii, in units of K. + HCratio : numpy.ndarray + Heating/cooling imbalance of the temperature profile of the last iteration, + output of the calc_HCratio() function. + + Returns + ------- + newTe_relax : numpy.ndarray + Adjusted temperature profile to use for the next iteration. + """ + + if itno == 2: # save for first time + np.savetxt( + path + "iterations.txt", + np.column_stack((grid, np.repeat(0.3, len(grid)), Te)), + header="grid fac1 Te1", + comments="", + delimiter=" ", + fmt="%.7e", + ) + + iterations_file = pd.read_csv(path + "iterations.txt", header=0, sep=" ") + fac = iterations_file["fac" + str(itno - 1)].values + + newTe_relax = get_new_Tstruc(Te, HCratio, fac) # adjust the temperature profile + newTe_relax = tools.smooth_gaus_savgol( + newTe_relax, fraction=1.0 / (20 * itno) + ) # smooth it + newTe_relax = np.clip( + newTe_relax, 1e1, 1e6 + ) # smoothing may have pushed newTe_relax < 10K again. + + if itno >= 4: # check for fluctuations. If so, we decrease the deltaT factor + prev_prevTe = iterations_file["Te" + str(itno - 2)] + previous_ratio = Te / prev_prevTe # compare itno-2 to itno-1 + + # compare itno-1 to the current itno (because of smoothing this ratio + # is not exactly the same as fT) + this_ratio = ( + newTe_relax / Te + ) + fl = ((previous_ratio < 1) & (this_ratio > 1)) | ( + (previous_ratio > 1) & (this_ratio < 1) + ) # boolean indicating where temperature fluctuates + fac[fl] = ( + 2 / 3 * fac[fl] + ) # take smaller changes in T in regions where the temperature fluctuates + fac = np.clip( + tools.smooth_gaus_savgol(fac, size=10), 0.02, 0.3 + ) # smooth the factor itself as well + newTe_relax = get_new_Tstruc( + Te, HCratio, fac + ) # recalculate new temperature profile with updated fac + newTe_relax = tools.smooth_gaus_savgol( + newTe_relax, fraction=1 / (20 * itno) + ) # smooth it + newTe_relax = np.clip(newTe_relax, 1e1, 1e6) + + iterations_file["fac" + str(itno)] = fac + iterations_file.to_csv( + path + "iterations.txt", sep=" ", float_format="%.7e", index=False + ) + + return newTe_relax + + +def constructTstruc(grid, newTe_relax, cloc, v, rho, mu, radheat, radcool): + """ + Proposes a new temperature profile based on a 'construction' algorithm, + starting at the cloc and at higher altitudes. + + Parameters + ---------- + grid : numpy.ndarray + Radius grid in units of cm. + newTe_relax : numpy.ndarray + Newly proposed temperature profile from the relaxation algorithm. + cloc : int + Index of the grid from where to start the construction algorithm. + v : numpy.ndarray + Velocity in units of cm s-1 at the 'grid' radii. + rho : numpy.ndarray + Density in units of g cm-3 at the 'grid' radii. + mu : numpy.ndarray + Mean particle mass in units of amu at the 'grid' radii. + radheat : numpy.ndarray + Radiative heating rate in units of erg s-1 cm-3, at the 'grid' radii. + radcool : numpy.ndarray + Radiative cooling rate in units of erg s-1 cm-3, as positive values, at + the 'grid' radii. + + Returns + ------- + newTe_construct : numpy.ndarray + Adjusted temperature profile to use for the next iteration. + """ + + newTe_construct = np.copy( + newTe_relax + ) # start with the temp struc from the relaxation function + + expansion_Tdivmu = ( + tools.k / tools.mH * v * np.gradient(rho, grid) + ) # this is expansion except for the T/mu term (still negative values) + advection_gradTdivmu = ( + -1 * tools.k / (tools.mH * 2 / 3) * rho * v + ) # this is advection except for the d(T/mu)/dr term + + def one_cell_HCratio(T, index): + expcool = expansion_Tdivmu[index] * T / mu[index] + adv = ( + advection_gradTdivmu[index] + * ((T / mu[index]) - (newTe_construct[index - 1] / mu[index - 1])) + / (grid[index] - grid[index - 1]) + ) + + # instead of completely keeping the radiative heating and cooling rate + # the same while we are solving for T in this bin, we adjust it a + # little bit. This helps to prevent that the temperature changes are + # too drastic and go into a regime where radiation becomes important + # again. We guess a quadratic dependence of the rates on T. This is not + # the true dependence, but it does reduce to the original rate when T + # -> original T, which is important. + guess_radheat = radheat[index] * (newTe_construct[index] / T) ** 2 + guess_radcool = radcool[index] * (T / newTe_construct[index]) ** 2 + + totheat = guess_radheat + max(adv, 0) # if adv is negative we don't add it here + # if adv is positive we don't add it here, we subtract expcool and adv + # because they are negative + totcool = ( + guess_radcool - expcool - min(adv, 0) + ) + + HCratio = max(totheat, totcool) / min( + totheat, totcool + ) # both entities are positive + + return HCratio - 1 # find root of this value to get H/C close to 1 + + for i in range(cloc + 1, len(grid)): # walk from cloc to higher altitudes + result = minimize_scalar( + one_cell_HCratio, method="bounded", bounds=[1e1, 1e6], args=(i) + ) + newTe_construct[i] = result.x + + # smooth around the abrupt edge where the constructed part sets in + smooth_newTe_construct = tools.smooth_gaus_savgol( + newTe_construct, fraction=0.03 + ) # first smooth the complete T(r) profile + smooth_newTe_construct = np.clip( + smooth_newTe_construct, 1e1, 1e6 + ) # after smoothing we might have ended up below 10K + # now combine the smoothed profile around 'cloc', and the non-smoothed + # version away from 'cloc' + smooth_weight = np.zeros(len(grid)) + smooth_weight += sps.norm.pdf(range(len(grid)), cloc, int(len(grid) / 30)) + smooth_weight /= np.max(smooth_weight) # normalize + raw_weight = 1 - smooth_weight + newTe_construct = ( + smooth_newTe_construct * smooth_weight + newTe_construct * raw_weight + ) + + return newTe_construct + + +def make_rates_plot( + altgrid, + Te, + newTe_relax, + radheat, + radcool, + expcool, + advheat, + advcool, + rho, + HCratio, + altmax, + fc, + newTe_construct=None, + cloc=None, + title=None, + savename=None, +): + """ + Makes a plot of the previous and newly proposed temperature profiles, + as well as the different heating/cooling rates and their ratio based on the + previous temperature profile. + + Parameters + ---------- + altgrid : numpy.ndarray + Radius grid in units of Rp. + Te : numpy.ndarray + Temperature profile of the last iteration in units of K. + newTe_relax : numpy.ndarray + Proposed temperature profile based on the relaxation algorithm. + radheat : numpy.ndarray + Radiative heating rate in units of erg s-1 cm-3. + radcool : numpy.ndarray + Radiative cooling rate in units of erg s-1 cm-3, as positive values. + expcool : numpy.ndarray + Expansion cooling rate in units of erg s-1 cm-3, as positive values. + advheat : numpy.ndarray + Advection heating rate in units of erg s-1 cm-3. + advcool : numpy.ndarray + Advection cooling rate in units of erg s-1 cm-3, as positive values. + rho : numpy.ndarray + Density in units of g cm-3 + HCratio : numpy.ndarray + Heating/cooling imbalance, output of the calc_HCratio() function. + altmax : numeric + Maximum altitude of the simulation in units of planet radius. + fc : numeric + Convergence threshold for H/C. + newTe_construct : numpy.ndarray, optional + Proposed temperature profile based on the construction algorithm, by + default None + cloc : int, optional + Index of the grid from where the construction algorithm was ran, by default None + title : str, optional + Title of the figure, by default None + savename : str, optional + Full path + filename to save the figure to, by default None + """ + + HCratiopos, HCrationeg = np.copy(HCratio), -1 * np.copy(HCratio) + HCratiopos[HCratiopos < 0] = 0.0 + HCrationeg[HCrationeg < 0] = 0.0 + + fig, (ax1, ax2, ax3) = plt.subplots(3, figsize=(4, 7)) + if title is not None: + ax1.set_title(title) + ax1.plot(altgrid, Te, color="#4CAF50", label="previous") + ax1.plot(altgrid, newTe_relax, color="#FFA500", label="relaxation") + if newTe_construct is not None: + ax1.plot(altgrid, newTe_construct, color="#800080", label="construction") + ax1.scatter(altgrid[cloc], newTe_relax[cloc], color="#800080") + ax1.set_ylabel("Temperature [K]") + ax1.legend(loc="best", fontsize=8) + + ax2.plot(altgrid, radheat / rho, color="red", linewidth=2.0) + ax2.plot(altgrid, radcool / rho, color="blue") + ax2.plot(altgrid, expcool / rho, color="blue", linestyle="dashed") + ax2.plot(altgrid, advheat / rho, color="red", linestyle="dotted") + ax2.plot(altgrid, advcool / rho, color="blue", linestyle="dotted") + ax2.set_yscale("log") + ax2.set_ylim( + 0.1 * min(radheat / rho, radcool / rho), + 2 + * max( + radheat / rho, + radcool / rho, + expcool / rho, + advheat / rho, + advcool / rho, + ), + ) + ax2.set_ylabel("Rate [erg/s/g]") + ax2.legend( + ( + ( + Line2D([], [], color="red", linestyle=(0, (6, 6))), + Line2D([], [], color="blue", linestyle=(6, (6, 6))), + ), + Line2D([], [], color="blue", linestyle="dashed"), + ( + Line2D([], [], color="red", linestyle=(0, (1, 2, 1, 8))), + Line2D([], [], color="blue", linestyle=(6, (1, 2, 1, 8))), + ), + ), + ("radiation", "expansion", "advection"), + loc="best", + fontsize=8, + ) + + ax3.plot(altgrid, HCratiopos, color="red") + ax3.plot(altgrid, HCrationeg, color="blue") + ax3.axhline(fc, color="k", linestyle="dotted") + ax3.set_yscale("log") + ax3.set_ylim(bottom=1) + ax3.set_ylabel("Ratio heat/cool") + + # use these with the altgrid: + tools.set_alt_ax(ax1, altmax=altmax, labels=False) + tools.set_alt_ax(ax2, altmax=altmax, labels=False) + tools.set_alt_ax(ax3, altmax=altmax, labels=True) + + fig.tight_layout() + if savename is not None: + plt.savefig(savename, bbox_inches="tight", dpi=200) + plt.clf() + plt.close() + + +def make_converged_plot( + altgrid, altmax, path, Te, radheat, rho, radcool, expcool, advheat, advcool +): + """ + Makes a plot of the converged temperature profile, as well as the different + heating/cooling rates. + + Parameters + ---------- + altgrid : numpy.ndarray + Radius grid in units of Rp. + altmax : numeric + Maximum altitude of the simulation in units of planet radius. + path : _type_ + _description_ + Te : numpy.ndarray + Converged temperature profile in units of K. + radheat : numpy.ndarray + Radiative heating rate in units of erg s-1 cm-3. + rho : numpy.ndarray + Density in units of g cm-3 + radcool : numpy.ndarray + Radiative cooling rate in units of erg s-1 cm-3, as positive values. + expcool : numpy.ndarray + Expansion cooling rate in units of erg s-1 cm-3, as positive values. + advheat : numpy.ndarray + Advection heating rate in units of erg s-1 cm-3. + advcool : numpy.ndarray + Advection cooling rate in units of erg s-1 cm-3, as positive values. + """ + + fig, (ax1, ax2) = plt.subplots(2, figsize=(4, 5.5)) + ax1.plot(altgrid, Te, color="k") + ax1.set_ylabel("Temperature [K]") + + ax2.plot(altgrid, radheat / rho, color="red") + ax2.plot(altgrid, radcool / rho, color="blue") + ax2.plot(altgrid, expcool / rho, color="blue", linestyle="dashed") + ax2.plot(altgrid, advheat / rho, color="red", linestyle="dotted") + ax2.plot(altgrid, advcool / rho, color="blue", linestyle="dotted") + ax2.set_yscale("log") + ax2.set_ylim( + 0.1 * min(radheat / rho, radcool / rho), + 2 + * max( + radheat / rho, + radcool / rho, + expcool / rho, + advheat / rho, + advcool / rho, + ), + ) + ax2.set_ylabel("Rate [erg/s/g]") + ax2.legend( + ( + ( + Line2D([], [], color="red", linestyle=(0, (6, 6))), + Line2D([], [], color="blue", linestyle=(6, (6, 6))), + ), + Line2D([], [], color="blue", linestyle="dashed"), + ( + Line2D([], [], color="red", linestyle=(0, (1, 2, 1, 8))), + Line2D([], [], color="blue", linestyle=(6, (1, 2, 1, 8))), + ), + ), + ("radiation", "expansion", "advection"), + loc="best", + fontsize=8, + ) + + # use these with the altgrid: + tools.set_alt_ax(ax1, altmax=altmax, labels=False) + tools.set_alt_ax(ax2, altmax=altmax) + + fig.tight_layout() + plt.savefig(path + "converged.png", bbox_inches="tight", dpi=200) + plt.clf() + plt.close() + + +def check_converged(fc, HCratio, newTe, prevTe, linthresh=50.0): + """ + Checks whether the temperature profile is converged. At every radial cell, + it checks for three conditions, one of which must be satisfied: + 1. The H/C ratio is less than fc (this is the "main" criterion). + 2. The newly proposed temperature profile is within the temperature difference + that a H/C equal to fc would induce. In principle, we would expect that if + this were the case, H/C itself would be < fc, but smoothing of the + temperature profile can cause different behavior. For example, we can get stuck + in a loop where H/C > fc, we then propose a new temperature profile that is + significantly different, but then after the smoothing step we end up with + the profile that we had before. To break out of such a loop that never converges, + we check if the temperature changes are less than we would expect for an + "fc-converged" profile, even if H/C itself is still >fc. In practice, this + means that the temperature profile changes less than 0.3 * log10(1.1), + which is ~1%, so up to 100 K for a typical profile. + 3. The newly proposed temperature profile is less than `linthresh` different + from the last iteration. This can be assumed to be precise enough convergence. + + Parameters + ---------- + fc : numeric + Convergence threshold for the total heating/cooling ratio. + HCratio : numpy.ndarray + Heating/cooling imbalance, output of the calc_HCratio() function. + newTe : numpy.ndarray + Newly proposed temperature profile based on both relaxation and + construction algorithms, in units of K. + prevTe : numpy.ndarray + Temperature profile of the previous iteration in units of K. + linthresh : numeric, optional + Convergence threshold for T(r) as an absolute temperature difference + in units of K, by default 50. + + Returns + ------- + converged : bool + Whether the temperature profile is converged. + """ + + ratioTe = np.maximum(newTe, prevTe) / np.minimum( + newTe, prevTe + ) # take element wise ratio + diffTe = np.abs(newTe - prevTe) # take element-wise absolute difference + + converged = np.all( + (np.abs(HCratio) < fc) + | (ratioTe < (1 + 0.3 * np.log10(fc))) + | (diffTe < linthresh) + ) + + return converged + + +def clean_converged_folder(folder): + """ + Deletes all files in a folder that are not called "converged*". + In the context of this module, it thus cleans all files of earlier + iterations, as well as helper files, preserving only the final + converged simulation. + + Parameters + ---------- + folder : str + Folder where the iterative algorithm is ran, typically: + $SUNBATHER_PROJECT_PATH/sims/1D/*plname*/*dir*/parker_*T0*_*Mdot*/ + """ + + if not os.path.isdir(folder): + warnings.warn(f"This folder does not exist: {folder}") + + elif not os.path.isfile(folder + "/converged.in"): + warnings.warn(f"This folder wasn't converged, I will not clean it: {folder}") + + else: + for filename in os.listdir(folder): + if filename[:9] != "converged" and os.path.isfile( + os.path.join(folder, filename) + ): + os.remove(os.path.join(folder, filename)) + + +def run_loop(path, itno, fc, save_sp=None, maxit=16): + """ + Solves for the nonisothermal temperature profile of a Parker wind + profile through an iterative convergence scheme including Cloudy. + + Parameters + ---------- + path : str + Folder where the iterative algorithm is ran, typically: + $SUNBATHER_PROJECT_PATH/sims/1D/*plname*/*dir*/parker_*T0*_*Mdot*/. + In this folder, the 'template.in' and 'iteration1.in' files must be + present, which are created automatically by the convergeT_parker.py module. + itno : int + Iteration number to start from. Can only be different from 1 if + this profile has been (partly) solved before. + fc : float + H/C convergence factor, see Linssen et al. (2024). A sensible value is 1.1. + save_sp : list, optional + A list of atomic/ionic species to let Cloudy save the number density profiles + for in the final converged simulation. Those are needed when doing radiative + transfer to produce transmission spectra. For example, to be able to make + metastable helium spectra, 'He' needs to be in the save_sp list. By default []. + maxit : int, optional + Maximum number of iterations, by default 16. + """ + if save_sp is None: + save_sp = [] + + if itno == 1: # iteration1 is just running Cloudy. Then, we move on to iteration2 + tools.run_Cloudy("iteration1", folder=path) + itno += 1 + + # now, we have ran our iteration1 and can start the iterative scheme to + # find a new profile: + while itno <= maxit: + prev_sim = tools.Sim( + path + f"iteration{itno-1}" + ) # load Cloudy results from previous iteration + Rp = prev_sim.p.R # planet radius in cm + altmax = prev_sim.altmax # maximum radius of the simulation in units of Rp + + # make logspaced grid to use throughout the code, interpolate all + # quantities onto this grid. + rgrid = np.logspace(np.log10(Rp), np.log10(altmax * Rp), num=1000) + + Te, mu, rho, v, radheat, radcool, expcool, advheat, advcool = simtogrid( + prev_sim, rgrid + ) # get all needed Cloudy quantities on the grid + HCratio = calc_HCratio( + radheat, radcool, expcool, advheat, advcool + ) # H/C or C/H ratio, depending on which is larger + + # now the procedure starts - we first produce a new temperature profile + newTe_relax = relaxTstruc( + rgrid, path, itno, Te, HCratio + ) # apply the relaxation algorithm + cloc = calc_cloc( + radheat, radcool, expcool, advheat, advcool, HCratio + ) # look for a point from where we could use construction + newTe_construct = None + if cloc != len(rgrid): + newTe_construct = constructTstruc( + rgrid, newTe_relax, int(cloc), v, rho, mu, radheat, radcool + ) # apply construction algorithm + + make_rates_plot( + rgrid / Rp, + Te, + newTe_relax, + radheat, + radcool, + expcool, + advheat, + advcool, + rho, + HCratio, + altmax, + fc, + title=f"iteration {itno}", + savename=path + f"iteration{itno}.png", + newTe_construct=newTe_construct, + cloc=cloc, + ) + + # get the final new temperature profile, based on whether the + # construction algorithm was applied + if newTe_construct is None: + newTe = newTe_relax + else: + newTe = newTe_construct + + # add this temperature profile to the 'iterations' file for future reference + iterations_file = pd.read_csv(path + "iterations.txt", header=0, sep=" ") + iterations_file["Te" + str(itno)] = newTe + iterations_file.to_csv( + path + "iterations.txt", sep=" ", float_format="%.7e", index=False + ) + + # now we check if the profile is converged. + if ( + itno <= 2 + ): + # always update the Te profile at least once - in case we start + # from a 'close' Parker wind profile that immediately satisfies fc + converged = False + else: + prevTe = iterations_file[ + "Te" + str(itno - 1) + ].values + # read out from file instead of Sim because the file has higher + # resolution + converged = check_converged( + fc, HCratio, newTe, prevTe, linthresh=50.0 + ) # check convergence criteria + + if converged: # run once more with more output + make_converged_plot( + rgrid / Rp, + altmax, + path, + Te, + radheat, + rho, + radcool, + expcool, + advheat, + advcool, + ) + # calculate these terms for the output converged.txt file - for + # fast access of some key parameters without loading in the Cloudy + # sim. + np.savetxt( + path + "converged.txt", + np.column_stack( + ( + rgrid / Rp, + rho, + Te, + mu, + radheat, + radcool, + expcool, + advheat, + advcool, + ) + ), + fmt="%1.5e", + header="R rho Te mu radheat radcool expcool advheat advcool", + comments="", + ) + + # we run the last simulation one more time but with all the output files + tools.copyadd_Cloudy_in( + path + "iteration" + str(itno - 1), + path + "converged", + outfiles=[".heat", ".den", ".en"], + denspecies=save_sp, + selected_den_levels=True, + hcfrac=0.01, + ) + tools.run_Cloudy("converged", folder=path) + tools.Sim( + path + "converged" + ) + # read in the simulation, so we open the .en file (if it exists) + # and hence compress its size (see tools.process_energies()) + clean_converged_folder(path) # remove all non-converged files + print(f"Temperature profile converged: {path}") + + break + + # set up the next iteration + Cltlaw = tools.alt_array_to_Cloudy( + rgrid, newTe, altmax, Rp, 1000 + ) # convert the temperature profile to a table format accepted by Cloudy + + tools.copyadd_Cloudy_in( + path + "template", path + "iteration" + str(itno), tlaw=Cltlaw + ) # add temperature profile to the template input file + if ( + itno != maxit + ): # no use running it if we are not entering the next while-loop iteration + tools.run_Cloudy(f"iteration{itno}", folder=path) + else: + print(f"Failed temperature convergence after {itno} iterations: {path}") + + itno += 1 diff --git a/src/species_enlim.txt b/src/sunbather/species_enlim.txt similarity index 100% rename from src/species_enlim.txt rename to src/sunbather/species_enlim.txt diff --git a/src/sunbather/tools.py b/src/sunbather/tools.py new file mode 100644 index 0000000..0992ae4 --- /dev/null +++ b/src/sunbather/tools.py @@ -0,0 +1,2969 @@ +""" +Tools for sunbather +""" + +import os +import glob +import re +from shutil import copyfile +from fractions import Fraction +import warnings +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt + +from scipy.interpolate import interp1d +from scipy.signal import savgol_filter +import scipy.stats as sps +from scipy.ndimage import gaussian_filter1d + +import astropy.units +import astropy.constants + +SUNBATHERPATH = os.path.dirname( + os.path.abspath(__file__) +) # the absolute path where this code lives + + +def get_cloudy_path(): + try: + # the path where Cloudy is installed + cloudypath = os.environ["CLOUDY_PATH"] + except KeyError as exc: + cloudypath = f"{SUNBATHERPATH}/cloudy/c23.01" + if not os.path.exists(f"{cloudypath}/source/cloudy.exe"): + raise KeyError( + "The environment variable 'CLOUDY_PATH' is not set. " + "Please set this variable in your .bashrc/.zshrc file " + "to the path where the Cloudy installation is located. " + "Do not point it to the /source/ subfolder, but to the main folder." + ) from exc + return cloudypath + + +def set_sunbather_project_path(projectpath): + """ + Set project path and ensure it has a planets.txt file + """ + os.environ["SUNBATHER_PROJECT_PATH"] = projectpath + if not os.path.exists(f"{projectpath}/planets.txt"): + print("Copying planets.txt from SUNBATHERPATH to projectpath") + copyfile( + f"{SUNBATHERPATH}/planets.txt", + f"{projectpath}/planets.txt", + ) + return + + +def get_sunbather_project_path(): + try: + projectpath = os.environ[ + "SUNBATHER_PROJECT_PATH" + ] # the path where you save your simulations and do analysis + except KeyError as exc: + projectpath = "./" + if not os.path.exists(f"{projectpath}/planets.txt"): + raise FileNotFoundError( + "The environment variable 'SUNBATHER_PROJECT_PATH' is not set, and no " + "planets.txt file found in current directory. Please set the " + "'SUNBATHER_PROJECT_PATH' variable in your .bashrc/.zshrc file " + "to the path where you want the sunbather models to be saved, " + "and make sure that the 'planets.txt' file is present in that folder." + ) from exc + return projectpath + + +def read_planets_file(): + if os.path.exists(f"{get_sunbather_project_path()}/planets.txt"): + # read planet parameters globally instead of in the Planets class (so we do it only + # once) + planets = pd.read_csv( + f"{get_sunbather_project_path()}/planets.txt", + dtype={ + "name": str, + "full name": str, + "R [RJ]": np.float64, + "Rstar [Rsun]": np.float64, + "a [AU]": np.float64, + "M [MJ]": np.float64, + "Mstar [Msun]": np.float64, + "transit impact parameter": np.float64, + "SEDname": str, + }, + comment="#", + ) + return planets + raise FileNotFoundError( + "The $SUNBATHER_PROJECT_PATH/planets.txt file cannot be found. " + "Please check if your $SUNBATHER_PROJECT_PATH actually exists on your machine. " + "Then, copy /sunbather/planets.txt to your project path." + ) + + +# define constants: +# c = 2.99792458e10 # cm/s +c = astropy.constants.c.to("cm/s").value +# h = 4.135667696e-15 # eV s, used to plot wavelengths in keV units +h = astropy.constants.h.to("eV*s").value +# mH = 1.674e-24 # g - intended: atomic mass unit +mH = (1 * astropy.units.u).to("g").value +# k = 1.381e-16 # erg/K +k = astropy.constants.k_B.to("erg/K").value +# AU = 1.49597871e13 # cm +AU = astropy.units.au.to("cm") +# pc = 3.08567758e18 # cm +pc = astropy.units.pc.to("cm") +# RJ = 7.1492e9 # cm +RJ = astropy.units.R_jup.to("cm") +# RE = 6.371e8 # cm +RE = astropy.units.R_earth.to("cm") +# Rsun = 69634000000 # cm +Rsun = astropy.units.R_sun.to("cm") +# Msun = 1.9891e33 # g +Msun = astropy.constants.M_sun.to("g").value +# MJ = 1.898e30 # g +MJ = astropy.constants.M_jup.to("g").value +# ME = 5.9722e27 # g +ME = astropy.constants.M_earth.to("g").value +# G = 6.6743e-8 # cm3/g/s2 +G = astropy.constants.G.to("cm**3 * g**-1 * s**-2").value +Ldict = { + "S": 0, + "P": 1, + "D": 2, + "F": 3, + "G": 4, + "H": 5, + "I": 6, + "K": 7, + "L": 8, + "M": 9, + "N": 10, + "O": 11, + "Q": 12, + "R": 13, + "T": 14, +} # atom number of states per L orbital + +element_names = { + "H": "hydrogen", + "He": "helium", + "Li": "lithium", + "Be": "beryllium", + "B": "boron", + "C": "carbon", + "N": "nitrogen", + "O": "oxygen", + "F": "fluorine", + "Ne": "neon", + "Na": "sodium", + "Mg": "magnesium", + "Al": "aluminium", + "Si": "silicon", + "P": "phosphorus", + "S": "sulphur", + "Cl": "chlorine", + "Ar": "argon", + "K": "potassium", + "Ca": "calcium", + "Sc": "scandium", + "Ti": "titanium", + "V": "vanadium", + "Cr": "chromium", + "Mn": "manganese", + "Fe": "iron", + "Co": "cobalt", + "Ni": "nickel", + "Cu": "copper", + "Zn": "zinc", +} +element_symbols = dict( + (reversed(item) for item in element_names.items()) +) # reverse dictionary mapping e.g. 'hydrogen'->'H' + +# number of corresponding energy levels between Cloudy and NIST - read txt file +# header for more info +species_enlim = pd.read_csv(SUNBATHERPATH + "/species_enlim.txt", index_col=0, header=1) + + +# ###################################### +# ########## CLOUDY SPECIES ########## +# ###################################### + + +def get_specieslist(max_ion=6, exclude_elements=None): + """ + Returns a list of atomic and ionic species names. Default returns all + species up to 6+ ionization. Higher than 6+ ionization is rarely attained + in an exoplanet atmosphere, but it can occur in high XUV flux scenarios + such as young planetary systems. The species list only includes species + for which the NIST database has any spectral line coefficients, as there is + little use saving other species as well. + + Parameters + ---------- + max_ion : int, optional + Maximum ionization degree of the included species, by default 6 + exclude_elements : str or list, optional + Elements to include (in both atomic and ionic form), by default [] + + Returns + ------- + specieslist : list + List of atomic and ionic species names in the string format expected by + Cloudy. + """ + if exclude_elements is None: + exclude_elements = [] + + if max_ion > 12: + warnings.warn( + "tools.get_specieslist(): You have set max_ion > 12, but " + "sunbather is currently only able to process species up to 12+ ionized. " + "However, this should typically be enough, even when using a strong " + "XUV flux." + ) + + if isinstance(exclude_elements, str): # turn into list with one element + exclude_elements = [exclude_elements] + + specieslist = species_enlim.index.tolist() # all species up to 12+ + + for element in exclude_elements: + specieslist = [sp for sp in specieslist if sp.split("+")[0] != element] + + for sp in specieslist[:]: + sp_split = sp.split("+") + + if len(sp_split) == 1: + deg_ion = 0 + elif sp_split[1] == "": + deg_ion = 1 + else: + deg_ion = int(sp_split[1]) + + if deg_ion > max_ion: + specieslist.remove(sp) + + return specieslist + + +def get_mass(species): + """ + Returns the mass of an atomic or positive ion. For ions, + it returns the mass of the atom, since the electron mass is negligible. + + Parameters + ---------- + species : str + Name of the species in the string format expected by Cloudy. + + Returns + ------- + mass : float + Mass of the species in units of g. + """ + + atom = species.split("+")[0] + + mass_dict = { + "H": 1.6735575e-24, + "He": 6.646477e-24, + "Li": 1.15e-23, + "Be": 1.4965082e-23, + "B": 1.795e-23, + "C": 1.9945e-23, + "N": 2.3259e-23, + "O": 2.6567e-23, + "F": 3.1547e-23, + "Ne": 3.35092e-23, + "Na": 3.817541e-23, + "Mg": 4.0359e-23, + "Al": 4.48038988e-23, + "Si": 4.6636e-23, + "P": 5.14331418e-23, + "S": 5.324e-23, + "Cl": 5.887e-23, + "Ar": 6.6335e-23, + "K": 6.49243e-23, + "Ca": 6.6551e-23, + "Sc": 7.4651042e-23, + "Ti": 7.9485e-23, + "V": 8.45904e-23, + "Cr": 8.63416e-23, + "Mn": 9.1226768e-23, + "Fe": 9.2733e-23, + "Co": 9.786087e-23, + "Ni": 9.74627e-23, + "Cu": 1.0552e-22, + "Zn": 1.086e-22, + } # g + + mass = mass_dict[atom] + + return mass + + +# ###################################### +# ########## CLOUDY FILES ########## +# ###################################### + + +def process_continuum(filename, nonzero=False): + """ + Reads a .con file from the 'save continuum units angstrom' command. + It renames the columns and adds a wavelength column. + The flux units of the continuum are as follows: + Take the SED in spectral flux density, so F(nu) instead of nu*F(nu), and + find the total area by integration. Then multiply with the frequency, + to get nu*F(nu), and normalize that by the total area found, and multiply + with the total luminosity. Those are the units of Cloudy. + + Parameters + ---------- + filename : str + Filename of a 'save continuum' Cloudy output file. + nonzero : bool, optional + Whether to remove rows where the incident spectrum is 0 (i.e., not + defined), by default False + + Returns + ------- + con_df : pandas.DataFrame + Parsed output of the 'save continuum' Cloudy command. + """ + + con_df = pd.read_table(filename) + con_df.rename(columns={"#Cont nu": "wav", "net trans": "nettrans"}, inplace=True) + if nonzero: + con_df = con_df[con_df.incident != 0] + + return con_df + + +def process_heating(filename, Rp=None, altmax=None, cloudy_version="17"): + """ + Reads a .heat file from the 'save heating' command. + If Rp and altmax are given, it adds an altitude/radius scale. + For each unique heating agent, it adds a column with its rate at each radial bin. + + Parameters + ---------- + filename : str + Filename of a 'save heating' Cloudy output file. + Rp : numeric, optional + Planet radius in units of cm, by default None + altmax : numeric, optional + Maximum altitude of the simulation in units of planet radius, by default None + cloudy_version : str, optional + Major Cloudy release version, by default "17" + + Returns + ------- + heat : pandas.DataFrame + Parsed output of the 'save heating' Cloudy command. + + Raises + ------ + TypeError + If a Cloudy version was used that is not supported by sunbather. + """ + + # determine max number of columns (otherwise pd.read_table assumes it is the number + # of the first row) + max_columns = 0 + with open(filename, "r", encoding="utf-8") as file: + for line in file: + num_columns = len(line.split("\t")) + max_columns = max(max_columns, num_columns) + # set up the column names + if cloudy_version == "17": + fixed_column_names = ["depth", "temp", "htot", "ctot"] + elif cloudy_version == "23": + fixed_column_names = ["depth", "temp", "htot", "ctot", "adv"] + else: + raise TypeError("Only C17.02 and C23.01 are currently supported.") + num_additional_columns = (max_columns - 4) // 2 + additional_column_names = [ + f"htype{i}" for i in range(1, num_additional_columns + 1) for _ in range(2) + ] + additional_column_names[1::2] = [ + f"hfrac{i}" for i in range(1, num_additional_columns + 1) + ] + all_column_names = fixed_column_names + additional_column_names + heat = pd.read_table( + filename, delimiter="\t", skiprows=1, header=None, names=all_column_names + ) + + if heat["depth"].eq("#>>>> Ionization not converged.").any(): + warnings.warn( + f"The simulation you are reading in exited OK but does contain ionization " + f"convergence failures: {filename[:-5]}" + ) + heat = heat[ + heat["depth"] != "#>>>> Ionization not converged." + ] # remove those extra lines from the heat DataFrame + + # remove the "second rows", which sometimes are in the .heat file and do + # not give the heating at a given depth + if isinstance(heat.depth.iloc[0], str): # in some cases there are no second rows + heat = heat[heat.depth.map(len) < 12] # delete second rows + + heat.depth = pd.to_numeric(heat.depth) # str to float + heat.reset_index( + drop=True, inplace=True + ) # reindex so that it has same index as e.g. .ovr + + if Rp is not None and altmax is not None: # add altitude scale + heat["alt"] = altmax * Rp - heat.depth + + agents = [] + for column in heat.columns: + if column.startswith("htype"): + agents.extend(heat[column].unique()) + agents = list( + set(agents) + ) # all unique heating agents that appear somewhere in the .heat file + + for agent in agents: + heat[agent] = np.nan # add 'empty' column for each agent + + # now do a (probably sub-optimal) for-loop over the whole df to put all hfracs in + # the corresponding column + htypes = [f"htype{i+1}" for i in range(num_additional_columns)] + hfracs = [f"hfrac{i+1}" for i in range(num_additional_columns)] + for htype, hfrac in zip(htypes, hfracs): + for index, agent in heat[htype].items(): + rate = heat.loc[index, hfrac] + heat.loc[index, agent] = rate + + if ( + np.nan in heat.columns + ): # sometimes columns are partially missing, resulting in columns called nan + heat.drop(columns=[np.nan], inplace=True) + + heat["sumfrac"] = heat.loc[:, [col for col in heat.columns if "hfrac" in col]].sum( + axis=1 + ) + + return heat + + +def process_cooling(filename, Rp=None, altmax=None, cloudy_version="17"): + """ + Reads a .cool file from the 'save cooling' command. + If Rp and altmax are given, it adds an altitude/radius scale. + For each unique cooling agent, it adds a column with its rate at each radial bin. + + Parameters + ---------- + filename : str + Filename of a 'save cooling' Cloudy output file. + Rp : numeric, optional + Planet radius in units of cm, by default None + altmax : numeric, optional + Maximum altitude of the simulation in units of planet radius, by default None + cloudy_version : str, optional + Major Cloudy release version, by default "17" + + Returns + ------- + cool : pandas.DataFrame + Parsed output of the 'save cooling' Cloudy command. + + Raises + ------ + TypeError + If a Cloudy version was used that is not supported by sunbather. + """ + + # determine max number of columns (otherwise pd.read_table assumes it is + # the number of the first row) + max_columns = 0 + with open(filename, "r", encoding="utf-8") as file: + for line in file: + num_columns = len(line.split("\t")) + max_columns = max(max_columns, num_columns) + # set up the column names + if cloudy_version == "17": + fixed_column_names = ["depth", "temp", "htot", "ctot"] + elif cloudy_version == "23": + fixed_column_names = ["depth", "temp", "htot", "ctot", "adv"] + else: + raise Exception("Only C17.02 and C23.01 are currently supported.") + num_additional_columns = (max_columns - 4) // 2 + additional_column_names = [ + f"ctype{i}" for i in range(1, num_additional_columns + 1) for _ in range(2) + ] + additional_column_names[1::2] = [ + f"cfrac{i}" for i in range(1, num_additional_columns + 1) + ] + all_column_names = fixed_column_names + additional_column_names + cool = pd.read_table( + filename, delimiter="\t", skiprows=1, header=None, names=all_column_names + ) + + if cool["depth"].eq("#>>>> Ionization not converged.").any(): + warnings.warn( + f"The simulation you are reading in exited OK but does contain ionization " + f"convergence failures: {filename[:-5]}" + ) + # remove those extra lines from the cool DataFrame + cool = cool[cool["depth"] != "#>>>> Ionization not converged."] + cool["depth"] = cool["depth"].astype(float) + cool = cool.reset_index(drop=True) # so it matches other dfs like .ovr + + if Rp is not None and altmax is not None: # add altitude scale + cool["alt"] = altmax * Rp - cool.depth + + agents = [] + for column in cool.columns: + if column.startswith("ctype"): + agents.extend(cool[column].unique()) + agents = list( + set(agents) + ) # all unique cooling agents that appear somewhere in the .cool file + + for agent in agents: + cool[agent] = np.nan # add 'empty' column for each agent + + # now do a (probably sub-optimal) for-loop over the whole df to put all cfracs in + # the corresponding column + ctypes = [f"ctype{i+1}" for i in range(num_additional_columns)] + cfracs = [f"cfrac{i+1}" for i in range(num_additional_columns)] + for ctype, cfrac in zip(ctypes, cfracs): + for index, agent in cool[ctype].items(): + rate = cool.loc[index, cfrac] + cool.loc[index, agent] = rate + + if ( + np.nan in cool.columns + ): # sometimes columns are partially missing, resulting in columns called nan + cool.drop(columns=[np.nan], inplace=True) + + cool["sumfrac"] = cool.loc[:, [col for col in cool.columns if "cfrac" in col]].sum( + axis=1 + ) + + return cool + + +def process_coolingH2(filename, Rp=None, altmax=None): + """ + Reads a .coolH2 file from the 'save H2 cooling' command, + which keeps track of cooling and heating processes unique to the + H2 molecule, when using the 'database H2' command. + From the Cloudy source code "mole_h2_io.cpp" the columns are: + depth, Temp, ctot/htot, H2 destruction rate Solomon TH85, + H2 destruction rate Solomon big H2, photodis heating, + heating dissoc. electronic exited states, + cooling collisions in X (neg = heating), + "HeatDexc"=net heat, "-HeatDexc/abundance"=net cool per particle + + If Rp and altmax are given, it adds an altitude/radius scale. + + Parameters + ---------- + filename : str + Filename of a 'save H2 cooling' Cloudy output file. + Rp : numeric, optional + Planet radius in units of cm, by default None + altmax : numeric, optional + Maximum altitude of the simulation in units of planet radius, by default None + + Returns + ------- + coolH2 : pandas.DataFrame + Parsed output of the 'save H2 cooling' Cloudy command. + """ + + coolH2 = pd.read_table( + filename, + names=[ + "depth", + "Te", + "ctot", + "desTH85", + "desbigH2", + "phdisheat", + "eedisheat", + "collcool", + "netheat", + "netcoolpp", + ], + header=1, + ) + if Rp is not None and altmax is not None: + coolH2["alt"] = altmax * Rp - coolH2["depth"] + + return coolH2 + + +def process_overview(filename, Rp=None, altmax=None, abundances=None): + """ + Reads in a '.ovr' file from the 'save overview' command. + If Rp and altmax are given, it adds an altitude/radius scale. + It also adds the mass density, the values of which are only correct if + the correct abundances are passed. + + Parameters + ---------- + filename : str + Filename of a 'save overview' Cloudy output file. + Rp : numeric, optional + Planet radius in units of cm, by default None + altmax : numeric, optional + Maximum altitude of the simulation in units of planet radius, by default None + abundances : dict, optional + Dictionary with the abudance of each element, expressed as a fraction of the + total. Can be easily created with get_abundances(). By default None, which + results in solar composition. + + Returns + ------- + ovr : pandas.DataFrame + Parsed output of the 'save overview' Cloudy command. + """ + + ovr = pd.read_table(filename) + ovr.rename(columns={"#depth": "depth"}, inplace=True) + ovr["rho"] = hden_to_rho(ovr.hden, abundances=abundances) # Hdens to total dens + if Rp is not None and altmax is not None: + ovr["alt"] = altmax * Rp - ovr["depth"] + ovr["mu"] = calc_mu(ovr.rho, ovr.eden, abundances=abundances) + + if ( + (ovr["2H_2/H"].max() > 0.1) + or (ovr["CO/C"].max() > 0.1) + or (ovr["H2O/O"].max() > 0.1) + ): + warnings.warn( + f"Molecules are significant, the calculated mean particle mass could be " + f"inaccurate: {filename}" + ) + + return ovr + + +def process_densities(filename, Rp=None, altmax=None): + """ + Reads a .den file from the 'save species densities' command. + If Rp and altmax are given, it adds an altitude/radius scale. + + Parameters + ---------- + filename : str + Filename of a 'save species densities' Cloudy output file. + Rp : numeric, optional + Planet radius in units of cm, by default None + altmax : numeric, optional + Maximum altitude of the simulation in units of planet radius, by default None + + Returns + ------- + den : pandas.DataFrame + Parsed output of the 'save species densities' Cloudy command. + """ + + den = pd.read_table(filename) + den.rename(columns={"#depth densities": "depth"}, inplace=True) + + if Rp is not None and altmax is not None: + den["alt"] = altmax * Rp - den["depth"] + + return den + + +def process_energies(filename, rewrite=True, cloudy_version="17"): + """ + Reads a '.en' file from the 'save species energies' command. + This command must always be used alongside the 'save species densities' command, + since they give the associated energy of each level printed in the + densities file. Without saving the energies, it is for example not clear + which atomic configuration / energy level 'He[52]' corresponds to. + This function returns a dictionary mapping the column names of + the .den file to their corresponding atomic configurations. + The atomic configuration is needed to identify the spectral lines originating + from this level during radiative transfer. + + Parameters + ---------- + filename : str + Filename of a 'save species energies' Cloudy output file. + rewrite : bool, optional + Whether to rewrite the file to only keeping only the first row. Normally, + the energies of each energy level are stored per depth cell of the simulation, + but they should be the same at each depth. Retaining only the values of the + first row in this way helps to compress file size. By default True. + cloudy_version : str, optional + Major Cloudy release version, by default "17" + + Returns + ------- + en_df : dict + Dictionary mapping the column names of the .den file to their atomic configurations. + + Raises + ------ + ValueError + If the energy values are not the same at each depth. + """ + + en = pd.read_table( + filename, float_precision="round_trip" + ) # use round_trip to prevent exp numerical errors + + if ( + en.columns.values[0][0] == "#" + ): # condition checks whether it has already been rewritten, if not, we do all following stuff: + + for i, col in enumerate(en.columns): # check if all rows are the same + if len(en.iloc[:, col].unique()) != 1: + raise ValueError( + "In reading .en file, found a column with not identical values!" + + " filename:", + filename, + "col:", + col, + "colname:", + en.columns[col], + "unique values:", + en.iloc[:, col].unique(), + ) + + en.rename( + columns={en.columns.values[0]: en.columns.values[0][10:]}, inplace=True + ) # rename the column + + if rewrite: # save with only first row to save file size + en.iloc[[0], :].to_csv(filename, sep="\t", index=False, float_format="%.5e") + + en_df = pd.DataFrame(index=en.columns.values) + en_df["species"] = [ + k.split("[")[0] for k in en_df.index.values + ] # we want to match 'He12' to species='He', for example + en_df["energy"] = en.iloc[0, :].values + en_df["configuration"] = "" + en_df["term"] = "" + en_df["J"] = "" + + # the & set action takes the intersection of all unique species of the .en file, and those known with NIST levels + unique_species = list(set(en_df.species.values) & set(species_enlim.index.tolist())) + + for species in unique_species: + species_levels = pd.read_table( + SUNBATHERPATH + "/RT_tables/" + species + "_levels_processed.txt" + ) # get the NIST levels + species_energies = en_df[ + en_df.species == species + ].energy # get Cloudy's energies + + # tolerance of difference between Cloudy's and NISTs energy levels. They usually differ at the decimal level so we need some tolerance. + atol = species_enlim.loc[species, f"atol_C{cloudy_version}"] + # start by assuming we can match this many energy levels + n_matching = species_enlim.loc[species, f"idx_C{cloudy_version}"] + + for n in range(n_matching): + if ( + not np.abs(species_energies.iloc[n] - species_levels.energy.iloc[n]) + < atol + ): + warnings.warn( + f"In {filename} while getting atomic states for species {species}, I expected to be able to match the first {n_matching} " + f"energy levels between Cloudy and NIST to a precision of {atol} but I have an energy mismatch at energy level {n+1}. " + f"This should not introduce bugs, as I will now only parse the first {n} levels." + ) + + # for debugging, you can print the energy levels of Cloudy and NIST: + # print("\nCloudy, NIST, Match?") + # for i in range(n_matching): + # print(species_energies.iloc[i], species_levels.energy.iloc[i], np.isclose(species_energies.iloc[:n_matching], species_levels.energy.iloc[:n_matching], rtol=0.0, atol=atol)[i]) + + n_matching = n # reset n_matching to how many actually match + + break + + # Now assign the first n_matching columns to their expected values as given by the NIST species_levels DataFrame + first_iloc = np.where(en_df.species == species)[0][ + 0 + ] # iloc at which the species (e.g. He or Ca+3) starts. + en_df.iloc[ + first_iloc : first_iloc + n_matching, en_df.columns.get_loc("configuration") + ] = species_levels.configuration.iloc[:n_matching].values + en_df.iloc[ + first_iloc : first_iloc + n_matching, en_df.columns.get_loc("term") + ] = species_levels.term.iloc[:n_matching].values + en_df.iloc[first_iloc : first_iloc + n_matching, en_df.columns.get_loc("J")] = ( + species_levels.J.iloc[:n_matching].values + ) + + return en_df + + +def find_line_lowerstate_in_en_df(species, lineinfo, en_df, verbose=False): + """ + Finds the column name of the .den file that corresponds to + the ground state of the given line. So for example if species='He', + and we are looking for the metastable helium line, + it will return 'He[2]', meaning the 'He[2]' column of the .den file contains + the number densities of the metastable helium atom. + + Additionally, it calculates a multiplication factor <1 for the number + density of this energy level. This is for spectral lines that originate from a + specific J (total angular momentum quantum number) configuration, but Cloudy + does not save the densities of this specific J-value, only of the parent LS state. + In this case, we use a statistical argument to guess how many of the particles + are in each J-state. For this, we use that each J-state has 2*J+1 substates, + and then assuming all substates are equally populated, we can calculate the + population of each J-level. The assumption of equal population may not always be strictly + valid. In LTE, the population should in principle be calculated form the Boltzmann + distribution, but equal populations will be a good approximation at high temperature + or when the energy levels of the J-substates are close together. In NLTE, the + assumption is less valid due to departure from the Boltzmann equation. + + Parameters + ---------- + species : str + Name of the atomic or ionic species in the string format expected by Cloudy. + lineinfo : pandas.DataFrame + One row containing the spectral line coefficients from NIST, from the + RT.read_NIST_lines() function. + en_df : dict + Dictionary mapping the column names of the .den file to their atomic configurations, + from the process_energies() function. + verbose : bool, optional + Whether to print out , by default False + + Returns + ------- + match : str + Column name of the .den file that contains the number densities of the energy + level that this spectral line originates from. + lineweight : float + Multiplication factor <1 for the number density of this energy level, to get + the number density of the specific J-state that the spectral line originates from. + """ + + en_df = en_df[ + en_df.species == species + ] # keep only the part for this species to not mix up the energy levels of different ones + match, lineweight = None, None # start with the assumption that we cannot match it + + # check if the line originates from a J sublevel, a term, or only principal quantum number + if str(lineinfo["term_i"]) != "nan" and str(lineinfo["J_i"]) != "nan": + linetype = "J" # then now match with configuration and term: + matchedrow = en_df[ + (en_df.configuration == lineinfo.conf_i) + & (en_df.term == lineinfo.term_i) + & (en_df.J == lineinfo.J_i) + ] + assert len(matchedrow) <= 1 + + if len(matchedrow) == 1: + match = matchedrow.index.item() + # since the Cloudy column is for this J specifically, we don't need + # to downweigh the density + lineweight = 1.0 + + elif len(matchedrow) == 0: + # the exact J was not found in Cloudy's levels, but maybe the term + # is there in Cloudy, just not resolved. + matchedtermrow = en_df[ + (en_df.configuration == lineinfo.conf_i) + & (en_df.term == lineinfo.term_i) + ] + + if len(matchedtermrow) == 1: + if str(matchedtermrow.J.values[0]) == "nan": + # This can only happen if the Cloudy level is a term with + # no J resolved. Then we use statistical weights to guess + # how many of the atoms in this term state would be in the + # J state of the level and use this as lineweight + L = Ldict[ + "".join( + x + for x in matchedtermrow.loc[:, "term"].item() + if x.isalpha() + )[-1] + ] # last letter in term string + S = ( + float( + re.search( + r"\d+", matchedtermrow.loc[:, "term"].item() + ).group() + ) + - 1.0 + ) / 2.0 # first number in term string + J_states = np.arange(np.abs(L - S), np.abs(L + S) + 1, 1.0) + J_statweights = 2 * J_states + 1 + J_probweights = J_statweights / np.sum(J_statweights) + + lineweight = J_probweights[ + J_states == Fraction(lineinfo.loc["J_i"]) + ][0] + + match = matchedtermrow.index.item() + else: + verbose_print( + f"One J level of the term is resolved, but not the one of this line: {species} " + + lineinfo.conf_i, + verbose=verbose, + ) + + else: + verbose_print( + f"Multiple J levels of the term are resolved, but not the one of this line: {species} " + + lineinfo.conf_i, + verbose=verbose, + ) + + elif str(lineinfo["term_i"]) != "nan": + linetype = "LS" + + verbose_print( + "Currently not able to do lines originating from LS state without J number.", + verbose=verbose, + ) + verbose_print( + f"Lower state configuration: {species} " + lineinfo.conf_i, verbose=verbose + ) + else: + linetype = "n" + + verbose_print( + "Currently not able to do lines originating from n state without term. " + "This is not a problem if this line is also in the NIST database with its " + "different term components, such as for e.g. H n=2, but only if they " + "aren't such as for H n>6, or if they go to an upper level n>6 from any " + "given level.", + verbose=verbose, + ) + verbose_print( + f"Lower state configuration: {species} " + lineinfo.conf_i, verbose=verbose + ) + + """ + DEVELOPERS NOTE: + If we do decide to make this functionality, for example by summing the densities + of all sublevels of a particular n, we also need to tweak the cleaning of + hydrogen lines algorithm. Right now, we remove double lines only for the upper + state, so e.g. for Ly alpha, we remove the separate 2p 3/2 and 2p 1/2 etc. + component and leave only the one line with upper state n=2. However, we don't + do this for lower states, which is not a problem yet because the lower n state + lines are ignored as stated above. However if we make the functionality, we + should also remove double lines in the lower level. + """ + + return match, lineweight + + +# ###################################### +# ########## MISCELLANEOUS ########### +# ###################################### + + +def verbose_print(message, verbose=False): + """ + Prints the provided string only if verbose is True. + + Parameters + ---------- + message : str + String to optionally print. + verbose : bool, optional + Whether to print the provided message, by default False + """ + + if verbose: + print(message) + + +def get_SED_norm_1AU(SEDname): + """ + Reads in an SED file and returns the normalization in monochromatic flux + (i.e., nu*F_nu or lambda*F_lambda) and Ryd units. + These are needed because Cloudy does not preserve the normalization of + user-specified SEDs. To do a simulation of an atmosphere, the normalization + of the SED must afterwards still be scaled to the planet distance. + Then, the log10 of nuFnu can be passed to Cloudy using the + "nuFnu(nu) = ... at ... Ryd" command. + This function requires that the units of the SED are Å and + monochromatic flux (i.e., nu*F_nu or lambda*F_lambda). + + Parameters + ---------- + SEDname : str + Name of a SED file located in $CLOUDY_PATH/data/SED/. + + Returns + ------- + nuFnu : float + Monochromatic flux specified at the energy of the Ryd output variable. + Ryd : float + Energy where the monochromatic flux of the nuFnu output variable is specified. + """ + + with open(f"{get_cloudy_path()}/data/SED/{SEDname}", "r", encoding="utf-8") as f: + for line in f: + if not line.startswith("#"): # skip through the comments at the top + assert ("angstrom" in line) or ("Angstrom" in line) # verify the units + assert "nuFnu" in line # verify the units + break + data = np.genfromtxt( + f, skip_header=1 + ) # skip first line, which has extra words specifying the units + + ang, nuFnu = data[-2, 0], data[-2, 1] # read out intensity somewhere + Ryd = 911.560270107676 / ang # convert wavelength in Å to energy in Ryd + + return nuFnu, Ryd + + +def speciesstring(specieslist, selected_levels=False, cloudy_version="17"): + """ + Takes a list of species names and returns a long string with those species + between quotes and [:] added (or [:maxlevel] if selected_levels=True), + and \n between them. This string can then be used in a Cloudy input + script for .den and .en files. The maxlevel is the number of energy levels + that can be matched between Cloudy and NIST. Saving higher levels than that is not + really useful since they cannot be postprocessed by the radiative transfer module. + + Parameters + ---------- + specieslist : list + Species to include. + selected_levels : bool, optional + If True, only energy levels up to the number that can be matched to NIST + will be included. If False, all energy levels of each species will be + included, regardless of whether we can match them to NIST. By default False. + cloudy_version : str, optional + Major Cloudy release version, by default "17" + + Returns + ------- + speciesstr : str + One long string containing the species and the energy level numbers. + """ + + if not selected_levels: # so just all levels available in cloudy + speciesstr = '"' + specieslist[0] + '[:]"' + if len(specieslist) > 1: + for species in specieslist[1:]: + speciesstr += '\n"' + species + '[:]"' + + elif ( + selected_levels + ): # then we read out the max level that we expect to match the energy of + speciesstr = ( + '"' + + specieslist[0] + + "[:" + + str(species_enlim.loc[specieslist[0], f"idx_C{cloudy_version}"]) + + ']"' + ) + if len(specieslist) > 1: + for species in specieslist[1:]: + speciesstr += ( + '\n"' + + species + + "[:" + + str(species_enlim.loc[species, f"idx_C{cloudy_version}"]) + + ']"' + ) + + return speciesstr + + +def read_parker(plname, temp, mdot, pdir, filename=None): + """ + Reads an isothermal Parker wind profile as generated by the construct_parker.py module. + + Parameters + ---------- + plname : str + Planet name (must have parameters stored in + $SUNBATHER_PROJECT_PATH/planets.txt). + temp : str or numeric + Temperature in units of K. + mdot : str or numeric + log of the mass-loss rate in units of g s-1. + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for example + profiles with different assumptions such as stellar SED/semi-major + axis/composition. + filename : str, optional + If None, the profile as specified by plname, temp, mdot, pdir is read. If not None, + filename must specify the full path + filename of the isothermal Parker wind + profile to read in. By default None. + + Returns + ------- + pprof : pandas.DataFrame + Radial density, velocity and mean particle mass profiles of the isothermal + Parker wind profile. + """ + + if filename is None: + mdot = f"{float(mdot):.3f}" + temp = str(int(temp)) + filename = ( + f"{get_sunbather_project_path()}/parker_profiles/{plname}/" + f"{pdir}/pprof_{plname}_T={temp}_M={mdot}.txt" + ) + + pprof = pd.read_table( + filename, names=["alt", "rho", "v", "mu"], dtype=np.float64, comment="#" + ) + pprof["drhodr"] = np.gradient(pprof["rho"], pprof["alt"]) + + return pprof + + +def calc_mu(rho, ne, abundances=None, mass=False): + """ + Calculates the mean particle mass of an atomic/ionic gas mixture, + but neglecting molecules (and the negligible mass contributed by + electrons). Based on formula: + mu = sum(ni*mi) / (sum(ni) + ne) + where ni and mi are the number density and mass of element i, and + ne is the electron number density. + Use ni = ntot * fi and ntot = rho / sum(fi*mi) + where ntot is the total number density, fi the abundance of element i + expressed as a 0 alt[0] # should be in ascending alt order + assert ( + alt[-1] - altmax * Rp > -1.0 + ) # For extrapolation: the alt scale should extend at least to within 1 cm of altmax*Rp + + if not np.isclose(alt[0], Rp, rtol=1e-2, atol=0.0): + warnings.warn( + f"Are you sure the altitude array starts at Rp? alt[0]/Rp = {alt[0]/Rp}" + ) + + depth = altmax * Rp - alt + ifunc = interp1d(depth, quantity, fill_value="extrapolate") + + Clgridr1 = np.logspace(np.log10(alt[0]), np.log10(altmax * Rp), num=int(0.8 * nmax)) + Clgridr1[0], Clgridr1[-1] = ( + alt[0], + altmax * Rp, + ) # reset these for potential log-numerical errors + Clgridr1 = (Clgridr1[-1] - Clgridr1)[::-1] + # sample the first 10 points better since Cloudy messes up with log-space interpolation there + Clgridr2 = np.logspace(-2, np.log10(Clgridr1[9]), num=nmax - len(Clgridr1)) + Clgridr = np.concatenate((Clgridr2, Clgridr1[10:])) + Clgridr[0] = 1e-35 + + Clgridq = ifunc(Clgridr) + law = np.column_stack((Clgridr, Clgridq)) + if log: + law[law[:, 1] == 0.0, 1] = 1e-100 + law = np.log10(law) + + return law + + +def smooth_gaus_savgol(y, size=None, fraction=None): + """ + Smooth an array using a Gaussian filter, but smooth the start and + end of the array with a Savitzky-Golay filter. + + Parameters + ---------- + y : array-like + Array to smooth. + size : int, optional + Smoothing size expressed as a number of points that will serve as the Gaussian + standard deviation. If None, instead a fraction must be provided, by default None + fraction : float, optional + Smoothing size expressed as a fraction of the total array length + that will serve as the Gaussian standard deviation. If None, instead + a size must be provided, by default None + + Returns + ------- + ysmooth : numpy.ndarray + Smoothed array. + + Raises + ------ + ValueError + If neither or both size and fraction were provided. + """ + + if size is not None and fraction is None: + size = max(3, size) + elif fraction is not None and size is None: + assert ( + 0.0 < fraction < 1.0 + ), "fraction must be greater than 0 and smaller than 1" + size = int(np.ceil(len(y) * fraction) // 2 * 2 + 1) # make it odd + size = max(3, size) + else: + raise ValueError("Please provide either 'size' or 'fraction'.") + + ygaus = gaussian_filter1d(y, size) + ysavgol = savgol_filter(y, 2 * int(size / 2) + 1, polyorder=2) + + savgolweight = np.zeros(len(y)) + savgolweight += sps.norm.pdf(range(len(y)), 0, size) + savgolweight += sps.norm.pdf(range(len(y)), len(y), size) + savgolweight /= np.max(savgolweight) # normalize + gausweight = 1 - savgolweight + + ysmooth = ygaus * gausweight + ysavgol * savgolweight + + return ysmooth + + +# ###################################### +# ########## CLOUDY I/O ########## +# ###################################### + + +def run_Cloudy(filename, folder=None): + """ + Run a Cloudy simulation from within Python. + + Parameters + ---------- + filename : str + Name of the simulation input file. If the folder argument is not + specfied, filename must include the full path to the simulation. + If the folder argument is specified, the filename should only + specify the filename. + folder : str, optional + Full path to the directory where the file is located, excluding + the filename itself, which must be specified with the filename + argument. If folder is None, filename must also include the + full path. By default None. + """ + + if folder is None: # then the folder should be in the simname + folder, filename = os.path.split(filename) + + if filename.endswith(".in"): + filename = filename[:-3] # filename should not contain the extension + + os.system(f"cd {folder} && {get_cloudy_path()}/source/cloudy.exe -p {filename}") + + +def remove_duplicates(law, fmt): + """ + Takes a Cloudy law (e.g., dlaw or tlaw) and a formatter, and removes + duplicate rows from the law. This is mainly for the illuminated side of the + simulation, where we have a very finely sampled grid which can result in + duplicate values after applying the string formatter. This function thus + does not alter the law in any way, but merely improves readability of the + Cloudy .in file laws as the many (obsolete) duplicate rows are removed. + + Parameters + ---------- + law : numpy.ndarray + Quantity on a 'depth'-grid as a 2D array, in the format that Cloudy expects it. + fmt : str + String formatter specifying a float precision. This function will remove + floats that are duplicate up to the precision implied by this fmt formatter. + + Returns + ------- + new_law : numpy.ndarray + Same quantity but with rows removed that have the same float precision + under the provided fmt formatter. + """ + + nonduplicates = [0] + for i in range(1, len(law) - 1): + if format(law[i, 1], fmt) != format(law[i - 1, 1], fmt) or format( + law[i, 1], fmt + ) != format(law[i + 1, 1], fmt): + nonduplicates.append(i) + nonduplicates.append(-1) + + new_law = law[nonduplicates] + + return new_law + + +def copyadd_Cloudy_in( + oldsimname, + newsimname, + set_thickness=False, + dlaw=None, + tlaw=None, + cextra=None, + hextra=None, + othercommands=None, + outfiles=None, + denspecies=None, + selected_den_levels=False, + constant_temp=None, + double_tau=False, + hcfrac=None, + cloudy_version="17", +): + """ + Makes a copy of a Cloudy input file and appends commands. + + Parameters + ---------- + oldsimname : str + Full path + name of the Cloudy input file to copy, without the file extension. + newsimname : str + Full path + name of the target Cloudy input file, without the file extension. + set_thickness : bool, optional + Whether to include a command that ends the simulation at a depth equal + to the length of the dlaw, by default True + dlaw : numpy.ndarray, optional + Hydrogen number density in units of cm-3, as a 2D array where dlaw[:,0] + specifies the log10 of the depth into the cloud in cm, and dlaw[:,1] + specifies the log10 of the hydrogen number density in units of cm-3, by default + None + tlaw : numpy.ndarray, optional + Temperature in units of K as a 2D array where tlaw[:,0] + specifies the log10 of the depth into the cloud in cm, and tlaw[:,1] + specifies the log10 of the temperature in units of K, by default None + cextra : numpy.ndarray, optional + Extra unspecified cooling in units of erg s-1 cm-3, as a 2D array where + cextra[:,0] specifies the log10 of the depth into the cloud in cm, + and cextra[:,1] specifies the log10 of the cooling rate in units of + erg s-1 cm-3, by default None + hextra : numpy.ndarray, optional + Extra unspecified heating in units of erg s-1 cm-3, as a 2D array where + hextra[:,0] specifies the log10 of the depth into the cloud in cm, + and hextra[:,1] specifies the log10 of the heating rate in units of + erg s-1 cm-3, by default None + othercommands : str, optional + String to include in the input file. Any command not otherwise supported + by this function can be included here, by default None + outfiles : list, optional + List of file extensions indicating which Cloudy output to save. For example, + include '.heat' to include the 'save heating' command, by default ['.ovr', + '.cool'] + denspecies : list, optional + List of atomic/ionic species for which to save densities and energies, which + are needed to do radiative transfer. The list can easily be created by the + get_specieslist() function. By default []. + selected_den_levels : bool, optional + If True, only energy levels up to the number that can be matched to NIST + will be included in the 'save densities' command. If False, all energy levels + of each species will be included, regardless of whether we can match them + to NIST. By default False. + constant_temp : str or numeric, optional + Constant temperature in units of K, by default None + double_tau : bool, optional + Whether to use the 'double optical depths' command. This command is useful + for 1D simulations, ensuring that radiation does not escape the atmosphere + at the back-side into the planet core. By default False + hcfrac : str or numeric, optional + Threshold fraction of the total heating/cooling rate for which the .heat and + .cool files should save agents. Cloudy's default is 0.05, so that individual + heating and cooling processes contributing <0.05 of the total are not saved. + By default None, so that Cloudy's default of 0.05 is used. + cloudy_version : str, optional + Major Cloudy release version, used only in combination with the denspecies + argument, by default "17". + """ + if outfiles is None: + outfiles = [] + if denspecies is None: + denspecies = [] + + if denspecies != []: + assert ".den" in outfiles and ".en" in outfiles + if ".den" in outfiles or ".en" in outfiles: + assert ".den" in outfiles and ".en" in outfiles + if constant_temp is not None: + assert not np.any(tlaw is not None) + + copyfile(oldsimname + ".in", newsimname + ".in") + + with open(newsimname + ".in", "a", encoding="utf-8") as f: + if set_thickness: + f.write( + "\nstop thickness " + + "{:.7f}".format(dlaw[-1, 0]) + + "\t#last dlaw point" + ) + if ".ovr" in outfiles: + f.write('\nsave overview ".ovr" last') + if ".cool" in outfiles: + f.write('\nsave cooling ".cool" last') + if ".coolH2" in outfiles: + f.write('\nsave H2 cooling ".coolH2" last') + if ".heat" in outfiles: + f.write('\nsave heating ".heat" last') + if ".con" in outfiles: + f.write('\nsave continuum ".con" last units angstrom') + if ( + ".den" in outfiles + ): # then ".en" is always there as well due to the assertion above + if denspecies != []: + f.write( + '\nsave species densities last ".den"\n' + + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\nend" + ) + f.write( + '\nsave species energies last ".en"\n' + + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\nend" + ) + if constant_temp is not None: + f.write("\nconstant temperature t= " + str(constant_temp) + " linear") + if double_tau: + f.write( + "\ndouble optical depths #so radiation does not escape into planet " + "core freely" + ) + if hcfrac: + f.write( + "\nset WeakHeatCool " + + str(hcfrac) + + " #for .heat and .cool output files" + ) + if othercommands is not None: + f.write("\n" + othercommands) + if np.any(dlaw is not None): + dlaw = remove_duplicates(dlaw, "1.7f") + f.write("\n# ========= density law ================") + f.write("\n#depth sets distances from edge of cloud") + f.write("\ndlaw table depth\n") + np.savetxt(f, dlaw, fmt="%1.7f") + f.write( + "{:.7f}".format(dlaw[-1, 0] + 0.1) + " " + "{:.7f}".format(dlaw[-1, 1]) + ) + f.write("\nend of dlaw #last point added to prevent roundoff") + if np.any(tlaw is not None): + tlaw = remove_duplicates(tlaw, "1.7f") + f.write("\n# ========= temperature law ============") + f.write("\n#depth sets distances from edge of cloud") + f.write("\ntlaw table depth\n") + np.savetxt(f, tlaw, fmt="%1.7f") + f.write( + "{:.7f}".format(tlaw[-1, 0] + 0.1) + " " + "{:.7f}".format(tlaw[-1, 1]) + ) + f.write("\nend of tlaw #last point added to prevent roundoff") + if np.any(cextra is not None): + cextra = remove_duplicates(cextra, "1.7f") + f.write("\n# ========= cextra law ================") + f.write("\n#depth sets distances from edge of cloud") + f.write("\ncextra table depth\n") + np.savetxt(f, cextra, fmt="%1.7f") + f.write( + "{:.7f}".format(cextra[-1, 0] + 0.1) + + " " + + "{:.7f}".format(cextra[-1, 1]) + ) + f.write("\nend of cextra #last point added to prevent roundoff") + if np.any(hextra is not None): + hextra = remove_duplicates(hextra, "1.7f") + f.write("\n# ========= hextra law ================") + f.write("\n#depth sets distances from edge of cloud") + f.write("\nhextra table depth\n") + np.savetxt(f, hextra, fmt="%1.7f") + f.write( + "{:.7f}".format(hextra[-1, 0] + 0.1) + + " " + + "{:.7f}".format(hextra[-1, 1]) + ) + f.write("\nend of hextra #last point added to prevent roundoff") + + +def write_Cloudy_in( + simname, + title=None, + flux_scaling=None, + SED=None, + set_thickness=True, + dlaw=None, + tlaw=None, + cextra=None, + hextra=None, + othercommands=None, + overwrite=False, + iterate="convergence", + nend=3000, + outfiles=None, + denspecies=None, + selected_den_levels=False, + constant_temp=None, + double_tau=False, + cosmic_rays=False, + zdict=None, + hcfrac=None, + comments=None, + cloudy_version="17", +): + """ + Writes a Cloudy input file for simulating an exoplanet atmosphere. + + Parameters + ---------- + simname : str + Full path + name of the Cloudy simulation, without the file extension. + title : str, optional + Title of simulation, by default None + flux_scaling : tuple, optional + Normalization of the SED, as a tuple with the monochromatic flux + and energy in Ryd where it is specified, by default None + SED : str, optional + Name of a SED file located in $CLOUDY_PATH/data/SED/, by default None + set_thickness : bool, optional + Whether to include a command that ends the simulation at a depth equal + to the length of the dlaw, by default True + dlaw : numpy.ndarray, optional + Hydrogen number density in units of cm-3, as a 2D array where dlaw[:,0] + specifies the log10 of the depth into the cloud in cm, and dlaw[:,1] + specifies the log10 of the hydrogen number density in units of cm-3, by default None + tlaw : numpy.ndarray, optional + Temperature in units of K as a 2D array where tlaw[:,0] + specifies the log10 of the depth into the cloud in cm, and tlaw[:,1] + specifies the log10 of the temperature in units of K, by default None + cextra : numpy.ndarray, optional + Extra unspecified cooling in units of erg s-1 cm-3, as a 2D array where + cextra[:,0] specifies the log10 of the depth into the cloud in cm, + and cextra[:,1] specifies the log10 of the cooling rate in units of + erg s-1 cm-3, by default None + hextra : numpy.ndarray, optional + Extra unspecified heating in units of erg s-1 cm-3, as a 2D array where + hextra[:,0] specifies the log10 of the depth into the cloud in cm, + and hextra[:,1] specifies the log10 of the heating rate in units of + erg s-1 cm-3, by default None + othercommands : str, optional + String to include in the input file. Any command not otherwise supported + by this function can be included here, by default None + overwrite : bool, optional + Whether to overwrite the simname if it already exists, by default False + iterate : str or int, optional + Argument to Cloudy's 'iterate' command, either a number or 'convergence', + by default 'convergence' + nend : int, optional + Argument to Cloudy's 'set nend' command, which sets the maximum number of Cloudy + cells. Cloudy's default is 1400 which can often be too few. For this function, + by default 3000. + outfiles : list, optional + List of file extensions indicating which Cloudy output to save. For example, + include '.heat' to include the 'save heating' command, by default ['.ovr', '.cool'] + denspecies : list, optional + List of atomic/ionic species for which to save densities and energies, which + are needed to do radiative transfer. The list can easily be created by the + get_specieslist() function. By default []. + selected_den_levels : bool, optional + If True, only energy levels up to the number that can be matched to NIST + will be included in the 'save densities' command. If False, all energy levels + of each species will be included, regardless of whether we can match them + to NIST. By default False. + constant_temp : str or numeric, optional + Constant temperature in units of K, by default None + double_tau : bool, optional + Whether to use the 'double optical depths' command. This command is useful + for 1D simulations, ensuring that radiation does not escape the atmosphere + at the back-side into the planet core. By default False + cosmic_rays : bool, optional + Whether to include cosmic rays, by default False + zdict : dict, optional + Dictionary with the scale factors of all elements relative + to a solar composition. Can be easily created with get_zdict(). + Default is None, which results in a solar composition. + hcfrac : str or numeric, optional + Threshold fraction of the total heating/cooling rate for which the .heat and + .cool files should save agents. Cloudy's default is 0.05, so that individual + heating and cooling processes contributing <0.05 of the total are not saved. + By default None, so that Cloudy's default of 0.05 is used. + comments : str, optional + Comments to write at the top of the input file. Make sure to include hashtags + in the string, by default None + cloudy_version : str, optional + Major Cloudy release version, used only in combination with the denspecies + argument, by default "17". + """ + if outfiles is None: + outfiles = [".ovr", ".cool"] + + if denspecies is None: + denspecies = [] + + assert ( + flux_scaling is not None + ) # we need this to proceed. Give in format [F,E] like nuF(nu) = F at E Ryd + assert SED is not None + if denspecies != []: + assert ".den" in outfiles and ".en" in outfiles + if ".den" in outfiles or ".en" in outfiles: + assert ".den" in outfiles and ".en" in outfiles and denspecies != [] + if not overwrite: + assert not os.path.isfile(simname + ".in") + if constant_temp is not None: + assert not np.any(tlaw is not None) + + with open(simname + ".in", "w", encoding="utf-8") as f: + if comments is not None: + f.write(comments + "\n") + if title is not None: + f.write("title " + title) + f.write("\n# ========= input spectrum ================") + f.write( + "\nnuF(nu) = " + + str(flux_scaling[0]) + + " at " + + str(flux_scaling[1]) + + " Ryd" + ) + f.write('\ntable SED "' + SED + '"') + if cosmic_rays: + f.write("\ncosmic rays background") + f.write("\n# ========= chemistry ================") + f.write("\n# solar abundances and metallicity is standard") + if zdict is not None: + for element in zdict.keys(): + if zdict[element] == 0.0: + f.write("\nelement " + element_names[element] + " off") + elif ( + zdict[element] != 1.0 + ): # only write it to Cloudy if the scale factor is not 1 + f.write( + "\nelement scale factor " + + element_names[element] + + " " + + str(zdict[element]) + ) + f.write("\n# ========= other ================") + if nend is not None: + f.write( + "\nset nend " + + str(nend) + + " #models at high density need >1400 zones" + ) + f.write("\nset temperature floor 5 linear") + f.write("\nstop temperature off #otherwise it stops at 1e4 K") + if iterate == "convergence": + f.write("\niterate to convergence") + else: + f.write("niterate " + str(iterate)) + f.write("\nprint last iteration") + if set_thickness: + f.write( + "\nstop thickness " + + "{:.7f}".format(dlaw[-1, 0]) + + "\t#last dlaw point" + ) + if constant_temp is not None: + f.write("\nconstant temperature t= " + str(constant_temp) + " linear") + if double_tau: + f.write( + "\ndouble optical depths #so radiation does not escape into planet core freely" + ) + if hcfrac: + f.write( + "\nset WeakHeatCool " + + str(hcfrac) + + " #for .heat and .cool output files" + ) + if othercommands is not None: + f.write("\n" + othercommands) + f.write("\n# ========= output ================") + if ".ovr" in outfiles: + f.write('\nsave overview ".ovr" last') + if ".cool" in outfiles: + f.write('\nsave cooling ".cool" last') + if ".coolH2" in outfiles: + f.write('\nsave H2 cooling ".coolH2" last') + if ".heat" in outfiles: + f.write('\nsave heating ".heat" last') + if ".con" in outfiles: + f.write('\nsave continuum ".con" last units angstrom') + if ".den" in outfiles: # then ".en" is always there as well. + f.write( + '\nsave species densities last ".den"\n' + + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\nend" + ) + f.write( + '\nsave species energies last ".en"\n' + + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\nend" + ) + if dlaw is not None: + dlaw = remove_duplicates(dlaw, "1.7f") + f.write("\n# ========= density law ================") + f.write("\n#depth sets distances from edge of cloud") + f.write("\ndlaw table depth\n") + np.savetxt(f, dlaw, fmt="%1.7f") + f.write( + "{:.7f}".format(dlaw[-1, 0] + 0.1) + " " + "{:.7f}".format(dlaw[-1, 1]) + ) + f.write("\nend of dlaw #last point added to prevent roundoff") + if tlaw is not None: + tlaw = remove_duplicates(tlaw, "1.7f") + f.write("\n# ========= temperature law ============") + f.write("\n#depth sets distances from edge of cloud") + f.write("\ntlaw table depth\n") + np.savetxt(f, tlaw, fmt="%1.7f") + f.write( + "{:.7f}".format(tlaw[-1, 0] + 0.1) + " " + "{:.7f}".format(tlaw[-1, 1]) + ) + f.write("\nend of tlaw #last point added to prevent roundoff") + if cextra is not None: + cextra = remove_duplicates(cextra, "1.7f") + f.write("\n# ========= cextra law ================") + f.write("\n#depth sets distances from edge of cloud") + f.write("\ncextra table depth\n") + np.savetxt(f, cextra, fmt="%1.7f") + f.write( + "{:.7f}".format(cextra[-1, 0] + 0.1) + + " " + + "{:.7f}".format(cextra[-1, 1]) + ) + f.write("\nend of cextra #last point added to prevent roundoff") + if hextra is not None: + hextra = remove_duplicates(hextra, "1.7f") + f.write("\n# ========= hextra law ================") + f.write("\n#depth sets distances from edge of cloud") + f.write("\nhextra table depth\n") + np.savetxt(f, hextra, fmt="%1.7f") + f.write( + "{:.7f}".format(hextra[-1, 0] + 0.1) + + " " + + "{:.7f}".format(hextra[-1, 1]) + ) + f.write("\nend of hextra #last point added to prevent roundoff") + + +def insertden_Cloudy_in( + simname, denspecies, selected_den_levels=True, rerun=False, cloudy_version="17" +): + """ + Takes a Cloudy .in input file and adds extra species to the + 'save species densities' command. This is useful for example if you first went + through the convergeT_parker.py temperature convergence scheme, + but later want to add additional species to the 'converged' simulation. + + Parameters + ---------- + simname : str + Full path + name of the Cloudy simulation, without the file extension. + denspecies : list, optional + List of atomic/ionic species for which to save densities and energies, which + are needed to do radiative transfer. The list can easily be created by the + get_specieslist() function. + selected_den_levels : bool, optional + If True, only energy levels up to the number that can be matched to NIST + will be included in the 'save densities' command. If False, all energy levels + of each species will be included, regardless of whether we can match them + to NIST. By default True. + rerun : bool, optional + Whether to run the new Cloudy input file, by default False + cloudy_version : str, optional + Major Cloudy release version, by default "17". + + Raises + ------ + ValueError + If there are multiple 'save species densities' commands in the Cloudy input file. + """ + + with open(simname + ".in", "r", encoding="utf-8") as f: + oldcontent = f.readlines() + + newcontent = oldcontent + indices = [i for i, s in enumerate(oldcontent) if "save species densities" in s] + if len(indices) == 0: # then there is no 'save species densities' command yet + newcontent.append( + '\nsave species densities last ".den"\n' + + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\nend" + ) + newcontent.append( + '\nsave species energies last ".en"\n' + + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\nend" + ) + + elif ( + len(indices) == 1 + ): # then there already is a 'save species densities' command with some species + for sp in denspecies.copy(): + if ( + len([i for i, s in enumerate(oldcontent) if sp + "[" in s]) != 0 + ): # check if this species is already in the file + denspecies.remove(sp) + print(sp, "was already in the .in file so I did not add it again.") + if len(denspecies) >= 1: + newcontent.insert( + indices[0] + 1, + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\n", + ) + # also add them to the 'save species energies' list + indices2 = [ + i for i, s in enumerate(oldcontent) if "save species energies" in s + ] + newcontent.insert( + indices2[0] + 1, + speciesstring( + denspecies, + selected_levels=selected_den_levels, + cloudy_version=cloudy_version, + ) + + "\n", + ) + else: + return + + else: + raise ValueError( + "There are multiple 'save species densities' commands in the .in file. This shouldn't be the case, please check." + ) + + newcontent = "".join(newcontent) # turn list into string + with open(simname + ".in", "w", encoding="utf-8") as f: # overwrite the old file + f.write(newcontent) + + if rerun: + run_Cloudy(simname) + + +# ###################################### +# ########## CLASSES ########### +# ###################################### + + +class Parker: + """ + Class that stores a Parker wind profile and its parameters. + """ + + def __init__( + self, plname, temp, mdot, pdir, fH=None, zdict=None, SED=None, readin=True + ): + """ + Parameters + ---------- + plname : str + Name of the planet + temp : str or numeric + Temperature in units of K. + mdot : str or numeric + log10 of the mass-loss rate in units of g s-1. + pdir : str + Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ + where the isothermal parker wind density and velocity profiles are saved. + Different folders may exist there for a given planet, to separate for example profiles + with different assumptions such as stellar SED/semi-major axis/composition. + fH : float, optional + Hydrogen abundance fraction, in case of a H/He composition, by default None + zdict : dict, optional + Dictionary with the scale factors of all elements relative + to a solar composition. Can be easily created with get_zdict(). + Default is None, which results in a solar composition. + SED : str, optional + Stellar SED name, by default None + readin : bool, optional + Whether to read in the atmospheric profile, by default True + """ + + self.plname = plname + self.temp = int(temp) + if isinstance(mdot, str): + self.mdot = mdot + self.mdotf = float(mdot) + elif isinstance(mdot, (float, int)): + self.mdot = "%.3f" % mdot + self.mdotf = mdot + if fH is not None: + self.fH = fH + if zdict is not None: + self.zdict = zdict + if SED is not None: + self.SED = SED + if readin: + self.prof = read_parker(plname, temp, mdot, pdir) + + +class Planet: + """ + Class that stores planet/star parameters. + """ + + def __init__( + self, + name, + fullname=None, + R=None, + Rstar=None, + a=None, + M=None, + Mstar=None, + bp=None, + SEDname=None, + ): + """ + Parameters + ---------- + name : str + Planet name. Typically does not include spaces. If this name appears in the + $SUNBATHER_PROJECT_PATH/planets.txt file, those parameters are automatically + fetched. Specific values can then be changed by providing them as arguments. + If the planet name does not appear in $SUNBATHER_PROJECT_PATH/planets.txt, + all parameters must be provided upon initialization. + fullname : str, optional + Full planet name, can include spaces and other special characters, by default None + R : float, optional + Planet radius in units of cm, by default None + Rstar : float, optional + Star radius in units of cm, by default None + a : float, optional + Semi-major axis in units of cm, by default None + M : float, optional + Planet mass in units of g, by default None + Mstar : float, optional + Star mass in units of g, by default None + bp : float, optional + Transit impact parameter, in units of the star radius, by default None + SEDname : str, optional + Stellar SED name, by default None + """ + + # check if we can fetch planet parameters from planets.txt: + planets = read_planets_file() + if name in planets["name"].values or name in planets["full name"].values: + this_planet = planets[ + (planets["name"] == name) | (planets["full name"] == name) + ] + assert ( + len(this_planet) == 1 + ), "Multiple entries were found in planets.txt for this planet name." + + self.name = this_planet["name"].values[0] + self.fullname = this_planet["full name"].values[0] + self.R = this_planet["R [RJ]"].values[0] * RJ # in cm + self.Rstar = this_planet["Rstar [Rsun]"].values[0] * Rsun # in cm + self.a = this_planet["a [AU]"].values[0] * AU # in cm + self.M = this_planet["M [MJ]"].values[0] * MJ # in g + self.Mstar = this_planet["Mstar [Msun]"].values[0] * Msun # in g + self.bp = this_planet["transit impact parameter"].values[0] # dimensionless + self.SEDname = ( + this_planet["SEDname"].values[0].strip() + ) # strip to remove whitespace from beginning and end + + # if any specified, overwrite values read from planets.txt + if fullname is not None: + self.fullname = fullname + if R is not None: + self.R = R + if Rstar is not None: + self.Rstar = Rstar + if a is not None: + self.a = a + if M is not None: + self.M = M + if Mstar is not None: + self.Mstar = Mstar + if bp is not None: + self.bp = bp + if SEDname is not None: + self.SEDname = SEDname + + else: + assert ( + fullname is not None + and R is not None + and Rstar is not None + and a is not None + and M is not None + and Mstar is not None + and bp is not None + and SEDname is not None + ), "I'm trying to make a Planet that is not in the planets.txt file, but I don't have all required arguments." + self.name = name + self.fullname = fullname + self.R = R + self.Rstar = Rstar + self.a = a + self.M = M + self.Mstar = Mstar + self.bp = bp + self.SEDname = SEDname + + self.__update_Rroche() + self.__update_phi() + self.__update_Kp() + + def set_var( + self, + name=None, + fullname=None, + R=None, + Rstar=None, + a=None, + M=None, + Mstar=None, + bp=None, + SEDname=None, + ): + """ + Change planet/star parameters after initialization. + """ + + if name is not None: + self.name = name + if R is not None: + self.R = R + self.__update_phi() + if Rstar is not None: + self.Rstar = Rstar + if a is not None: + self.a = a + self.__update_Rroche() + self.__update_Kp() + if M is not None: + self.M = M + self.__update_phi() + self.__update_Rroche() + self.__update_Kp() + if Mstar is not None: + self.Mstar = Mstar + self.__update_Rroche() + self.__update_Kp() + if bp is not None: + self.bp = bp + if SEDname is not None: + self.SEDname = SEDname + + def __update_phi(self): + """ + Tries to set/update the gravitational potential. + """ + + if (self.M is not None) and (self.R is not None): + self.phi = G * self.M / self.R + else: + self.phi = None + + def __update_Rroche(self): + """ + Tries to set/update the Roche radius. + """ + + if (self.a is not None) and (self.M is not None) and (self.Mstar is not None): + self.Rroche = roche_radius(self.a, self.M, self.Mstar) + else: + self.Rroche = None + + def __update_Kp(self): + """ + Tries to set/update the orbital velocity semi-amplitude. + """ + + if (self.a is not None) and (self.M is not None) and (self.Mstar is not None): + self.Kp = np.sqrt(G * (self.M + self.Mstar) / self.a) + else: + self.Kp = None + + def print_params(self): + """ + Prints out all parameters in read-friendly format. + """ + + print(f"Name: {self.name}") + if self.fullname is not None: + print(f"Full name: {self.fullname}") + if self.R is not None: + print(f"Planet radius: {self.R} cm, {self.R / RJ} RJ") + if self.Rstar is not None: + print(f"Star radius: {self.Rstar} cm, {self.Rstar / Rsun} Rsun") + if self.a is not None: + print(f"Semi-major axis: {self.a} cm, {self.a / AU} AU") + if self.M is not None: + print(f"Planet mass: {self.M} g, {self.M / MJ} MJ") + if self.Mstar is not None: + print(f"Star mass: {self.Mstar} g, {self.Mstar / Msun} Msun") + if self.bp is not None: + print(f"Transit impact parameter: {self.bp} Rstar") + if self.SEDname is not None: + print(f"Stellar spectrum name: {self.SEDname}") + if self.Rroche is not None: + print( + f"Roche radius: {self.Rroche} cm, {self.Rroche / RJ} RJ, " + f"{self.Rroche / self.R} Rp" + ) + if self.phi is not None: + print(f"log10(Gravitational potential): {np.log10(self.phi)} log10(erg/g)") + if self.Kp is not None: + print( + f"Orbital velocity semi-amplitude: {self.Kp} cm/s, {self.Kp/1e5} km/s" + ) + + def plot_transit_geometry(self, phase=0.0, altmax=None): + """ + Plots a schematic of the transit geometry. Helpful to understand where the + planet and its atmosphere are relative to the stellar disk, for a given planet + impact parameter and phase. The dotted line shows the planet Roche radius. The + altmax argument can be used to draw another dashed line in units of the planet + radius, for example the extent of the sunbather simulation (typically 8 Rp). + """ + + fig, ax = plt.subplots(1) + title = "" + # draw star + ax.plot( + self.Rstar * np.cos(np.linspace(0, 2 * np.pi, 100)), + self.Rstar * np.sin(np.linspace(0, 2 * np.pi, 100)), + c="k", + zorder=0, + ) + ax.text( + 1 / np.sqrt(2) * self.Rstar, + -1 / np.sqrt(2) * self.Rstar, + r"$R_s$", + color="k", + ha="left", + va="top", + zorder=0, + ) + + # draw planet + pl_zorder = -1 if (phase % 1 > 0.25 and phase % 1 < 0.75) else 1 + ax.plot( + self.a * np.sin(2 * np.pi * phase) + + self.R * np.cos(np.linspace(0, 2 * np.pi, 100)), + self.bp * self.Rstar + self.R * np.sin(np.linspace(0, 2 * np.pi, 100)), + c="b", + zorder=pl_zorder, + ) + ax.text( + self.a * np.sin(2 * np.pi * phase) + 1 / np.sqrt(2) * self.R, + self.bp * self.Rstar - 1 / np.sqrt(2) * self.R, + r"$R_P$", + color="b", + ha="left", + va="top", + zorder=pl_zorder, + ) + + # draw planet vy direction + if phase % 1 > 0.75 or phase % 1 < 0.25: + ax.text( + self.a * np.sin(2 * np.pi * phase) + self.R, + self.bp * self.Rstar, + r"$\rightarrow$", + color="b", + ha="left", + va="top", + zorder=pl_zorder, + ) + title = f"Phase: {phase} mod 1 = {phase % 1}" + elif phase % 1 > 0.25 and phase % 1 < 0.75: + ax.text( + self.a * np.sin(2 * np.pi * phase) - self.R, + self.bp * self.Rstar, + r"$\leftarrow$", + color="b", + ha="right", + va="top", + zorder=pl_zorder, + ) + title = f"Phase: {phase} mod 1 = {phase % 1} (planet behind star)" + else: # at 0.25 or 0.75, only vx velocity + pass + + # draw Roche indication + if self.Rroche is not None: + ax.plot( + self.a * np.sin(2 * np.pi * phase) + + self.Rroche * np.cos(np.linspace(0, 2 * np.pi, 100)), + self.bp * self.Rstar + + self.Rroche * np.sin(np.linspace(0, 2 * np.pi, 100)), + c="b", + linestyle="dotted", + ) + ax.text( + self.a * np.sin(2 * np.pi * phase) + 1 / np.sqrt(2) * self.Rroche, + self.bp * self.Rstar - 1 / np.sqrt(2) * self.Rroche, + r"$R_{Roche}$", + color="b", + ha="left", + va="top", + zorder=pl_zorder, + ) + + # draw altmax indication + if altmax is not None: + ax.plot( + self.a * np.sin(2 * np.pi * phase) + + altmax * self.R * np.cos(np.linspace(0, 2 * np.pi, 100)), + self.bp * self.Rstar + + altmax * self.R * np.sin(np.linspace(0, 2 * np.pi, 100)), + c="b", + linestyle="dashed", + ) + ax.text( + self.a * np.sin(2 * np.pi * phase) + altmax / np.sqrt(2) * self.R, + self.bp * self.Rstar - altmax / np.sqrt(2) * self.R, + "altmax", + color="b", + ha="left", + va="top", + zorder=pl_zorder, + ) + + plt.axis("equal") + ax.set_xlabel("y [cm]") + ax.set_ylabel("z [cm]") + ax.set_title(title) + plt.show() + + def max_T0(self, mu_bar=1.0): + """ + Calculates the maximum isothermal temperature T0 that the Parker wind can have, + for it to still be transonic. If T0 is higher than this value, + Rp > Rs which breaks the assumption of the Parker wind. + See Vissapragada et al. (2024) on TOI-1420 b. + """ + + maxT0 = G * self.M * mH * mu_bar / (2 * self.R * k) + + return maxT0 + + +class Sim: + """ + Loads the output of a Cloudy simulation. Tailored towards simulations of + an escaping exoplanet atmosphere. + """ + + def __init__( + self, + simname, + altmax=None, + proceedFail=False, + files="all", + planet=None, + parker=None, + ): + """ + Parameters + ---------- + simname : str + Full path + simulation name excluding file extension. + altmax : int, optional + Maximum altitude of the simulation in units of the planet radius. Will also + be automatically read from the input file if written as a comment. By + default None. + proceedFail : bool, optional + Whether to proceed loading the simulation if Cloudy did not exit OK, by + default False + files : list, optional + List of file extensions of Cloudy output to load. For example, include + '.heat' to read the output of the 'save heating' command. By default + ['all'], which reads in all output files present that are understood by this + class. + planet : Planet, optional + Object storing planet parameters. Will also be automatically read from the + input file if written as a comment. By default None. + parker : Parker, optional + Object storing the isothermal Parker wind atmospheric profiles and + parameters. Will also be automatically read from the input file if written + as a comment. By default None. + + Raises + ------ + TypeError + If the simname argument is not a string. + TypeError + If a Cloudy version was used that is not supported by sunbather. + FileNotFoundError + If the Cloudy simulation did not exit OK and proceedFail = False. + TypeError + If the altmax argument is not numeric. + """ + if isinstance(files, str): + files = [files] + + if not isinstance(simname, str): + raise TypeError("simname must be set to a string") + self.simname = simname + + # check the Cloudy version, and if the simulation did not crash. + _succesful = False + with open(simname + ".out", "r", encoding="utf-8") as f: + _outfile_content = f.read() + _succesful = "Cloudy exited OK" in _outfile_content + + if "Cloudy 17" in _outfile_content: + self.cloudy_version = "17" + elif "Cloudy 23" in _outfile_content or "Cloudy (c23" in _outfile_content: + self.cloudy_version = "23" + elif _succesful: + print(f"sim output file: {simname}.out") + raise TypeError( + f"This simulation did not use Cloudy v17 or v23, which are the " + f"only supported versions: {simname}" + ) + if not _succesful and not proceedFail: + raise FileNotFoundError( + f"This simulation went wrong: {simname} Check the .out file!" + ) + + # read the .in file to extract some sim info like changes to the chemical + # composition and altmax + self.disabled_elements = [] + zelem = {} + _parker_T, _parker_mdot, _parker_dir = None, None, None # temp variables + with open(simname + ".in", "r", encoding="utf-8") as f: + for line in f: + if ( + line[0] == "#" + ): # then it is a comment written by sunbather, extract info: + # check if a planet was defined + if "plname" in line: + self.p = Planet(line.split("=")[-1].strip("\n")) + + # check if a Parker profile was defined + if "parker_T" in line: + _parker_T = int(line.split("=")[-1].strip("\n")) + if "parker_mdot" in line: + _parker_mdot = line.split("=")[-1].strip("\n") + if "parker_dir" in line: + _parker_dir = line.split("=")[-1].strip("\n") + + # check if an altmax was defined + if "altmax" in line: + self.altmax = int(line.split("=")[1].strip("\n")) + + # read SED + if "table SED" in line: + self.SEDname = line.split('"')[1] + + # read chemical composition + if "element scale factor" in line.rstrip(): + zelem[element_symbols[line.split(" ")[3]]] = float( + line.rstrip().split(" ")[-1] + ) + elif "element" in line.rstrip() and "off" in line.rstrip(): + self.disabled_elements.append(element_symbols[line.split(" ")[1]]) + zelem[element_symbols[line.split(" ")[1]]] = 0.0 + + # set zdict and abundances as attributes + self.zdict = get_zdict(zelem=zelem) + self.abundances = get_abundances(zdict=self.zdict) + + # overwrite/set manually given Planet object + if planet is not None: + assert isinstance(planet, Planet) + if hasattr(self, "p"): + warnings.warn( + "I had already read out the Planet object from the .in file, but I " + "will overwrite that with the object you have given." + ) + self.p = planet + + # check if the SED of the Planet object matches the SED of the Cloudy simulation + if hasattr(self, "p") and hasattr(self, "SEDname"): + if self.p.SEDname != self.SEDname: + warnings.warn( + f"I read in the .in file that the SED used is {self.SEDname} which " + f"is different from the one of your Planet object. " + f"I will change the .SEDname attribute of the Planet object to " + f"match the one actually used in the simulation. Are you " + f"sure that also the associated Parker wind profile is correct?" + ) + self.p.set_var(SEDname=self.SEDname) + + # try to set a Parker object if the .in file had the required info for that + if ( + hasattr(self, "p") + and (_parker_T is not None) + and (_parker_mdot is not None) + and (_parker_dir is not None) + ): + self.par = Parker(self.p.name, _parker_T, _parker_mdot, _parker_dir) + + # overwrite/set manually given Parker object + if parker is not None: + assert isinstance(parker, Parker) + if hasattr(self, "par"): + warnings.warn( + "I had already read out the Parker object from the .in file, but I " + "will overwrite that with the object you have given." + ) + self.par = parker + + # overwrite/set manually given altmax + if altmax is not None: + if not isinstance(altmax, (float, int)): + # can it actually be a float? I'm not sure if the code can handle it - + # check and try. + raise TypeError("altmax must be set to a float or int") + if hasattr(self, "altmax"): + if self.altmax != altmax: + warnings.warn( + "I read the altmax from the .in file, but the value you have " + "explicitly passed is different. " + "I will use your value, but please make sure it is correct." + ) + self.altmax = altmax + + # temporary variables for adding the alt-columns to the pandas dataframes + _Rp, _altmax = None, None + if hasattr(self, "p") and hasattr(self, "altmax"): + _Rp = self.p.R + _altmax = self.altmax + + # read in the Cloudy simulation files + self.simfiles = [] + for simfile in glob.glob(simname + ".*", recursive=True): + filetype = simfile.split(".")[-1] + if filetype == "ovr" and ("ovr" in files or "all" in files): + self.ovr = process_overview( + self.simname + ".ovr", + Rp=_Rp, + altmax=_altmax, + abundances=self.abundances, + ) + self.simfiles.append("ovr") + if filetype == "con" and ("con" in files or "all" in files): + self.con = process_continuum(self.simname + ".con") + self.simfiles.append("con") + if filetype == "heat" and ("heat" in files or "all" in files): + self.heat = process_heating( + self.simname + ".heat", + Rp=_Rp, + altmax=_altmax, + cloudy_version=self.cloudy_version, + ) + self.simfiles.append("heat") + if filetype == "cool" and ("cool" in files or "all" in files): + self.cool = process_cooling( + self.simname + ".cool", + Rp=_Rp, + altmax=_altmax, + cloudy_version=self.cloudy_version, + ) + self.simfiles.append("cool") + if filetype == "coolH2" and ("coolH2" in files or "all" in files): + self.coolH2 = process_coolingH2( + self.simname + ".coolH2", Rp=_Rp, altmax=_altmax + ) + self.simfiles.append("coolH2") + if filetype == "den" and ("den" in files or "all" in files): + self.den = process_densities( + self.simname + ".den", Rp=_Rp, altmax=_altmax + ) + self.simfiles.append("den") + if filetype == "en" and ("en" in files or "all" in files): + self.en = process_energies( + self.simname + ".en", cloudy_version=self.cloudy_version + ) + self.simfiles.append("en") + + # set the velocity structure in .ovr if we have an associated Parker profile - + # needed for radiative transfer + if hasattr(self, "par") and hasattr(self, "ovr"): + if hasattr(self.par, "prof") and hasattr(self.ovr, "alt"): + Sim.addv(self, self.par.prof.alt, self.par.prof.v) + + def get_simfile(self, simfile): + """ + Returns the output of the requested simulation output file. + These can also be accessed as an attribute, + for example mysim.ovr or mysim.cool for a Sim object called mysim + """ + + if simfile not in self.simfiles: + raise FileNotFoundError( + "This simulation does not have a", simfile, "output file." + ) + + if simfile == "ovr": + return self.ovr + if simfile == "con": + return self.con + if simfile == "heat": + return self.heat + if simfile == "cool": + return self.cool + if simfile == "coolH2": + return self.coolH2 + if simfile == "den": + return self.den + if simfile == "en": + return self.en + if simfile == "ionFe": + return self.ionFe + if simfile == "ionNa": + return self.ionNa + return None + + def add_parker(self, parker): + """ + Adds a Parker profile object to the Sim, in case it wasn't added upon + initialization. + """ + + assert isinstance(parker, Parker) + self.par = parker + if hasattr(parker, "prof"): + Sim.addv(self, parker.prof.alt, parker.prof.v) + + def addv(self, alt, v, delete_negative=True): + """ + Adds a velocity profile in cm s-1 on the Cloudy grid. Will be added to the .ovr + file, but also available as the .v attribute for backwards compatibility of + sunbather. Called automatically when adding a Parker object to the Sim. + """ + + assert "ovr" in self.simfiles, "Simulation must have a 'save overview .ovr file" + assert "alt" in self.ovr.columns, ( + "The .ovr file must have an altitude column (which in turn requires a " + "known Rp and altmax)" + ) + + if delete_negative: + v[v < 0.0] = 0.0 + + self.ovr["v"] = interp1d(alt, v)(self.ovr.alt) + + vseries = pd.Series(index=self.ovr.alt.index, dtype=float) + vseries[self.ovr.alt.index] = interp1d(alt, v)(self.ovr.alt) + self.v = vseries diff --git a/src/tools.py b/src/tools.py deleted file mode 100644 index 10c6d69..0000000 --- a/src/tools.py +++ /dev/null @@ -1,2177 +0,0 @@ -import numpy as np -import pandas as pd -import matplotlib.pyplot as plt -import os -import glob -import re -from shutil import copyfile -from scipy.interpolate import interp1d -from scipy.signal import savgol_filter -import scipy.stats as sps -from scipy.ndimage import gaussian_filter1d -from fractions import Fraction -import warnings - - -####################################### -########### GLOBAL CONSTANTS ########## -####################################### - -sunbatherpath = os.path.dirname(os.path.abspath(__file__)) #the absolute path where this code lives -try: - cloudypath = os.environ['CLOUDY_PATH'] #the path where the Cloudy installation is -except KeyError: - raise KeyError("The environment variable 'CLOUDY_PATH' is not set. " \ - "Please set this variable in your .bashrc/.zshrc file " \ - "to the path where the Cloudy installation is located. " \ - "Do not point it to the /source/ subfolder, but to the main folder.") - -try: - projectpath = os.environ['SUNBATHER_PROJECT_PATH'] #the path where you save your simulations and do analysis -except KeyError: - raise KeyError("The environment variable 'SUNBATHER_PROJECT_PATH' is not set. " \ - "Please set this variable in your .bashrc/.zshrc file " \ - "to the path where you want the sunbather models to be saved. " \ - "Make sure that the 'planets.txt' file is present in that folder.") - -try: - #read planet parameters globally instead of in the Planets class (so we do it only once) - planets_file = pd.read_csv(projectpath+'/planets.txt', dtype={'name':str, 'full name':str, 'R [RJ]':np.float64, - 'Rstar [Rsun]':np.float64, 'a [AU]':np.float64, 'M [MJ]':np.float64, 'Mstar [Msun]':np.float64, - 'transit impact parameter':np.float64, 'SEDname':str}, comment='#') -except FileNotFoundError: - raise FileNotFoundError("The $SUNBATHER_PROJECT_PATH/planets.txt file cannot be found. " \ - "Please check if your $SUNBATHER_PROJECT_PATH actually exists on your machine. "\ - "Then, copy /sunbather/planets.txt to your project path.") - -#define constants: -c = 2.99792458e10 #cm/s -h = 4.135667696e-15 #eV s, used to plot wavelengths in keV units -mH = 1.674e-24 #g -k = 1.381e-16 #erg/K -AU = 1.49597871e13 #cm -pc = 3.08567758e18 #cm -RJ = 7.1492e9 #cm -RE = 6.371e8 #cm -Rsun = 69634000000 #cm -Msun = 1.9891e33 #g -MJ = 1.898e30 #g -ME = 5.9722e27 #g -G = 6.6743e-8 #cm3/g/s2 -Ldict = {'S':0, 'P':1, 'D':2, 'F':3, 'G':4, 'H':5, 'I':6, 'K':7, 'L':8, - 'M':9, 'N':10, 'O':11, 'Q':12, 'R':13, 'T':14} #atom number of states per L orbital - -element_names = {'H':'hydrogen', 'He':'helium', 'Li':'lithium', 'Be':'beryllium', 'B':'boron', 'C':'carbon', - 'N':'nitrogen', 'O':'oxygen', 'F':'fluorine', 'Ne':'neon', 'Na':'sodium', - 'Mg':'magnesium', 'Al':'aluminium', 'Si':'silicon', 'P':'phosphorus', - 'S':'sulphur', 'Cl':'chlorine', 'Ar':'argon', 'K':'potassium', 'Ca':'calcium', - 'Sc':'scandium', 'Ti':'titanium', 'V':'vanadium', 'Cr':'chromium', 'Mn':'manganese', - 'Fe':'iron', 'Co':'cobalt', 'Ni':'nickel', 'Cu':'copper', 'Zn':'zinc'} -element_symbols = dict((reversed(item) for item in element_names.items())) #reverse dictionary mapping e.g. 'hydrogen'->'H' - -#number of corresponding energy levels between Cloudy and NIST - read txt file header for more info -species_enlim = pd.read_csv(sunbatherpath+"/species_enlim.txt", index_col=0, header=1) - - -####################################### -########### CLOUDY SPECIES ########## -####################################### - -def get_specieslist(max_ion=6, exclude_elements=[]): - """ - Returns a list of atomic and ionic species names. Default returns all species up to 6+ - ionization. Higher than 6+ ionization is rarely attained in an exoplanet atmosphere, - but it can occur in high XUV flux scenarios such as young planetary systems. - The species list only includes species for which the NIST database has any spectral - line coefficients, as there is little use saving other species as well. - - Parameters - ---------- - max_ion : int, optional - Maximum ionization degree of the included species, by default 6 - exclude_elements : str or list, optional - Elements to include (in both atomic and ionic form), by default [] - - Returns - ------- - specieslist : list - List of atomic and ionic species names in the string format expected by Cloudy. - """ - - if max_ion > 12: - warnings.warn("tools.get_specieslist(): You have set max_ion > 12, but " \ - "sunbather is currently only able to process species up to 12+ ionized. " \ - "However, this should typically be enough, even when using a strong XUV flux.") - - if isinstance(exclude_elements, str): #turn into list with one element - exclude_elements = [exclude_elements] - - specieslist = species_enlim.index.tolist() #all species up to 12+ - - for element in exclude_elements: - specieslist = [sp for sp in specieslist if sp.split('+')[0] != element] - - for sp in specieslist[:]: - sp_split = sp.split('+') - - if len(sp_split) == 1: - deg_ion = 0 - elif sp_split[1] == '': - deg_ion = 1 - else: - deg_ion = int(sp_split[1]) - - if deg_ion > max_ion: - specieslist.remove(sp) - - return specieslist - - -def get_mass(species): - """ - Returns the mass of an atomic or positive ion. For ions, - it returns the mass of the atom, since the electron mass is negligible. - - Parameters - ---------- - species : str - Name of the species in the string format expected by Cloudy. - - Returns - ------- - mass : float - Mass of the species in units of g. - """ - - atom = species.split('+')[0] - - mass_dict = {'H':1.6735575e-24, 'He':6.646477e-24, 'Li':1.15e-23, 'Be':1.4965082e-23, - 'B':1.795e-23, 'C':1.9945e-23, 'N':2.3259e-23, 'O':2.6567e-23, - 'F':3.1547e-23, 'Ne':3.35092e-23, 'Na':3.817541e-23, 'Mg':4.0359e-23, - 'Al':4.48038988e-23, 'Si':4.6636e-23, 'P':5.14331418e-23, 'S':5.324e-23, - 'Cl':5.887e-23, 'Ar':6.6335e-23, 'K':6.49243e-23, 'Ca':6.6551e-23, - 'Sc':7.4651042e-23, 'Ti':7.9485e-23, 'V':8.45904e-23, 'Cr':8.63416e-23, - 'Mn':9.1226768e-23, 'Fe':9.2733e-23, 'Co':9.786087e-23, 'Ni':9.74627e-23, - 'Cu':1.0552e-22, 'Zn':1.086e-22} #g - - mass = mass_dict[atom] - - return mass - - -####################################### -########### CLOUDY FILES ########## -####################################### - -def process_continuum(filename, nonzero=False): - """ - Rreads a .con file from the 'save continuum units angstrom' command. - It renames the columns and adds a wavelength column. - The flux units of the continuum are as follows: - Take the SED in spectral flux density, so F(nu) instead of nu*F(nu), and - find the total area by integration. Then multiply with the frequency, - to get nu*F(nu), and normalize that by the total area found, and multiply - with the total luminosity. Those are the units of Cloudy. - - Parameters - ---------- - filename : str - Filename of a 'save continuum' Cloudy output file. - nonzero : bool, optional - Whether to remove rows where the incident spectrum is 0 (i.e., not defined), by default False - - Returns - ------- - con_df : pandas.DataFrame - Parsed output of the 'save continuum' Cloudy command. - """ - - con_df = pd.read_table(filename) - con_df.rename(columns={'#Cont nu':'wav', 'net trans':'nettrans'}, inplace=True) - if nonzero: - con_df = con_df[con_df.incident != 0] - - return con_df - - -def process_heating(filename, Rp=None, altmax=None, cloudy_version="17"): - """ - Reads a .heat file from the 'save heating' command. - If Rp and altmax are given, it adds an altitude/radius scale. - For each unique heating agent, it adds a column with its rate at each radial bin. - - Parameters - ---------- - filename : str - Filename of a 'save heating' Cloudy output file. - Rp : numeric, optional - Planet radius in units of cm, by default None - altmax : numeric, optional - Maximum altitude of the simulation in units of planet radius, by default None - cloudy_version : str, optional - Major Cloudy release version, by default "17" - - Returns - ------- - heat : pandas.DataFrame - Parsed output of the 'save heating' Cloudy command. - - Raises - ------ - TypeError - If a Cloudy version was used that is not supported by sunbather. - """ - - #determine max number of columns (otherwise pd.read_table assumes it is the number of the first row) - max_columns = 0 - with open(filename, 'r') as file: - for line in file: - num_columns = len(line.split('\t')) - max_columns = max(max_columns, num_columns) - #set up the column names - if cloudy_version == "17": - fixed_column_names = ['depth', 'temp', 'htot', 'ctot'] - elif cloudy_version == "23": - fixed_column_names = ['depth', 'temp', 'htot', 'ctot', 'adv'] - else: - raise TypeError("Only C17.02 and C23.01 are currently supported.") - num_additional_columns = (max_columns - 4) // 2 - additional_column_names = [f'htype{i}' for i in range(1, num_additional_columns + 1) for _ in range(2)] - additional_column_names[1::2] = [f'hfrac{i}' for i in range(1, num_additional_columns + 1)] - all_column_names = fixed_column_names + additional_column_names - heat = pd.read_table(filename, delimiter='\t', skiprows=1, header=None, names=all_column_names) - - if heat['depth'].eq("#>>>> Ionization not converged.").any(): - warnings.warn(f"The simulation you are reading in exited OK but does contain ionization convergence failures: {filename[:-5]}") - heat = heat[heat['depth'] != "#>>>> Ionization not converged."] #remove those extra lines from the heat DataFrame - - #remove the "second rows", which sometimes are in the .heat file and do not give the heating at a given depth - if type(heat.depth.iloc[0]) == str: #in some cases there are no second rows - heat = heat[heat.depth.map(len)<12] #delete second rows - - heat.depth = pd.to_numeric(heat.depth) #str to float - heat.reset_index(drop=True, inplace=True) #reindex so that it has same index as e.g. .ovr - - if Rp != None and altmax != None: #add altitude scale - heat['alt'] = altmax * Rp - heat.depth - - agents = [] - for column in heat.columns: - if column.startswith('htype'): - agents.extend(heat[column].unique()) - agents = list(set(agents)) #all unique heating agents that appear somewhere in the .heat file - - for agent in agents: - heat[agent] = np.nan #add 'empty' column for each agent - - #now do a (probably sub-optimal) for-loop over the whole df to put all hfracs in the corresponding column - htypes = [f'htype{i+1}' for i in range(num_additional_columns)] - hfracs = [f'hfrac{i+1}' for i in range(num_additional_columns)] - for htype, hfrac in zip(htypes, hfracs): - for index, agent in heat[htype].items(): - rate = heat.loc[index, hfrac] - heat.loc[index, agent] = rate - - if np.nan in heat.columns: #sometimes columns are partially missing, resulting in columns called nan - heat.drop(columns=[np.nan], inplace=True) - - heat['sumfrac'] = heat.loc[:,[col for col in heat.columns if 'hfrac' in col]].sum(axis=1) - - return heat - - -def process_cooling(filename, Rp=None, altmax=None, cloudy_version="17"): - """ - Reads a .cool file from the 'save cooling' command. - If Rp and altmax are given, it adds an altitude/radius scale. - For each unique cooling agent, it adds a column with its rate at each radial bin. - - Parameters - ---------- - filename : str - Filename of a 'save cooling' Cloudy output file. - Rp : numeric, optional - Planet radius in units of cm, by default None - altmax : numeric, optional - Maximum altitude of the simulation in units of planet radius, by default None - cloudy_version : str, optional - Major Cloudy release version, by default "17" - - Returns - ------- - cool : pandas.DataFrame - Parsed output of the 'save cooling' Cloudy command. - - Raises - ------ - TypeError - If a Cloudy version was used that is not supported by sunbather. - """ - - #determine max number of columns (otherwise pd.read_table assumes it is the number of the first row) - max_columns = 0 - with open(filename, 'r') as file: - for line in file: - num_columns = len(line.split('\t')) - max_columns = max(max_columns, num_columns) - #set up the column names - if cloudy_version == "17": - fixed_column_names = ['depth', 'temp', 'htot', 'ctot'] - elif cloudy_version == "23": - fixed_column_names = ['depth', 'temp', 'htot', 'ctot', 'adv'] - else: - raise Exception("Only C17.02 and C23.01 are currently supported.") - num_additional_columns = (max_columns - 4) // 2 - additional_column_names = [f'ctype{i}' for i in range(1, num_additional_columns + 1) for _ in range(2)] - additional_column_names[1::2] = [f'cfrac{i}' for i in range(1, num_additional_columns + 1)] - all_column_names = fixed_column_names + additional_column_names - cool = pd.read_table(filename, delimiter='\t', skiprows=1, header=None, names=all_column_names) - - if cool['depth'].eq("#>>>> Ionization not converged.").any(): - warnings.warn(f"The simulation you are reading in exited OK but does contain ionization convergence failures: {filename[:-5]}") - #remove those extra lines from the cool DataFrame - cool = cool[cool['depth'] != "#>>>> Ionization not converged."] - cool['depth'] = cool['depth'].astype(float) - cool = cool.reset_index(drop=True) #so it matches other dfs like .ovr - - - if Rp != None and altmax != None: #add altitude scale - cool['alt'] = altmax * Rp - cool.depth - - agents = [] - for column in cool.columns: - if column.startswith('ctype'): - agents.extend(cool[column].unique()) - agents = list(set(agents)) #all unique cooling agents that appear somewhere in the .cool file - - for agent in agents: - cool[agent] = np.nan #add 'empty' column for each agent - - #now do a (probably sub-optimal) for-loop over the whole df to put all cfracs in the corresponding column - ctypes = [f'ctype{i+1}' for i in range(num_additional_columns)] - cfracs = [f'cfrac{i+1}' for i in range(num_additional_columns)] - for ctype, cfrac in zip(ctypes, cfracs): - for index, agent in cool[ctype].items(): - rate = cool.loc[index, cfrac] - cool.loc[index, agent] = rate - - if np.nan in cool.columns: #sometimes columns are partially missing, resulting in columns called nan - cool.drop(columns=[np.nan], inplace=True) - - cool['sumfrac'] = cool.loc[:,[col for col in cool.columns if 'cfrac' in col]].sum(axis=1) - - return cool - - -def process_coolingH2(filename, Rp=None, altmax=None): - """ - Reads a .coolH2 file from the 'save H2 cooling' command, - which keeps track of cooling and heating processes unique to the - H2 molecule, when using the 'database H2' command. - From the Cloudy source code "mole_h2_io.cpp" the columns are: - depth, Temp, ctot/htot, H2 destruction rate Solomon TH85, - H2 destruction rate Solomon big H2, photodis heating, - heating dissoc. electronic exited states, - cooling collisions in X (neg = heating), - "HeatDexc"=net heat, "-HeatDexc/abundance"=net cool per particle - - If Rp and altmax are given, it adds an altitude/radius scale. - - Parameters - ---------- - filename : str - Filename of a 'save H2 cooling' Cloudy output file. - Rp : numeric, optional - Planet radius in units of cm, by default None - altmax : numeric, optional - Maximum altitude of the simulation in units of planet radius, by default None - - Returns - ------- - coolH2 : pandas.DataFrame - Parsed output of the 'save H2 cooling' Cloudy command. - """ - - coolH2 = pd.read_table(filename, names=['depth', 'Te', 'ctot', 'desTH85', - 'desbigH2', 'phdisheat', 'eedisheat', 'collcool', - 'netheat', 'netcoolpp'], header=1) - if Rp != None and altmax != None: - coolH2['alt'] = altmax*Rp - coolH2['depth'] - - return coolH2 - - -def process_overview(filename, Rp=None, altmax=None, abundances=None): - """ - Reads in a '.ovr' file from the 'save overview' command. - If Rp and altmax are given, it adds an altitude/radius scale. - It also adds the mass density, the values of which are only correct if - the correct abundances are passed. - - Parameters - ---------- - filename : str - Filename of a 'save overview' Cloudy output file. - Rp : numeric, optional - Planet radius in units of cm, by default None - altmax : numeric, optional - Maximum altitude of the simulation in units of planet radius, by default None - abundances : dict, optional - Dictionary with the abudance of each element, expressed as a fraction of the total. - Can be easily created with get_abundances(). By default None, which - results in solar composition. - - Returns - ------- - ovr : pandas.DataFrame - Parsed output of the 'save overview' Cloudy command. - """ - - ovr = pd.read_table(filename) - ovr.rename(columns={'#depth':'depth'}, inplace=True) - ovr['rho'] = hden_to_rho(ovr.hden, abundances=abundances) #Hdens to total dens - if Rp != None and altmax != None: - ovr['alt'] = altmax * Rp - ovr['depth'] - ovr['mu'] = calc_mu(ovr.rho, ovr.eden, abundances=abundances) - - if (ovr['2H_2/H'].max() > 0.1) or (ovr['CO/C'].max() > 0.1) or (ovr['H2O/O'].max() > 0.1): - warnings.warn(f"Molecules are significant, the calculated mean particle mass could be inaccurate: {filename}") - - return ovr - - -def process_densities(filename, Rp=None, altmax=None): - """ - Reads a .den file from the 'save species densities' command. - If Rp and altmax are given, it adds an altitude/radius scale. - - Parameters - ---------- - filename : str - Filename of a 'save species densities' Cloudy output file. - Rp : numeric, optional - Planet radius in units of cm, by default None - altmax : numeric, optional - Maximum altitude of the simulation in units of planet radius, by default None - - Returns - ------- - den : pandas.DataFrame - Parsed output of the 'save species densities' Cloudy command. - """ - - den = pd.read_table(filename) - den.rename(columns={'#depth densities':'depth'}, inplace=True) - - if Rp != None and altmax != None: - den['alt'] = altmax*Rp - den['depth'] - - return den - - -def process_energies(filename, rewrite=True, cloudy_version="17"): - """ - Reads a '.en' file from the 'save species energies' command. - This command must always be used alongside the 'save species densities' command, - since they give the associated energy of each level printed in the - densities file. Without saving the energies, it is for example not clear - which atomic configuration / energy level 'He[52]' corresponds to. - This function returns a dictionary mapping the column names of - the .den file to their corresponding atomic configurations. - The atomic configuration is needed to identify the spectral lines originating - from this level during radiative transfer. - - Parameters - ---------- - filename : str - Filename of a 'save species energies' Cloudy output file. - rewrite : bool, optional - Whether to rewrite the file to only keeping only the first row. Normally, - the energies of each energy level are stored per depth cell of the simulation, - but they should be the same at each depth. Retaining only the values of the - first row in this way helps to compress file size. By default True. - cloudy_version : str, optional - Major Cloudy release version, by default "17" - - Returns - ------- - en_df : dict - Dictionary mapping the column names of the .den file to their atomic configurations. - - Raises - ------ - ValueError - If the energy values are not the same at each depth. - """ - - en = pd.read_table(filename, float_precision='round_trip') #use round_trip to prevent exp numerical errors - - if en.columns.values[0][0] == '#': #condition checks whether it has already been rewritten, if not, we do all following stuff: - - for col in range(len(en.columns)): #check if all rows are the same - if len(en.iloc[:,col].unique()) != 1: - raise ValueError("In reading .en file, found a column with not identical values!" - +" filename:", filename, "col:", col, "colname:", en.columns[col], "unique values:", - en.iloc[:,col].unique()) - - en.rename(columns={en.columns.values[0] : en.columns.values[0][10:]}, inplace=True) #rename the column - - if rewrite: #save with only first row to save file size - en.iloc[[0],:].to_csv(filename, sep='\t', index=False, float_format='%.5e') - - en_df = pd.DataFrame(index = en.columns.values) - en_df['species'] = [k.split('[')[0] for k in en_df.index.values] #we want to match 'He12' to species='He', for example - en_df['energy'] = en.iloc[0,:].values - en_df['configuration'] = "" - en_df['term'] = "" - en_df['J'] = "" - - - #the & set action takes the intersection of all unique species of the .en file, and those known with NIST levels - unique_species = list(set(en_df.species.values) & set(species_enlim.index.tolist())) - - for species in unique_species: - species_levels = pd.read_table(sunbatherpath+'/RT_tables/'+species+'_levels_processed.txt') #get the NIST levels - species_energies = en_df[en_df.species == species].energy #get Cloudy's energies - - #tolerance of difference between Cloudy's and NISTs energy levels. They usually differ at the decimal level so we need some tolerance. - atol = species_enlim.loc[species, f"atol_C{cloudy_version}"] - #start by assuming we can match this many energy levels - n_matching = species_enlim.loc[species, f"idx_C{cloudy_version}"] - - for n in range(n_matching): - if not np.abs(species_energies.iloc[n] - species_levels.energy.iloc[n]) < atol: - warnings.warn(f"In {filename} while getting atomic states for species {species}, I expected to be able to match the first {n_matching} " + \ - f"energy levels between Cloudy and NIST to a precision of {atol} but I have an energy mismatch at energy level {n+1}. " + \ - f"This should not introduce bugs, as I will now only parse the first {n} levels.") - - #for debugging, you can print the energy levels of Cloudy and NIST: - #print("\nCloudy, NIST, Match?") - #for i in range(n_matching): - # print(species_energies.iloc[i], species_levels.energy.iloc[i], np.isclose(species_energies.iloc[:n_matching], species_levels.energy.iloc[:n_matching], rtol=0.0, atol=atol)[i]) - - n_matching = n #reset n_matching to how many actually match - - break - - #Now assign the first n_matching columns to their expected values as given by the NIST species_levels DataFrame - first_iloc = np.where(en_df.species == species)[0][0] #iloc at which the species (e.g. He or Ca+3) starts. - en_df.iloc[first_iloc:first_iloc+n_matching, en_df.columns.get_loc('configuration')] = species_levels.configuration.iloc[:n_matching].values - en_df.iloc[first_iloc:first_iloc+n_matching, en_df.columns.get_loc('term')] = species_levels.term.iloc[:n_matching].values - en_df.iloc[first_iloc:first_iloc+n_matching, en_df.columns.get_loc('J')] = species_levels.J.iloc[:n_matching].values - - return en_df - - -def find_line_lowerstate_in_en_df(species, lineinfo, en_df, verbose=False): - """ - Finds the column name of the .den file that corresponds to - the ground state of the given line. So for example if species='He', - and we are looking for the metastable helium line, - it will return 'He[2]', meaning the 'He[2]' column of the .den file contains - the number densities of the metastable helium atom. - - Additionally, it calculates a multiplication factor <1 for the number - density of this energy level. This is for spectral lines that originate from a - specific J (total angular momentum quantum number) configuration, but Cloudy - does not save the densities of this specific J-value, only of the parent LS state. - In this case, we use a statistical argument to guess how many of the particles - are in each J-state. For this, we use that each J-state has 2*J+1 substates, - and then assuming all substates are equally populated, we can calculate the - population of each J-level. The assumption of equal population may not always be strictly - valid. In LTE, the population should in principle be calculated form the Boltzmann - distribution, but equal populations will be a good approximation at high temperature - or when the energy levels of the J-substates are close together. In NLTE, the - assumption is less valid due to departure from the Boltzmann equation. - - Parameters - ---------- - species : str - Name of the atomic or ionic species in the string format expected by Cloudy. - lineinfo : pandas.DataFrame - One row containing the spectral line coefficients from NIST, from the - RT.read_NIST_lines() function. - en_df : dict - Dictionary mapping the column names of the .den file to their atomic configurations, - from the process_energies() function. - verbose : bool, optional - Whether to print out , by default False - - Returns - ------- - match : str - Column name of the .den file that contains the number densities of the energy - level that this spectral line originates from. - lineweight : float - Multiplication factor <1 for the number density of this energy level, to get - the number density of the specific J-state that the spectral line originates from. - """ - - en_df = en_df[en_df.species == species] #keep only the part for this species to not mix up the energy levels of different ones - match, lineweight = None, None #start with the assumption that we cannot match it - - #check if the line originates from a J sublevel, a term, or only principal quantum number - if str(lineinfo['term_i']) != 'nan' and str(lineinfo['J_i']) != 'nan': - linetype = 'J' #then now match with configuration and term: - matchedrow = en_df[(en_df.configuration == lineinfo.conf_i) & (en_df.term == lineinfo.term_i) & (en_df.J == lineinfo.J_i)] - assert len(matchedrow) <= 1 - - if len(matchedrow) == 1: - match = matchedrow.index.item() - lineweight = 1. #since the Cloudy column is for this J specifically, we don't need to downweigh the density - - elif len(matchedrow) == 0: - #the exact J was not found in Cloudy's levels, but maybe the term is there in Cloudy, just not resolved. - matchedtermrow = en_df[(en_df.configuration == lineinfo.conf_i) & (en_df.term == lineinfo.term_i)] - - if len(matchedtermrow) == 1: - if str(matchedtermrow.J.values[0]) == 'nan': #this can only happen if the Cloudy level is a term with no J resolved. - #then we use statistical weights to guess how many of the atoms in this term state would be in the J state of the level and use this as lineweight - L = Ldict[''.join(x for x in matchedtermrow.loc[:,'term'].item() if x.isalpha())[-1]] #last letter in term string - S = (float(re.search(r'\d+', matchedtermrow.loc[:,'term'].item()).group())-1.)/2. #first number in term string - J_states = np.arange(np.abs(L-S), np.abs(L+S)+1, 1.0) - J_statweights = 2*J_states + 1 - J_probweights = J_statweights / np.sum(J_statweights) - - lineweight = J_probweights[J_states == Fraction(lineinfo.loc['J_i'])][0] - - match = matchedtermrow.index.item() - else: - verbose_print(f"One J level of the term is resolved, but not the one of this line: {species} "+ lineinfo.conf_i, verbose=verbose) - - else: - verbose_print(f"Multiple J levels of the term are resolved, but not the one of this line: {species} "+ lineinfo.conf_i, verbose=verbose) - - elif str(lineinfo['term_i']) != 'nan': - linetype = "LS" - - verbose_print("Currently not able to do lines originating from LS state without J number.", verbose=verbose) - verbose_print(f"Lower state configuration: {species} "+ lineinfo.conf_i, verbose=verbose) - else: - linetype = "n" - - verbose_print("Currently not able to do lines originating from n state without term. This is not a problem "+ - 'if this line is also in the NIST database with its different term components, such as for e.g. '+ - "H n=2, but only if they aren't such as for H n>6, or if they go to an upper level n>6 from any given level.", verbose=verbose) - verbose_print(f"Lower state configuration: {species} "+ lineinfo.conf_i, verbose=verbose) - - ''' - DEVELOPERS NOTE: - If we do decide to make this functionality, for example by summing the densities of all sublevels of a - particular n, we also need to tweak the cleaning of hydrogen lines algorithm. Right now, we remove - double lines only for the upper state, so e.g. for Ly alpha, we remove the separate 2p 3/2 and 2p 1/2 etc. component - and leave only the one line with upper state n=2. - However, we don't do this for lower states, which is not a problem yet because the lower n state lines are ignored as - stated above. However if we make the functionality, we should also remove double lines in the lower level. - ''' - - return match, lineweight - - -####################################### -########### MISCELLANEOUS ########### -####################################### - -def verbose_print(message, verbose=False): - """ - Prints the provided string only if verbose is True. - - Parameters - ---------- - message : str - String to optionally print. - verbose : bool, optional - Whether to print the provided message, by default False - """ - - if verbose: - print(message) - - -def get_SED_norm_1AU(SEDname): - """ - Reads in an SED file and returns the normalization in monochromatic flux - (i.e., nu*F_nu or lambda*F_lambda) and Ryd units. - These are needed because Cloudy does not preserve the normalization of - user-specified SEDs. To do a simulation of an atmosphere, the normalization - of the SED must afterwards still be scaled to the planet distance. - Then, the log10 of nuFnu can be passed to Cloudy using the - "nuFnu(nu) = ... at ... Ryd" command. - This function requires that the units of the SED are Å and - monochromatic flux (i.e., nu*F_nu or lambda*F_lambda). - - Parameters - ---------- - SEDname : str - Name of a SED file located in $CLOUDY_PATH/data/SED/. - - Returns - ------- - nuFnu : float - Monochromatic flux specified at the energy of the Ryd output variable. - Ryd : float - Energy where the monochromatic flux of the nuFnu output variable is specified. - """ - - with open(cloudypath+'/data/SED/'+SEDname, 'r') as f: - for line in f: - if not line.startswith('#'): #skip through the comments at the top - assert ('angstrom' in line) or ('Angstrom' in line) #verify the units - assert 'nuFnu' in line #verify the units - break - data = np.genfromtxt(f, skip_header=1) #skip first line, which has extra words specifying the units - - ang, nuFnu = data[-2,0], data[-2,1] #read out intensity somewhere - Ryd = 911.560270107676 / ang #convert wavelength in Å to energy in Ryd - - return nuFnu, Ryd - - -def speciesstring(specieslist, selected_levels=False, cloudy_version="17"): - """ - Takes a list of species names and returns a long string with those species - between quotes and [:] added (or [:maxlevel] if selected_levels=True), - and \n between them. This string can then be used in a Cloudy input - script for .den and .en files. The maxlevel is the number of energy levels - that can be matched between Cloudy and NIST. Saving higher levels than that is not - really useful since they cannot be postprocessed by the radiative transfer module. - - Parameters - ---------- - specieslist : list - Species to include. - selected_levels : bool, optional - If True, only energy levels up to the number that can be matched to NIST - will be included. If False, all energy levels of each species will be - included, regardless of whether we can match them to NIST. By default False. - cloudy_version : str, optional - Major Cloudy release version, by default "17" - - Returns - ------- - speciesstr : str - One long string containing the species and the energy level numbers. - """ - - if not selected_levels: #so just all levels available in cloudy - speciesstr = '"'+specieslist[0]+'[:]"' - if len(specieslist) > 1: - for species in specieslist[1:]: - speciesstr += '\n"'+species+'[:]"' - - elif selected_levels: #then we read out the max level that we expect to match the energy of - speciesstr = '"'+specieslist[0]+'[:'+str(species_enlim.loc[specieslist[0], f"idx_C{cloudy_version}"])+']"' - if len(specieslist) > 1: - for species in specieslist[1:]: - speciesstr += '\n"'+species+'[:'+str(species_enlim.loc[species, f"idx_C{cloudy_version}"])+']"' - - return speciesstr - - -def read_parker(plname, T, Mdot, pdir, filename=None): - """ - Reads an isothermal Parker wind profile as generated by the construct_parker.py module. - - Parameters - ---------- - plname : str - Planet name (must have parameters stored in $SUNBATHER_PROJECT_PATH/planets.txt). - T : str or numeric - Temperature in units of K. - Mdot : str or numeric - log of the mass-loss rate in units of g s-1. - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - filename : str, optional - If None, the profile as specified by plname, T, Mdot, pdir is read. If not None, - filename must specfy the full path + filename of the isothermal Parker wind profile - to read in. By default None. - - Returns - ------- - pprof : pandas.DataFrame - Radial density, velocity and mean particle mass profiles of the isothermal Parker wind profile. - """ - - if filename == None: - Mdot = "%.3f" % float(Mdot) - T = str(int(T)) - filename = projectpath+'/parker_profiles/'+plname+'/'+pdir+'/pprof_'+plname+'_T='+T+'_M='+Mdot+'.txt' - - pprof = pd.read_table(filename, names=['alt', 'rho', 'v', 'mu'], dtype=np.float64, comment='#') - pprof['drhodr'] = np.gradient(pprof['rho'], pprof['alt']) - - return pprof - - -def calc_mu(rho, ne, abundances=None, mass=False): - """ - Calculates the mean particle mass of an atomic/ionic gas mixture, - but neglecting molecules (and the negligible mass contributed by - electrons). Based on formula: - mu = sum(ni*mi) / (sum(ni) + ne) - where ni and mi are the number density and mass of element i, and - ne is the electron number density. - Use ni = ntot * fi and ntot = rho / sum(fi*mi) - where ntot is the total number density, fi the abundance of element i - expressed as a 0 alt[0] #should be in ascending alt order - assert alt[-1] - altmax*Rp > -1. #For extrapolation: the alt scale should extend at least to within 1 cm of altmax*Rp - - if not np.isclose(alt[0], Rp, rtol=1e-2, atol=0.0): - warnings.warn(f"Are you sure the altitude array starts at Rp? alt[0]/Rp = {alt[0]/Rp}") - - depth = altmax*Rp - alt - ifunc = interp1d(depth, quantity, fill_value='extrapolate') - - - Clgridr1 = np.logspace(np.log10(alt[0]), np.log10(altmax*Rp), num=int(0.8*nmax)) - Clgridr1[0], Clgridr1[-1] = alt[0], altmax*Rp #reset these for potential log-numerical errors - Clgridr1 = (Clgridr1[-1] - Clgridr1)[::-1] - #sample the first 10 points better since Cloudy messes up with log-space interpolation there - Clgridr2 = np.logspace(-2, np.log10(Clgridr1[9]), num=(nmax-len(Clgridr1))) - Clgridr = np.concatenate((Clgridr2, Clgridr1[10:])) - Clgridr[0] = 1e-35 - - Clgridq = ifunc(Clgridr) - law = np.column_stack((Clgridr, Clgridq)) - if log: - law[law[:,1]==0., 1] = 1e-100 - law = np.log10(law) - - return law - - -def smooth_gaus_savgol(y, size=None, fraction=None): - """ - Smooth an array using a Gaussian filter, but smooth the start and - end of the array with a Savitzky-Golay filter. - - Parameters - ---------- - y : array-like - Array to smooth. - size : int, optional - Smoothing size expressed as a number of points that will serve as the Gaussian - standard deviation. If None, instead a fraction must be provided, by default None - fraction : float, optional - Smoothing size expressed as a fraction of the total array length - that will serve as the Gaussian standard deviation. If None, instead - a size must be provided, by default None - - Returns - ------- - ysmooth : numpy.ndarray - Smoothed array. - - Raises - ------ - ValueError - If neither or both size and fraction were provided. - """ - - if size != None and fraction == None: - size = max(3, size) - elif fraction != None and size == None: - assert 0. < fraction < 1., "fraction must be greater than 0 and smaller than 1" - size = int(np.ceil(len(y)*fraction) // 2 * 2 + 1) #make it odd - size = max(3, size) - else: - raise ValueError("Please provide either 'size' or 'fraction'.") - - ygaus = gaussian_filter1d(y, size) - ysavgol = savgol_filter(y, 2*int(size/2)+1, polyorder=2) - - savgolweight = np.zeros(len(y)) - savgolweight += sps.norm.pdf(range(len(y)), 0, size) - savgolweight += sps.norm.pdf(range(len(y)), len(y), size) - savgolweight /= np.max(savgolweight) #normalize - gausweight = 1 - savgolweight - - ysmooth = ygaus * gausweight + ysavgol * savgolweight - - return ysmooth - - -####################################### -########### CLOUDY I/O ########## -####################################### - -def run_Cloudy(filename, folder=None): - """ - Run a Cloudy simulation from within Python. - - Parameters - ---------- - filename : str - Name of the simulation input file. If the folder argument is not - specfied, filename must include the full path to the simulation. - If the folder argument is specified, the filename should only - specify the filename. - folder : str, optional - Full path to the directory where the file is located, excluding - the filename itself, which must be specified with the filename - argument. If folder is None, filename must also include the - full path. By default None. - """ - - if folder is None: #then the folder should be in the simname - folder, filename = os.path.split(filename) - - if filename.endswith(".in"): - filename = filename[:-3] #filename should not contain the extension - - os.system('cd '+folder+' && '+cloudypath+'/source/cloudy.exe -p '+filename) - - -def remove_duplicates(law, fmt): - """ - Takes a Cloudy law (e.g., dlaw or tlaw) and a formatter, and removes - duplicate rows from the law. This is mainly for the illuminated side of the - simulation, where we have a very finely sampled grid which can result in - duplicate values after applying the string formatter. This function thus - does not alter the law in any way, but merely improves readability of the - Cloudy .in file laws as the many (obsolete) duplicate rows are removed. - - Parameters - ---------- - law : numpy.ndarray - Quantity on a 'depth'-grid as a 2D array, in the format that Cloudy expects it. - fmt : str - String formatter specifying a float precision. This function will remove - floats that are duplicate up to the precision implied by this fmt formatter. - - Returns - ------- - new_law : numpy.ndarray - Same quantity but with rows removed that have the same float precision - under the provided fmt formatter. - """ - - nonduplicates = [0] - for i in range(1, len(law)-1): - if format(law[i,1], fmt) != format(law[i-1,1], fmt) or format(law[i,1], fmt) != format(law[i+1,1], fmt): - nonduplicates.append(i) - nonduplicates.append(-1) - - new_law = law[nonduplicates] - - return new_law - - -def copyadd_Cloudy_in(oldsimname, newsimname, set_thickness=False, - dlaw=None, tlaw=None, cextra=None, hextra=None, - othercommands=None, outfiles=[], denspecies=[], selected_den_levels=False, - constantT=None, double_tau=False, hcfrac=None, cloudy_version="17"): - """ - Makes a copy of a Cloudy input file and appends commands. - - Parameters - ---------- - oldsimname : str - Full path + name of the Cloudy input file to copy, without the file extension. - newsimname : str - Full path + name of the target Cloudy input file, without the file extension. - set_thickness : bool, optional - Whether to include a command that ends the simulation at a depth equal - to the length of the dlaw, by default True - dlaw : numpy.ndarray, optional - Hydrogen number density in units of cm-3, as a 2D array where dlaw[:,0] - specifies the log10 of the depth into the cloud in cm, and dlaw[:,1] - specifies the log10 of the hydrogen number density in units of cm-3, by default None - tlaw : numpy.ndarray, optional - Temperature in units of K as a 2D array where tlaw[:,0] - specifies the log10 of the depth into the cloud in cm, and tlaw[:,1] - specifies the log10 of the temperature in units of K, by default None - cextra : numpy.ndarray, optional - Extra unspecified cooling in units of erg s-1 cm-3, as a 2D array where - cextra[:,0] specifies the log10 of the depth into the cloud in cm, - and cextra[:,1] specifies the log10 of the cooling rate in units of - erg s-1 cm-3, by default None - hextra : numpy.ndarray, optional - Extra unspecified heating in units of erg s-1 cm-3, as a 2D array where - hextra[:,0] specifies the log10 of the depth into the cloud in cm, - and hextra[:,1] specifies the log10 of the heating rate in units of - erg s-1 cm-3, by default None - othercommands : str, optional - String to include in the input file. Any command not otherwise supported - by this function can be included here, by default None - outfiles : list, optional - List of file extensions indicating which Cloudy output to save. For example, - include '.heat' to include the 'save heating' command, by default ['.ovr', '.cool'] - denspecies : list, optional - List of atomic/ionic species for which to save densities and energies, which - are needed to do radiative transfer. The list can easily be created by the - get_specieslist() function. By default []. - selected_den_levels : bool, optional - If True, only energy levels up to the number that can be matched to NIST - will be included in the 'save densities' command. If False, all energy levels - of each species will be included, regardless of whether we can match them - to NIST. By default False. - constantT : str or numeric, optional - Constant temperature in units of K, by default None - double_tau : bool, optional - Whether to use the 'double optical depths' command. This command is useful - for 1D simulations, ensuring that radiation does not escape the atmosphere - at the back-side into the planet core. By default False - hcfrac : str or numeric, optional - Threshold fraction of the total heating/cooling rate for which the .heat and - .cool files should save agents. Cloudy's default is 0.05, so that individual - heating and cooling processes contributing <0.05 of the total are not saved. - By default None, so that Cloudy's default of 0.05 is used. - cloudy_version : str, optional - Major Cloudy release version, used only in combination with the denspecies - argument, by default "17". - """ - - if denspecies != []: - assert ".den" in outfiles and ".en" in outfiles - if ".den" in outfiles or ".en" in outfiles: - assert ".den" in outfiles and ".en" in outfiles - if constantT != None: - assert not np.any(tlaw != None) - - copyfile(oldsimname+".in", newsimname+".in") - - with open(newsimname+".in", "a") as f: - if set_thickness: - f.write('\nstop thickness '+'{:.7f}'.format(dlaw[-1,0])+'\t#last dlaw point') - if ".ovr" in outfiles: - f.write('\nsave overview ".ovr" last') - if ".cool" in outfiles: - f.write('\nsave cooling ".cool" last') - if ".coolH2" in outfiles: - f.write('\nsave H2 cooling ".coolH2" last') - if ".heat" in outfiles: - f.write('\nsave heating ".heat" last') - if ".con" in outfiles: - f.write('\nsave continuum ".con" last units angstrom') - if ".den" in outfiles: #then ".en" is always there as well due to the assertion above - if denspecies != []: - f.write('\nsave species densities last ".den"\n'+speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\nend") - f.write('\nsave species energies last ".en"\n'+speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\nend") - if constantT != None: - f.write('\nconstant temperature t= '+str(constantT)+' linear') - if double_tau: - f.write('\ndouble optical depths #so radiation does not escape into planet core freely') - if hcfrac: - f.write('\nset WeakHeatCool '+str(hcfrac)+' #for .heat and .cool output files') - if othercommands != None: - f.write("\n"+othercommands) - if np.any(dlaw != None): - dlaw = remove_duplicates(dlaw, "1.7f") - f.write("\n# ========= density law ================") - f.write("\n#depth sets distances from edge of cloud") - f.write("\ndlaw table depth\n") - np.savetxt(f, dlaw, fmt='%1.7f') - f.write('{:.7f}'.format(dlaw[-1,0]+0.1)+ - ' '+'{:.7f}'.format(dlaw[-1,1])) - f.write("\nend of dlaw #last point added to prevent roundoff") - if np.any(tlaw != None): - tlaw = remove_duplicates(tlaw, "1.7f") - f.write("\n# ========= temperature law ============") - f.write("\n#depth sets distances from edge of cloud") - f.write("\ntlaw table depth\n") - np.savetxt(f, tlaw, fmt='%1.7f') - f.write('{:.7f}'.format(tlaw[-1,0]+0.1)+ - ' '+'{:.7f}'.format(tlaw[-1,1])) - f.write("\nend of tlaw #last point added to prevent roundoff") - if np.any(cextra != None): - cextra = remove_duplicates(cextra, "1.7f") - f.write("\n# ========= cextra law ================") - f.write("\n#depth sets distances from edge of cloud") - f.write("\ncextra table depth\n") - np.savetxt(f, cextra, fmt='%1.7f') - f.write('{:.7f}'.format(cextra[-1,0]+0.1)+ - ' '+'{:.7f}'.format(cextra[-1,1])) - f.write("\nend of cextra #last point added to prevent roundoff") - if np.any(hextra != None): - hextra = remove_duplicates(hextra, "1.7f") - f.write("\n# ========= hextra law ================") - f.write("\n#depth sets distances from edge of cloud") - f.write("\nhextra table depth\n") - np.savetxt(f, hextra, fmt='%1.7f') - f.write('{:.7f}'.format(hextra[-1,0]+0.1)+ - ' '+'{:.7f}'.format(hextra[-1,1])) - f.write("\nend of hextra #last point added to prevent roundoff") - - -def write_Cloudy_in(simname, title=None, flux_scaling=None, - SED=None, set_thickness=True, - dlaw=None, tlaw=None, cextra=None, hextra=None, - othercommands=None, overwrite=False, iterate='convergence', - nend=3000, outfiles=['.ovr', '.cool'], denspecies=[], selected_den_levels=False, - constantT=None, double_tau=False, cosmic_rays=False, zdict=None, hcfrac=None, - comments=None, cloudy_version="17"): - """ - Writes a Cloudy input file for simulating an exoplanet atmosphere. - - Parameters - ---------- - simname : str - Full path + name of the Cloudy simulation, without the file extension. - title : str, optional - Title of simulation, by default None - flux_scaling : tuple, optional - Normalization of the SED, as a tuple with the monochromatic flux - and energy in Ryd where it is specified, by default None - SED : str, optional - Name of a SED file located in $CLOUDY_PATH/data/SED/, by default None - set_thickness : bool, optional - Whether to include a command that ends the simulation at a depth equal - to the length of the dlaw, by default True - dlaw : numpy.ndarray, optional - Hydrogen number density in units of cm-3, as a 2D array where dlaw[:,0] - specifies the log10 of the depth into the cloud in cm, and dlaw[:,1] - specifies the log10 of the hydrogen number density in units of cm-3, by default None - tlaw : numpy.ndarray, optional - Temperature in units of K as a 2D array where tlaw[:,0] - specifies the log10 of the depth into the cloud in cm, and tlaw[:,1] - specifies the log10 of the temperature in units of K, by default None - cextra : numpy.ndarray, optional - Extra unspecified cooling in units of erg s-1 cm-3, as a 2D array where - cextra[:,0] specifies the log10 of the depth into the cloud in cm, - and cextra[:,1] specifies the log10 of the cooling rate in units of - erg s-1 cm-3, by default None - hextra : numpy.ndarray, optional - Extra unspecified heating in units of erg s-1 cm-3, as a 2D array where - hextra[:,0] specifies the log10 of the depth into the cloud in cm, - and hextra[:,1] specifies the log10 of the heating rate in units of - erg s-1 cm-3, by default None - othercommands : str, optional - String to include in the input file. Any command not otherwise supported - by this function can be included here, by default None - overwrite : bool, optional - Whether to overwrite the simname if it already exists, by default False - iterate : str or int, optional - Argument to Cloudy's 'iterate' command, either a number or 'convergence', - by default 'convergence' - nend : int, optional - Argument to Cloudy's 'set nend' command, which sets the maximum number of Cloudy - cells. Cloudy's default is 1400 which can often be too few. For this function, - by default 3000. - outfiles : list, optional - List of file extensions indicating which Cloudy output to save. For example, - include '.heat' to include the 'save heating' command, by default ['.ovr', '.cool'] - denspecies : list, optional - List of atomic/ionic species for which to save densities and energies, which - are needed to do radiative transfer. The list can easily be created by the - get_specieslist() function. By default []. - selected_den_levels : bool, optional - If True, only energy levels up to the number that can be matched to NIST - will be included in the 'save densities' command. If False, all energy levels - of each species will be included, regardless of whether we can match them - to NIST. By default False. - constantT : str or numeric, optional - Constant temperature in units of K, by default None - double_tau : bool, optional - Whether to use the 'double optical depths' command. This command is useful - for 1D simulations, ensuring that radiation does not escape the atmosphere - at the back-side into the planet core. By default False - cosmic_rays : bool, optional - Whether to include cosmic rays, by default False - zdict : dict, optional - Dictionary with the scale factors of all elements relative - to a solar composition. Can be easily created with get_zdict(). - Default is None, which results in a solar composition. - hcfrac : str or numeric, optional - Threshold fraction of the total heating/cooling rate for which the .heat and - .cool files should save agents. Cloudy's default is 0.05, so that individual - heating and cooling processes contributing <0.05 of the total are not saved. - By default None, so that Cloudy's default of 0.05 is used. - comments : str, optional - Comments to write at the top of the input file. Make sure to include hashtags - in the string, by default None - cloudy_version : str, optional - Major Cloudy release version, used only in combination with the denspecies - argument, by default "17". - """ - - assert flux_scaling is not None #we need this to proceed. Give in format [F,E] like nuF(nu) = F at E Ryd - assert SED != None - if denspecies != []: - assert ".den" in outfiles and ".en" in outfiles - if ".den" in outfiles or ".en" in outfiles: - assert ".den" in outfiles and ".en" in outfiles and denspecies != [] - if not overwrite: - assert not os.path.isfile(simname+".in") - if constantT != None: - assert not np.any(tlaw != None) - - with open(simname+".in", "w") as f: - if comments != None: - f.write(comments+'\n') - if title != None: - f.write('title '+title) - f.write("\n# ========= input spectrum ================") - f.write("\nnuF(nu) = "+str(flux_scaling[0])+" at "+str(flux_scaling[1])+" Ryd") - f.write('\ntable SED "'+SED+'"') - if cosmic_rays: - f.write('\ncosmic rays background') - f.write("\n# ========= chemistry ================") - f.write("\n# solar abundances and metallicity is standard") - if zdict != None: - for element in zdict.keys(): - if zdict[element] == 0.: - f.write("\nelement "+element_names[element]+" off") - elif zdict[element] != 1.: #only write it to Cloudy if the scale factor is not 1 - f.write("\nelement scale factor "+element_names[element]+" "+str(zdict[element])) - f.write("\n# ========= other ================") - if nend != None: - f.write("\nset nend "+str(nend)+" #models at high density need >1400 zones") - f.write("\nset temperature floor 5 linear") - f.write("\nstop temperature off #otherwise it stops at 1e4 K") - if iterate == 'convergence': - f.write("\niterate to convergence") - else: - f.write("niterate "+str(iterate)) - f.write("\nprint last iteration") - if set_thickness: - f.write('\nstop thickness '+'{:.7f}'.format(dlaw[-1,0])+'\t#last dlaw point') - if constantT != None: - f.write('\nconstant temperature t= '+str(constantT)+' linear') - if double_tau: - f.write('\ndouble optical depths #so radiation does not escape into planet core freely') - if hcfrac: - f.write('\nset WeakHeatCool '+str(hcfrac)+' #for .heat and .cool output files') - if othercommands != None: - f.write("\n"+othercommands) - f.write("\n# ========= output ================") - if ".ovr" in outfiles: - f.write('\nsave overview ".ovr" last') - if ".cool" in outfiles: - f.write('\nsave cooling ".cool" last') - if ".coolH2" in outfiles: - f.write('\nsave H2 cooling ".coolH2" last') - if ".heat" in outfiles: - f.write('\nsave heating ".heat" last') - if ".con" in outfiles: - f.write('\nsave continuum ".con" last units angstrom') - if ".den" in outfiles: #then ".en" is always there as well. - f.write('\nsave species densities last ".den"\n'+speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\nend") - f.write('\nsave species energies last ".en"\n'+speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\nend") - if dlaw is not None: - dlaw = remove_duplicates(dlaw, "1.7f") - f.write("\n# ========= density law ================") - f.write("\n#depth sets distances from edge of cloud") - f.write("\ndlaw table depth\n") - np.savetxt(f, dlaw, fmt='%1.7f') - f.write('{:.7f}'.format(dlaw[-1,0]+0.1)+ - ' '+'{:.7f}'.format(dlaw[-1,1])) - f.write("\nend of dlaw #last point added to prevent roundoff") - if tlaw is not None: - tlaw = remove_duplicates(tlaw, "1.7f") - f.write("\n# ========= temperature law ============") - f.write("\n#depth sets distances from edge of cloud") - f.write("\ntlaw table depth\n") - np.savetxt(f, tlaw, fmt='%1.7f') - f.write('{:.7f}'.format(tlaw[-1,0]+0.1)+ - ' '+'{:.7f}'.format(tlaw[-1,1])) - f.write("\nend of tlaw #last point added to prevent roundoff") - if cextra is not None: - cextra = remove_duplicates(cextra, "1.7f") - f.write("\n# ========= cextra law ================") - f.write("\n#depth sets distances from edge of cloud") - f.write("\ncextra table depth\n") - np.savetxt(f, cextra, fmt='%1.7f') - f.write('{:.7f}'.format(cextra[-1,0]+0.1)+ - ' '+'{:.7f}'.format(cextra[-1,1])) - f.write("\nend of cextra #last point added to prevent roundoff") - if hextra is not None: - hextra = remove_duplicates(hextra, "1.7f") - f.write("\n# ========= hextra law ================") - f.write("\n#depth sets distances from edge of cloud") - f.write("\nhextra table depth\n") - np.savetxt(f, hextra, fmt='%1.7f') - f.write('{:.7f}'.format(hextra[-1,0]+0.1)+ - ' '+'{:.7f}'.format(hextra[-1,1])) - f.write("\nend of hextra #last point added to prevent roundoff") - - -def insertden_Cloudy_in(simname, denspecies, selected_den_levels=True, rerun=False, cloudy_version="17"): - """ - Takes a Cloudy .in input file and adds extra species to the - 'save species densities' command. This is useful for example if you first went - through the convergeT_parker.py temperature convergence scheme, - but later want to add additional species to the 'converged' simulation. - - Parameters - ---------- - simname : str - Full path + name of the Cloudy simulation, without the file extension. - denspecies : list, optional - List of atomic/ionic species for which to save densities and energies, which - are needed to do radiative transfer. The list can easily be created by the - get_specieslist() function. - selected_den_levels : bool, optional - If True, only energy levels up to the number that can be matched to NIST - will be included in the 'save densities' command. If False, all energy levels - of each species will be included, regardless of whether we can match them - to NIST. By default True. - rerun : bool, optional - Whether to run the new Cloudy input file, by default False - cloudy_version : str, optional - Major Cloudy release version, by default "17". - - Raises - ------ - ValueError - If there are multiple 'save species densities' commands in the Cloudy input file. - """ - - with open(simname+".in", "r") as f: - oldcontent = f.readlines() - - newcontent = oldcontent - indices = [i for i, s in enumerate(oldcontent) if 'save species densities' in s] - if len(indices) == 0: #then there is no 'save species densities' command yet - newcontent.append('\nsave species densities last ".den"\n'+speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\nend") - newcontent.append('\nsave species energies last ".en"\n'+speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\nend") - - elif len(indices) == 1: #then there already is a 'save species densities' command with some species - for sp in denspecies.copy(): - if len([i for i, s in enumerate(oldcontent) if sp+"[" in s]) != 0: #check if this species is already in the file - denspecies.remove(sp) - print(sp, "was already in the .in file so I did not add it again.") - if len(denspecies) >= 1: - newcontent.insert(indices[0]+1, speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\n") - #also add them to the 'save species energies' list - indices2 = [i for i, s in enumerate(oldcontent) if 'save species energies' in s] - newcontent.insert(indices2[0]+1, speciesstring(denspecies, selected_levels=selected_den_levels, cloudy_version=cloudy_version)+"\n") - else: - return - - else: - raise ValueError("There are multiple 'save species densities' commands in the .in file. This shouldn't be the case, please check.") - - newcontent = "".join(newcontent) #turn list into string - with open(simname+".in", "w") as f: #overwrite the old file - f.write(newcontent) - - if rerun: - run_Cloudy(simname) - - -####################################### -########### CLASSES ########### -####################################### - -class Parker: - """ - Class that stores a Parker wind profile and its parameters. - """ - - def __init__(self, plname, T, Mdot, pdir, fH=None, zdict=None, SED=None, readin=True): - """ - Parameters - ---------- - plname : str - Name of the planet - T : str or numeric - Temperature in units of K. - Mdot : str or numeric - log10 of the mass-loss rate in units of g s-1. - pdir : str - Directory as $SUNBATHER_PROJECT_PATH/parker_profiles/*plname*/*pdir*/ - where the isothermal parker wind density and velocity profiles are saved. - Different folders may exist there for a given planet, to separate for example profiles - with different assumptions such as stellar SED/semi-major axis/composition. - fH : float, optional - Hydrogen abundance fraction, in case of a H/He composition, by default None - zdict : dict, optional - Dictionary with the scale factors of all elements relative - to a solar composition. Can be easily created with get_zdict(). - Default is None, which results in a solar composition. - SED : str, optional - Stellar SED name, by default None - readin : bool, optional - Whether to read in the atmospheric profile, by default True - """ - - self.plname = plname - self.T = int(T) - if type(Mdot) == str: - self.Mdot = Mdot - self.Mdotf = float(Mdot) - elif type(Mdot) == float or type(Mdot) == int: - self.Mdot = "%.3f" % Mdot - self.Mdotf = Mdot - if fH != None: - self.fH = fH - if zdict != None: - self.zdict = zdict - if SED != None: - self.SED = SED - if readin: - self.prof = read_parker(plname, T, Mdot, pdir) - - -class Planet: - """ - Class that stores planet/star parameters. - """ - - def __init__(self, name, fullname=None, R=None, Rstar=None, a=None, M=None, Mstar=None, bp=None, SEDname=None): - """ - Parameters - ---------- - name : str - Planet name. Typically does not include spaces. If this name appears in the - $SUNBATHER_PROJECT_PATH/planets.txt file, those parameters are automatically - fetched. Specific values can then be changed by providing them as arguments. - If the planet name does not appear in $SUNBATHER_PROJECT_PATH/planets.txt, - all parameters must be provided upon initialization. - fullname : str, optional - Full planet name, can include spaces and other special characters, by default None - R : float, optional - Planet radius in units of cm, by default None - Rstar : float, optional - Star radius in units of cm, by default None - a : float, optional - Semi-major axis in units of cm, by default None - M : float, optional - Planet mass in units of g, by default None - Mstar : float, optional - Star mass in units of g, by default None - bp : float, optional - Transit impact parameter, in units of the star radius, by default None - SEDname : str, optional - Stellar SED name, by default None - """ - - #check if we can fetch planet parameters from planets.txt: - if name in planets_file['name'].values or name in planets_file['full name'].values: - this_planet = planets_file[(planets_file['name'] == name) | (planets_file['full name'] == name)] - assert len(this_planet) == 1, "Multiple entries were found in planets.txt for this planet name." - - self.name = this_planet['name'].values[0] - self.fullname = this_planet['full name'].values[0] - self.R = this_planet['R [RJ]'].values[0] * RJ #in cm - self.Rstar = this_planet['Rstar [Rsun]'].values[0] *Rsun #in cm - self.a = this_planet['a [AU]'].values[0] * AU #in cm - self.M = this_planet['M [MJ]'].values[0] * MJ #in g - self.Mstar = this_planet['Mstar [Msun]'].values[0] * Msun #in g - self.bp = this_planet['transit impact parameter'].values[0] #dimensionless - self.SEDname = this_planet['SEDname'].values[0].strip() #strip to remove whitespace from beginning and end - - #if any specified, overwrite values read from planets.txt - if fullname != None: - self.fullname = fullname - if R != None: - self.R = R - if Rstar != None: - self.Rstar = Rstar - if a != None: - self.a = a - if M != None: - self.M = M - if Mstar != None: - self.Mstar = Mstar - if bp != None: - self.bp = bp - if SEDname != None: - self.SEDname = SEDname - - else: - assert fullname is not None and R is not None and Rstar is not None and a is not None and M is not None and \ - Mstar is not None and bp is not None and SEDname is not None, \ - "I'm trying to make a Planet that is not in the planets.txt file, but I don't have all required arguments." - self.name = name - self.fullname = fullname - self.R = R - self.Rstar = Rstar - self.a = a - self.M = M - self.Mstar = Mstar - self.bp = bp - self.SEDname = SEDname - - self.__update_Rroche() - self.__update_phi() - self.__update_Kp() - - def set_var(self, name=None, fullname=None, R=None, Rstar=None, a=None, M=None, Mstar=None, bp=None, SEDname=None): - """ - Change planet/star parameters after initialization. - """ - - if name != None: - self.name = name - if R != None: - self.R = R - self.__update_phi() - if Rstar != None: - self.Rstar = Rstar - if a != None: - self.a = a - self.__update_Rroche() - self.__update_Kp() - if M != None: - self.M = M - self.__update_phi() - self.__update_Rroche() - self.__update_Kp() - if Mstar != None: - self.Mstar = Mstar - self.__update_Rroche() - self.__update_Kp() - if bp != None: - self.bp = bp - if SEDname != None: - self.SEDname = SEDname - - def __update_phi(self): - """ - Tries to set/update the gravitational potential. - """ - - if (self.M != None) and (self.R != None): - self.phi = G * self.M / self.R - else: - self.phi = None - - def __update_Rroche(self): - """ - Tries to set/update the Roche radius. - """ - - if (self.a != None) and (self.M != None) and (self.Mstar != None): - self.Rroche = roche_radius(self.a, self.M, self.Mstar) - else: - self.Rroche = None - - def __update_Kp(self): - """ - Tries to set/update the orbital velocity semi-amplitude. - """ - - if (self.a != None) and (self.M != None) and (self.Mstar != None): - self.Kp = np.sqrt(G * (self.M + self.Mstar) / self.a) - else: - self.Kp = None - - def print_params(self): - """ - Prints out all parameters in read-friendly format. - """ - - print(f"Name: {self.name}") - if self.fullname is not None: - print(f"Full name: {self.fullname}") - if self.R is not None: - print(f"Planet radius: {self.R} cm, {self.R / RJ} RJ") - if self.Rstar is not None: - print(f"Star radius: {self.Rstar} cm, {self.Rstar / Rsun} Rsun") - if self.a is not None: - print(f"Semi-major axis: {self.a} cm, {self.a / AU} AU") - if self.M is not None: - print(f"Planet mass: {self.M} g, {self.M / MJ} MJ") - if self.Mstar is not None: - print(f"Star mass: {self.Mstar} g, {self.Mstar / Msun} Msun") - if self.bp is not None: - print(f"Transit impact parameter: {self.bp} Rstar") - if self.SEDname is not None: - print(f"Stellar spectrum name: {self.SEDname}") - if self.Rroche is not None: - print(f"Roche radius: {self.Rroche} cm, {self.Rroche / RJ} RJ, {self.Rroche / self.R} Rp") - if self.phi is not None: - print(f"log10(Gravitational potential): {np.log10(self.phi)} log10(erg/g)") - if self.Kp is not None: - print(f"Orbital velocity semi-amplitude: {self.Kp} cm/s, {self.Kp/1e5} km/s") - - def plot_transit_geometry(self, phase=0., altmax=None): - """ - Plots a schematic of the transit geometry. Helpful to understand - where the planet and its atmosphere are relative to the stellar disk, - for a given planet impact parameter and phase. The dotted line shows - the planet Roche radius. The altmax argument can be used to draw - another dashed line in units of the planet radius, for example the - extent of the sunbather simulation (typically 8 Rp). - """ - - fig, ax = plt.subplots(1) - #draw star - ax.plot(self.Rstar*np.cos(np.linspace(0, 2*np.pi, 100)), self.Rstar*np.sin(np.linspace(0, 2*np.pi, 100)), c='k', zorder=0) - ax.text(1/np.sqrt(2)*self.Rstar, -1/np.sqrt(2)*self.Rstar, r"$R_s$", color="k", ha="left", va="top", zorder=0) - - #draw planet - pl_zorder = -1 if (phase%1 > 0.25 and phase%1 < 0.75) else 1 - ax.plot(self.a*np.sin(2*np.pi*phase) + self.R*np.cos(np.linspace(0, 2*np.pi, 100)), - self.bp*self.Rstar + self.R*np.sin(np.linspace(0, 2*np.pi, 100)), c='b', zorder=pl_zorder) - ax.text(self.a*np.sin(2*np.pi*phase) + 1/np.sqrt(2)*self.R, self.bp*self.Rstar - 1/np.sqrt(2)*self.R, - r"$R_P$", color="b", ha="left", va="top", zorder=pl_zorder) - - #draw planet vy direction - if phase%1 > 0.75 or phase%1 < 0.25: - ax.text(self.a*np.sin(2*np.pi*phase) + self.R, self.bp*self.Rstar, r"$\rightarrow$", color="b", ha="left", va="top", zorder=pl_zorder) - title = f"Phase: {phase} mod 1 = {phase%1}" - elif phase%1 > 0.25 and phase%1 < 0.75: - ax.text(self.a*np.sin(2*np.pi*phase) - self.R, self.bp*self.Rstar, r"$\leftarrow$", color="b", ha="right", va="top", zorder=pl_zorder) - title = f"Phase: {phase} mod 1 = {phase%1} (planet behind star)" - else: #at 0.25 or 0.75, only vx velocity - pass - - #draw Roche indication - if self.Rroche is not None: - ax.plot(self.a*np.sin(2*np.pi*phase) + self.Rroche*np.cos(np.linspace(0, 2*np.pi, 100)), - self.bp*self.Rstar + self.Rroche*np.sin(np.linspace(0, 2*np.pi, 100)), c='b', linestyle='dotted') - ax.text(self.a*np.sin(2*np.pi*phase) + 1/np.sqrt(2)*self.Rroche, self.bp*self.Rstar - 1/np.sqrt(2)*self.Rroche, - r"$R_{Roche}$", color="b", ha="left", va="top", zorder=pl_zorder) - - #draw altmax indication - if altmax is not None: - ax.plot(self.a*np.sin(2*np.pi*phase) + altmax*self.R*np.cos(np.linspace(0, 2*np.pi, 100)), - self.bp*self.Rstar + altmax*self.R*np.sin(np.linspace(0, 2*np.pi, 100)), c='b', linestyle='dashed') - ax.text(self.a*np.sin(2*np.pi*phase) + altmax/np.sqrt(2)*self.R, self.bp*self.Rstar - altmax/np.sqrt(2)*self.R, - "altmax", color="b", ha="left", va="top", zorder=pl_zorder) - - plt.axis('equal') - ax.set_xlabel('y [cm]') - ax.set_ylabel('z [cm]') - ax.set_title(title) - plt.show() - - def max_T0(self, mu_bar=1.): - """ - Calculates the maximum isothermal temperature T0 that the Parker wind can have, - for it to still be transonic. If T0 is higher than this value, - Rp > Rs which breaks the assumption of the Parker wind. - See Vissapragada et al. (2024) on TOI-1420 b. - """ - - maxT0 = G * self.M * mH * mu_bar / (2 * self.R * k) - - return maxT0 - - -class Sim: - """ - Loads the output of a Cloudy simulation. Tailored towards simulations of - an escaping exoplanet atmosphere. - """ - - def __init__(self, simname, altmax=None, proceedFail=False, files=['all'], planet=None, parker=None): - """ - Parameters - ---------- - simname : str - Full path + simulation name excluding file extension. - altmax : int, optional - Maximum altitude of the simulation in units of the planet radius. Will also - be automatically read from the input file if written as a comment. By default None. - proceedFail : bool, optional - Whether to proceed loading the simulation if Cloudy did not exit OK, by default False - files : list, optional - List of file extensions of Cloudy output to load. For example, - include '.heat' to read the output of the 'save heating' command. - By default ['all'], which reads in all output files present that are understood by - this class. - planet : Planet, optional - Object storing planet parameters. Will also be automatically read from the input file - if written as a comment. By default None. - parker : Parker, optional - Object storing the isothermal Parker wind atmospheric profiles and parameters. Will - also be automatically read from the input file if written as a comment. By default None. - - Raises - ------ - TypeError - If the simname argument is not a string. - TypeError - If a Cloudy version was used that is not supported by sunbather. - FileNotFoundError - If the Cloudy simulation did not exit OK and proceedFail = False. - TypeError - If the altmax argument is not numeric. - """ - - if not isinstance(simname, str): - raise TypeError("simname must be set to a string") - self.simname = simname - - #check the Cloudy version, and if the simulation did not crash. - _succesful = False - with open(simname+'.out', 'r') as f: - _outfile_content = f.read() - if "Cloudy exited OK" in _outfile_content: - _succesful = True - else: - _succesful = False - - if "Cloudy 17" in _outfile_content: - self.cloudy_version = "17" - elif "Cloudy 23" in _outfile_content: - self.cloudy_version = "23" - elif _succesful: - raise TypeError(f"This simulation did not use Cloudy v17 or v23, which are the only supported versions: {simname}") - if not _succesful and not proceedFail: - raise FileNotFoundError(f"This simulation went wrong: {simname} Check the .out file!") - - #read the .in file to extract some sim info like changes to the chemical composition and altmax - self.disabled_elements = [] - zelem = {} - _parker_T, _parker_Mdot, _parker_dir = None, None, None #temp variables - with open(simname+'.in', 'r') as f: - for line in f: - if line[0] == '#': #then it is a comment written by sunbather, extract info: - #check if a planet was defined - if 'plname' in line: - self.p = Planet(line.split('=')[-1].strip('\n')) - - #check if a Parker profile was defined - if 'parker_T' in line: - _parker_T = int(line.split('=')[-1].strip('\n')) - if 'parker_Mdot' in line: - _parker_Mdot = line.split('=')[-1].strip('\n') - if 'parker_dir' in line: - _parker_dir = line.split('=')[-1].strip('\n') - - #check if an altmax was defined - if 'altmax' in line: - self.altmax = int(line.split('=')[1].strip('\n')) - - #read SED - if 'table SED' in line: - self.SEDname = line.split('"')[1] - - #read chemical composition - if 'element scale factor' in line.rstrip(): - zelem[element_symbols[line.split(' ')[3]]] = float(line.rstrip().split(' ')[-1]) - elif 'element' in line.rstrip() and 'off' in line.rstrip(): - self.disabled_elements.append(element_symbols[line.split(' ')[1]]) - zelem[element_symbols[line.split(' ')[1]]] = 0. - - #set zdict and abundances as attributes - self.zdict = get_zdict(zelem=zelem) - self.abundances = get_abundances(zdict=self.zdict) - - #overwrite/set manually given Planet object - if planet != None: - assert isinstance(planet, Planet) - if hasattr(self, 'p'): - warnings.warn("I had already read out the Planet object from the .in file, but I will overwrite that with the object you have given.") - self.p = planet - - #check if the SED of the Planet object matches the SED of the Cloudy simulation - if hasattr(self, 'p') and hasattr(self, 'SEDname'): - if self.p.SEDname != self.SEDname: - warnings.warn(f"I read in the .in file that the SED used is {self.SEDname} which is different from the one of your Planet object. " \ - "I will change the .SEDname attribute of the Planet object to match the one actually used in the simulation. Are you " \ - "sure that also the associated Parker wind profile is correct?") - self.p.set_var(SEDname = self.SEDname) - - #try to set a Parker object if the .in file had the required info for that - if hasattr(self, 'p') and (_parker_T != None) and (_parker_Mdot != None) and (_parker_dir != None): - self.par = Parker(self.p.name, _parker_T, _parker_Mdot, _parker_dir) - - #overwrite/set manually given Parker object - if parker != None: - assert isinstance(parker, Parker) - if hasattr(self, 'par'): - warnings.warn("I had already read out the Parker object from the .in file, but I will overwrite that with the object you have given.") - self.par = parker - - #overwrite/set manually given altmax - if altmax != None: - if not (isinstance(altmax, float) or isinstance(altmax, int)): - raise TypeError("altmax must be set to a float or int") #can it actually be a float? I'm not sure if the code can handle it - check and try. - if hasattr(self, 'altmax'): - if self.altmax != altmax: - warnings.warn("I read the altmax from the .in file, but the value you have explicitly passed is different. " \ - "I will use your value, but please make sure it is correct.") - self.altmax = altmax - - - #temporary variables for adding the alt-columns to the pandas dataframes - _Rp, _altmax = None, None - if hasattr(self, 'p') and hasattr(self, 'altmax'): - _Rp = self.p.R - _altmax = self.altmax - - #read in the Cloudy simulation files - self.simfiles = [] - for simfile in glob.glob(simname+'.*', recursive=True): - filetype = simfile.split('.')[-1] - if filetype=='ovr' and ('ovr' in files or 'all' in files): - self.ovr = process_overview(self.simname+'.ovr', Rp=_Rp, altmax=_altmax, abundances=self.abundances) - self.simfiles.append('ovr') - if filetype=='con' and ('con' in files or 'all' in files): - self.con = process_continuum(self.simname+'.con') - self.simfiles.append('con') - if filetype=='heat' and ('heat' in files or 'all' in files): - self.heat = process_heating(self.simname+'.heat', Rp=_Rp, altmax=_altmax, cloudy_version=self.cloudy_version) - self.simfiles.append('heat') - if filetype=='cool' and ('cool' in files or 'all' in files): - self.cool = process_cooling(self.simname+'.cool', Rp=_Rp, altmax=_altmax, cloudy_version=self.cloudy_version) - self.simfiles.append('cool') - if filetype=='coolH2' and ('coolH2' in files or 'all' in files): - self.coolH2 = process_coolingH2(self.simname+'.coolH2', Rp=_Rp, altmax=_altmax) - self.simfiles.append('coolH2') - if filetype=='den' and ('den' in files or 'all' in files): - self.den = process_densities(self.simname+'.den', Rp=_Rp, altmax=_altmax) - self.simfiles.append('den') - if filetype=='en' and ('en' in files or 'all' in files): - self.en = process_energies(self.simname+'.en', cloudy_version=self.cloudy_version) - self.simfiles.append('en') - - #set the velocity structure in .ovr if we have an associated Parker profile - needed for radiative transfer - if hasattr(self, 'par') and hasattr(self, 'ovr'): - if hasattr(self.par, 'prof') and hasattr(self.ovr, 'alt'): - Sim.addv(self, self.par.prof.alt, self.par.prof.v) - - - def get_simfile(self, simfile): - """ - Returns the output of the requested simulation output file. - These can also be accessed as an attribute, - for example mysim.ovr or mysim.cool for a Sim object called mysim - """ - - if simfile not in self.simfiles: - raise FileNotFoundError("This simulation does not have a", simfile, "output file.") - - if simfile == 'ovr': - return self.ovr - elif simfile == 'con': - return self.con - elif simfile == 'heat': - return self.heat - elif simfile == 'cool': - return self.cool - elif simfile == 'coolH2': - return self.coolH2 - elif simfile == 'den': - return self.den - elif simfile == 'en': - return self.en - elif simfile == 'ionFe': - return self.ionFe - elif simfile == 'ionNa': - return self.ionNa - - - def add_parker(self, parker): - """ - Adds a Parker profile object to the Sim, in case it wasn't added upon initialization. - """ - - assert isinstance(parker, Parker) - self.par = parker - if hasattr(parker, 'prof'): - Sim.addv(self, parker.prof.alt, parker.prof.v) - - - def addv(self, alt, v, delete_negative=True): - """ - Adds a velocity profile in cm s-1 on the Cloudy grid. Will be added to the .ovr file, - but also available as the .v attribute for backwards compatability of sunbather. - Called automatically when adding a Parker object to the Sim. - """ - - assert 'ovr' in self.simfiles, "Simulation must have a 'save overview .ovr file" - assert 'alt' in self.ovr.columns, "The .ovr file must have an altitude column (which in turn requires a known Rp and altmax)" - - if delete_negative: - v[v < 0.] = 0. - - self.ovr['v'] = interp1d(alt, v)(self.ovr.alt) - - vseries = pd.Series(index=self.ovr.alt.index, dtype=float) - vseries[self.ovr.alt.index] = interp1d(alt, v)(self.ovr.alt) - self.v = vseries diff --git a/tests/test.py b/tests/test.py index 3f88f12..11071c3 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,109 +1,157 @@ import os import sys -this_path = os.path.dirname(os.path.abspath(__file__)) #the absolute path where this code lives -src_path = this_path.split('tests')[-2] + 'src/' -sys.path.append(src_path) - -#sunbather imports -import tools -import RT -#other imports +# other imports import pandas as pd import numpy as np from scipy.interpolate import interp1d import shutil +# sunbather imports +from sunbather import tools, RT -print("\nWill perform installation check by running the three main sunbather modules and checking if the output is as expected. " \ - +"Expected total run-time: 10 to 60 minutes. Should print 'success' at the end.\n") - -### SETUP CHECKS ### +# the absolute path where this code lives +this_path = os.path.dirname(os.path.abspath(__file__)) +src_path = this_path.split('tests')[-2] + 'src/' -#make sure projectpath exists -assert os.path.isdir(tools.projectpath), "Please create the projectpath folder on your machine" -#make sure the planets.txt file exists -assert os.path.isfile(tools.projectpath+'/planets.txt'), "Please make sure the 'planets.txt' file is present in $SUNBATHER_PROJECT_PATH" -#make sure the SED we need for this test has been copied to Cloudy -assert os.path.isfile(tools.cloudypath+'/data/SED/eps_Eri_binned.spec'), "Please copy /sunbather/stellar_SEDs/eps_Eri_binned.spec into $CLOUDY_PATH/data/SED/" +print( + "\nWill perform installation check by running the three main sunbather modules and checking if the output is as expected. " + + "Expected total run-time: 10 to 60 minutes. Should print 'success' at the end.\n" +) +# SETUP CHECKS +# make sure projectpath exists +projectpath = tools.get_sunbather_project_path() -### CHECK IF test.py HAS BEEN RAN BEFORE ### +assert os.path.isdir( + projectpath +), "Please create the projectpath folder on your machine" +# make sure the planets.txt file exists +assert os.path.isfile( + projectpath + "/planets.txt" +), "Please make sure the 'planets.txt' file is present in $SUNBATHER_PROJECT_PATH" +# make sure the SED we need for this test has been copied to Cloudy +assert os.path.isfile( + tools.get_cloudy_path() + "/data/SED/eps_Eri_binned.spec" +), "Please copy /sunbather/stellar_SEDs/eps_Eri_binned.spec into $CLOUDY_PATH/data/SED/" -parker_profile_file = tools.projectpath+"/parker_profiles/WASP52b/test/pprof_WASP52b_T=9000_M=11.000.txt" -simulation_folder = tools.projectpath+"/sims/1D/WASP52b/test/parker_9000_11.000/" -if os.path.exists(parker_profile_file) or os.path.exists(simulation_folder): - confirmation = input(f"It looks like test.py has been ran before, as {parker_profile_file} and/or {simulation_folder} already exist. Do you want to delete the previous output before continuing (recommended)? (y/n): ") - if confirmation.lower() == "y": - if os.path.exists(parker_profile_file): - os.remove(parker_profile_file) - if os.path.exists(simulation_folder): - shutil.rmtree(simulation_folder) - print("\nFile(s) deleted successfully.") - else: - print("\nDeletion cancelled.") +# ## CHECK IF test.py HAS BEEN RAN BEFORE ### +parker_profile_file = ( + projectpath + + "/parker_profiles/WASP52b/test/pprof_WASP52b_T=9000_M=11.000.txt" +) +simulation_folder = projectpath + "/sims/1D/WASP52b/test/parker_9000_11.000/" - -print("\nChecking construct_parker.py. A runtime for this module will follow when done...\n") +if os.path.exists(parker_profile_file) or os.path.exists(simulation_folder): + confirmation = input( + f"It looks like test.py has been ran before, as {parker_profile_file} and/or {simulation_folder} already exist. Do you want to delete the previous output before continuing (recommended)? (y/n): " + ) + if confirmation.lower() == "y": + if os.path.exists(parker_profile_file): + os.remove(parker_profile_file) + if os.path.exists(simulation_folder): + shutil.rmtree(simulation_folder) + print("\nFile(s) deleted successfully.") + else: + print("\nDeletion cancelled.") + + +print( + "\nChecking construct_parker.py. A runtime for this module will follow when done...\n" +) ### CREATING PARKER PROFILE ### -#create a parker profile - we use the p-winds/Cloudy hybrid scheme -os.system(f"cd {tools.sunbatherpath} && python construct_parker.py -plname WASP52b -pdir test -Mdot 11.0 -T 9000 -z 10 -zelem Ca=0 -overwrite") -#load the created profile -pprof_created = pd.read_table(tools.projectpath+'/parker_profiles/WASP52b/test/pprof_WASP52b_T=9000_M=11.000.txt', - names=['alt', 'rho', 'v', 'mu'], dtype=np.float64, comment='#') -#load the expected output -pprof_expected = pd.read_table(this_path+'/materials/pprof_WASP52b_T=9000_M=11.000.txt', - names=['alt', 'rho', 'v', 'mu'], dtype=np.float64, comment='#') -#check if they are equal to within 1% in altitude and mu and 10% in rho and v. -assert np.isclose(pprof_created[['alt', 'mu']], pprof_expected[['alt', 'mu']], rtol=0.01).all().all(), "The profile created with the construct_parker.py module is not as expected" -assert np.isclose(pprof_created[['rho', 'v']], pprof_expected[['rho', 'v']], rtol=0.1).all().all(), "The profile created with the construct_parker.py module is not as expected" - - - -print("\nChecking convergeT_parker.py. A runtime for this module will follow when done...\n") - -### CONVERGING TEMPERATURE STRUCTURE WITH CLOUDY ### - -#run the created profile through Cloudy -os.system(f"cd {tools.sunbatherpath} && python convergeT_parker.py -plname WASP52b -pdir test -dir test -Mdot 11.0 -T 9000 -z 10 -zelem Ca=0 -overwrite") -#load the created simulation -sim_created = tools.Sim(tools.projectpath+'/sims/1D/WASP52b/test/parker_9000_11.000/converged') -#load the expected simulation -sim_expected = tools.Sim(this_path+'/materials/converged') -#interpolate them to a common altitude grid as Cloudy's internal depth-grid may vary between simulations -alt_grid = np.logspace(np.log10(max(sim_created.ovr.alt.iloc[-1], sim_expected.ovr.alt.iloc[-1])+1e4), - np.log10(min(sim_created.ovr.alt.iloc[0], sim_expected.ovr.alt.iloc[0])-1e4), num=100) +# create a parker profile - we use the p-winds/Cloudy hybrid scheme +os.system( + f"cd {tools.sunbatherpath} && python construct_parker.py -plname WASP52b -pdir test -Mdot 11.0 -T 9000 -z 10 -zelem Ca=0 -overwrite" +) +# load the created profile +pprof_created = pd.read_table( + projectpath + + "/parker_profiles/WASP52b/test/pprof_WASP52b_T=9000_M=11.000.txt", + names=["alt", "rho", "v", "mu"], + dtype=np.float64, + comment="#", +) +# load the expected output +pprof_expected = pd.read_table( + this_path + "/materials/pprof_WASP52b_T=9000_M=11.000.txt", + names=["alt", "rho", "v", "mu"], + dtype=np.float64, + comment="#", +) +# check if they are equal to within 1% in altitude and mu and 10% in rho and v. +assert ( + np.isclose(pprof_created[["alt", "mu"]], pprof_expected[["alt", "mu"]], rtol=0.01) + .all() + .all() +), "The profile created with the construct_parker.py module is not as expected" +assert ( + np.isclose(pprof_created[["rho", "v"]], pprof_expected[["rho", "v"]], rtol=0.1) + .all() + .all() +), "The profile created with the construct_parker.py module is not as expected" + + +print( + "\nChecking convergeT_parker.py. A runtime for this module will follow when done...\n" +) + +# ## CONVERGING TEMPERATURE STRUCTURE WITH CLOUDY ### + +# run the created profile through Cloudy +os.system( + f"cd {tools.sunbatherpath} " + f"&& python convergeT_parker.py " + f"-plname WASP52b -pdir test -dir test " + f"-Mdot 11.0 -T 9000 -z 10 -zelem Ca=0 -overwrite" +) +# load the created simulation +sim_created = tools.Sim( + projectpath + "/sims/1D/WASP52b/test/parker_9000_11.000/converged" +) +# load the expected simulation +sim_expected = tools.Sim(this_path + "/materials/converged") +# interpolate them to a common altitude grid as Cloudy's internal depth-grid may vary between simulations +alt_grid = np.logspace( + np.log10(max(sim_created.ovr.alt.iloc[-1], sim_expected.ovr.alt.iloc[-1]) + 1e4), + np.log10(min(sim_created.ovr.alt.iloc[0], sim_expected.ovr.alt.iloc[0]) - 1e4), + num=100, +) T_created = interp1d(sim_created.ovr.alt, sim_created.ovr.Te)(alt_grid) T_expected = interp1d(sim_expected.ovr.alt, sim_expected.ovr.Te)(alt_grid) -#check if they are equal to within 10% -assert np.isclose(T_created, T_expected, rtol=0.1).all(), "The converged temperature profile of Cloudy is not as expected" - +# check if they are equal to within 10% +assert np.isclose( + T_created, T_expected, rtol=0.1 +).all(), "The converged temperature profile of Cloudy is not as expected" print("\nChecking RT.py...\n") ### MAKING TRANSIT SPECTRA ### -#make a helium spectrum +# make a helium spectrum wavs = np.linspace(10830, 10836, num=300) -FinFout_created, found_lines, notfound_lines = RT.FinFout(sim_created, wavs, 'He') -#load the expected helium spectrum -FinFout_expected = np.genfromtxt(this_path+'/materials/FinFout_helium.txt')[:,1] -assert np.isclose(FinFout_created, FinFout_expected, rtol=0.05).all(), "The created helium spectrum is not as expected" -#make a magnesium+ spectrum +FinFout_created, found_lines, notfound_lines = RT.FinFout(sim_created, wavs, "He") +# load the expected helium spectrum +FinFout_expected = np.genfromtxt(this_path + "/materials/FinFout_helium.txt")[:, 1] +assert np.isclose( + FinFout_created, FinFout_expected, rtol=0.05 +).all(), "The created helium spectrum is not as expected" +# make a magnesium+ spectrum wavs = np.linspace(2795.5, 2797, num=300) -FinFout_created, found_lines, notfound_lines = RT.FinFout(sim_created, wavs, 'Mg+') -#load the expected magnesium+ spectrum -FinFout_expected = np.genfromtxt(this_path+'/materials/FinFout_magnesium+.txt')[:,1] -assert np.isclose(FinFout_created, FinFout_expected, rtol=0.05).all(), "The created magnesium+ spectrum is not as expected" - +FinFout_created, found_lines, notfound_lines = RT.FinFout(sim_created, wavs, "Mg+") +# load the expected magnesium+ spectrum +FinFout_expected = np.genfromtxt(this_path + "/materials/FinFout_magnesium+.txt")[:, 1] +assert np.isclose( + FinFout_created, FinFout_expected, rtol=0.05 +).all(), "The created magnesium+ spectrum is not as expected" -#if we made it past all the asserts, the code is correctly installed +# if we made it past all the asserts, the code is correctly installed print("\nSuccess.") diff --git a/tests/test_sunbather.py b/tests/test_sunbather.py new file mode 100644 index 0000000..8790393 --- /dev/null +++ b/tests/test_sunbather.py @@ -0,0 +1,54 @@ +""" +Tests for the sunbather package +""" +import os +import pytest + + +def f(): + raise SystemExit(1) + + +def test_import(): + """ + Tests if sunbather can be imported. + """ + try: + import sunbather + except ImportError: + f() + + +def test_projectdirs(): + """ + Make sure projectpath exists + """ + from sunbather import tools + projectpath = tools.get_sunbather_project_path() + assert os.path.isdir( + projectpath + ), "Please create the projectpath folder on your machine" + + +def test_planetstxt(): + """ + Make sure the planets.txt file exists + """ + from sunbather import tools + projectpath = tools.get_sunbather_project_path() + assert os.path.isfile( + projectpath + "/planets.txt" + ), "Please make sure the 'planets.txt' file is present in $SUNBATHER_PROJECT_PATH" + + +def test_seds(): + """ + Make sure the SED we need for this test has been copied to Cloudy + """ + from sunbather import tools + assert os.path.isfile( + tools.get_cloudy_path() + "/data/SED/eps_Eri_binned.spec" + ), ( + "Please copy /sunbather/stellar_SEDs/eps_Eri_binned.spec " + "into $CLOUDY_PATH/data/SED/" + )