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 5020ed5..a87e2cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +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/ +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..07bdabf --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,12 @@ +API +=== + +.. autosummary:: + :toctree: generated + + sunbather + sunbather.construct_parker + sunbather.convergeT_parker + sunbather.install_cloudy + sunbather.solveT + sunbather.tools diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..827410d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,42 @@ +# 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", +] + +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..52db9b6 --- /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 +:depth: 2 +installation +glossary +faq +api +Notebook: Fit Helium \ +Notebook: 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/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 b04f4cc..0000000 --- a/src/construct_parker.py +++ /dev/null @@ -1,750 +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, spectrum, zdict, pdir, - mu_bar=None, 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. - 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) - 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 - - - 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. - - 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, - 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.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, mu_bar, 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, 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. - 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(). - 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. - """ - - 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 - - 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., 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, T, spectrum, zdict, pdir, - mu_bar=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: {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, T, spectrum, zdict, pdir, mu_bar=None, no_tidal=no_tidal, altmax=altmax) - 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, 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, mu_bar, launch_velocity = save_temp_parker_profile(planet, Mdot, T, spectrum, zdict, pdir, - mu_bar=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, avoid_pwinds_mubar, 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. - 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 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 - 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 - - if fH != None: #then run p_winds standalone - 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, 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_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, avoid_pwinds_mubar, - 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. - 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 = 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, avoid_pwinds_mubar, 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("-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. [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 or args.avoid_pwinds_mubar): - warnings.warn("The -zelem, -mu_conv -mu_maxit, and -avoid_pwinds_mubar 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.avoid_pwinds_mubar, 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.avoid_pwinds_mubar, 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.avoid_pwinds_mubar, 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.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") 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 338f247..0000000 --- a/src/solveT.py +++ /dev/null @@ -1,721 +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 - exp_cloc = len(HCratio) #start by setting a 'too high' value - expcool_dominates = (radcool / (radcool+expcool+advcool) < 0.2) - if True 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./(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): - 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..12a6076 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,25 @@ 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 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..dae299e --- /dev/null +++ b/src/sunbather/__init__.py @@ -0,0 +1,112 @@ +""" +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. Also asks to copy the default files to the working dir. + + :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 + workingdir = "./" + if not quiet: + q = input(f"Copy default files to the working dir ({workingdir})? (y/n) ") + while q.lower() not in ["y", "n"]: + q = input("Please enter 'y' or 'n': ") + if q == "n": + return + + sunbatherpath = f"{pathlib.Path(__file__).parent.resolve()}" + for file in os.listdir(f"{sunbatherpath}/data/workingdir"): + if not os.path.exists(f"{workingdir}/{file}"): + shutil.copyfile( + f"{sunbatherpath}/data/workingdir/{file}", + f"{workingdir}/{file}", + ) + else: + if not quiet: + print("File already exists! Overwrite?") + q = input("(y/n) ") + while q.lower() not in ["y", "n"]: + q = input("Please enter 'y' or 'n': ") + if q == "n": + continue + else: + continue + shutil.copyfile( + f"{sunbatherpath}/data/workingdir/{file}", + f"{workingdir}/{file}", + ) + + return + + +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..2c60c72 --- /dev/null +++ b/src/sunbather/construct_parker.py @@ -0,0 +1,1292 @@ +""" +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, + "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, 0.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 included. + altmax : int, optional + Maximum altitude of the profile in units of the planet radius. By default 20. + """ + + Mdot = float(Mdot) + T = int(T) + + projectpath = tools.get_sunbather_project_path() + save_name = ( + f"{projectpath}/parker_profiles/{planet.name}/{pdir}/" + f"pprof_{planet.name}_T={str(T)}_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}", + T, + ) + # 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, + 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, + spectrum, + zdict, + pdir, + mu_bar=None, + 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. + 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) + T = int(T) + + # 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, + 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, + 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( + 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 = ( + f"{projectpath}/parker_profiles/{planet.name}/{pdir}/temp/" + f"pprof_{planet.name}_T={str(T)}_M={Mdot:.3f}.txt" + ) + zdictstr = "abundance scale factors relative to solar:" + for sp in zdict.keys(): + zdictstr += f" {sp}={zdict[sp]:.1f}" + 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, 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.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, + T, + 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. + 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(). + 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 = ( + 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 + + 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, + T, + spectrum, + zdict, + pdir, + mu_bar=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: {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, + T, + spectrum, + zdict, + pdir, + mu_bar=None, + no_tidal=no_tidal, + altmax=altmax, + ) + 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, 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 " + 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, + T, + spectrum, + zdict, + pdir, + mu_bar=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 + 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, + avoid_pwinds_mubar, + 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. + 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 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 + 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 fH is not None: # then run p_winds standalone + 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, + 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_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, + avoid_pwinds_mubar, + 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. + 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 = 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, + avoid_pwinds_mubar, + no_tidal, + ) + ) + + p.starmap(catch_errors_run_s, pars) + p.close() + p.join() + + +def new_argument_parser(): + parser = argparse.ArgumentParser( + description="Creates 1D Parker profile(s) using the p_winds code and Cloudy.", + 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( + "-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( + "-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 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 [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" + ), + ) + 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( + "-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: + # This is a bit ugly, but it allows us to either call main directly or + # to call the script with command line arguments + print(f"-{key}={value}" for key, value in kwargs.items()) + args = parser.parse_args([f'-{key}={value}' for key, value in kwargs.items()]) + + 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.fH 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 'fH', 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" + ) + + 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.avoid_pwinds_mubar, + 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.avoid_pwinds_mubar, + 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.avoid_pwinds_mubar, + 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.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(sys.argv[1:]) diff --git a/src/sunbather/convergeT_parker.py b/src/sunbather/convergeT_parker.py new file mode 100644 index 0000000..f30fc8c --- /dev/null +++ b/src/sunbather/convergeT_parker.py @@ -0,0 +1,826 @@ +""" +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.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) + ) + + # 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 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 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(*args, **kwargs): + """ + Main function + """ + + t0 = time.time() + + parser = new_argument_parser() + args = parser.parse_args(*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..18e974e --- /dev/null +++ b/src/sunbather/install_cloudy.py @@ -0,0 +1,101 @@ +""" +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. + """ + 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..c098500 --- /dev/null +++ b/src/sunbather/tools.py @@ -0,0 +1,2961 @@ +""" +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 + +# ###################################### +# ########## GLOBAL 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 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 get_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_file = 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_file + 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, 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 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}" + T = str(int(T)) + filename = ( + f"{get_sunbather_project_path()}/parker_profiles/{plname}/" + f"{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.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, + 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 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 constantT 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 constantT is not 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 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, + 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". + """ + 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 constantT 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 constantT is not 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 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, 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 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, 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: + planets_file = get_planets_file() + 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 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: + 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/" + )