diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 7cf25b6c13..f15ec9990c 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -36,4 +36,10 @@ jobs: export PATH_TO_POSYDON=./ export PATH_TO_POSYDON_DATA=./posydon/unit_tests/_data/ export MESA_DIR=./ - python -m pytest posydon/unit_tests/ --cov=posydon.config --cov=posydon.utils --cov=posydon.grids --cov-branch --cov-report term-missing --cov-fail-under=100 + python -m pytest posydon/unit_tests/ --cov=posydon.utils \ + --cov=posydon.config \ + --cov=posydon.grids \ + --cov=posydon.popsyn.star_formation_history \ + --cov-branch \ + --cov-report term-missing \ + --cov-fail-under=100 diff --git a/.github/workflows/deploy-github-pages-development.yml b/.github/workflows/deploy-github-pages-development.yml new file mode 100644 index 0000000000..6ca83311e4 --- /dev/null +++ b/.github/workflows/deploy-github-pages-development.yml @@ -0,0 +1,91 @@ +name: Website Deploy on Development + +on: + push: + branches: + - development + +jobs: + build_development: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + path: 'POSYDON' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Get pip cache dir + id: pip-cache + run: | + python -m pip install --upgrade pip + echo "::set-output name=dir::$(pip cache dir)" + + - name: Load pip cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py', '**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + # install pandoc for the documentation + sudo apt-get update -y + sudo apt-get install gfortran swig libhdf5-serial-dev libmpich-dev -y + sudo apt-get install pandoc -y + + # install the dependencies in python + python -m pip install pandoc + python -m pip install coverage cpp-coveralls flake8 pytest + + - name: Make Documentation + env: + PATH_TO_POSYDON_DATA: ./ + MESA_DIR: ./ + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # make the output folder + output_folder=$GITHUB_WORKSPACE/_build/ + echo "Output folder: $output_folder" + mkdir -p $output_folder + + export PATH_TO_POSYDON=$GITHUB_WORKSPACE/POSYDON + cd $PATH_TO_POSYDON + + # build the documentation for the development branch + git checkout -f development + + # clean the repo & install + git clean -fdx + python -m pip install .[doc,vis] + + # build the documentation + cd docs && make html + + # move and copy docs out of the POSYDON folder + touch _build/html/.nojekyll + cd ../ + mkdir -p _build + mv docs/_build/html/ $output_folder/development + + - name: Deploy to GitHub Pages + if: success() + uses: crazy-max/ghaction-github-pages@v4 + with: + target_branch: gh-pages + build_dir: _build/ + keep_history: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_WORKSPACE: ${{ github.workspace }} diff --git a/.github/workflows/deploy-github-pages-release.yml b/.github/workflows/deploy-github-pages-release.yml new file mode 100644 index 0000000000..8ba7363822 --- /dev/null +++ b/.github/workflows/deploy-github-pages-release.yml @@ -0,0 +1,293 @@ +name: Website Deploy on Release + +on: + release: + types: [published] + +jobs: + build_v1: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + path: 'POSYDON' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.7' + + - name: Get pip cache dir + id: pip-cache + run: | + python -m pip install --upgrade pip + echo "::set-output name=dir::$(pip cache dir)" + + - name: Load pip cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py', '**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + # install pandoc for the documentation + sudo apt-get update -y + sudo apt-get install gfortran swig libhdf5-serial-dev libmpich-dev -y + sudo apt-get install pandoc -y + + # install the dependencies in python + python -m pip install pandoc + python -m pip install coverage cpp-coveralls flake8 pytest + # required for python 3.7 + python -m pip install wheel + + - name: Make Documentation + env: + PATH_TO_POSYDON: ./ + PATH_TO_POSYDON_DATA: ./ + MESA_DIR: ./ + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # make the output folder + output_folder=$GITHUB_WORKSPACE/_build/ + echo "Output folder: $output_folder" + mkdir $output_folder + + # move to the repository folder + export PATH_TO_POSYDON=$GITHUB_WORKSPACE/POSYDON + cd $PATH_TO_POSYDON + + # REQUIRED FOR OLDER VERSIONS OF POSYDON BEFORE THE INCLUSION OF + # THe _templatse folder and versioning code. + # get the _templates folder and move it out of the POSYDON folder + cp -r $PATH_TO_POSYDON/docs/_source/_templates $GITHUB_WORKSPACE + + # get the injection code from the release + injection_code=$(awk '/# INJECTION GRAB START/,/# INJECTION GRAB END/' "${PATH_TO_POSYDON}/docs/_source/conf.py") + + # get the available tags + TAGS=$(git tag -l | sort -V | grep "v1") + echo "v1 filtered tags:" + echo $TAGS + + # build the documentation for each tag + for tag in $TAGS + do + echo "Checking out tag: $tag" + git checkout -f $tag + git clean -fdx + python -m pip install .[doc,vis] + + # copy the _templates folder to the docs folder + cp -r $GITHUB_WORKSPACE/_templates $PATH_TO_POSYDON/docs + # remove the injection code from the conf.py file + echo "${injection_code}" >> "${PATH_TO_POSYDON}/docs/conf.py" + + # build the documentation + cd docs + make html + touch _build/html/.nojekyll + + # move and copy docs out of the POSYDON folder + mv _build/html/ $output_folder/$tag + + # cleanup for the next build + make clean + cd ../ + done + + - name: Deploy to GitHub Pages + if: success() + uses: crazy-max/ghaction-github-pages@v2 + with: + target_branch: gh-pages + build_dir: _build/ + keep_history: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build_v2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-tags: true + path: 'POSYDON' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Get pip cache dir + id: pip-cache + run: | + python -m pip install --upgrade pip + echo "::set-output name=dir::$(pip cache dir)" + + - name: Load pip cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py', '**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + # install pandoc for the documentation + sudo apt-get update -y + sudo apt-get install gfortran swig libhdf5-serial-dev libmpich-dev -y + sudo apt-get install pandoc -y + + # install the dependencies in python + python -m pip install pandoc + python -m pip install coverage cpp-coveralls flake8 pytest + + - name: Make Documentation + env: + PATH_TO_POSYDON: ./ + PATH_TO_POSYDON_DATA: ./ + MESA_DIR: ./ + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # make the output folder + output_folder=$GITHUB_WORKSPACE/_build/ + echo "Output folder: $output_folder" + mkdir $output_folder + + # move to the repository folder + export PATH_TO_POSYDON=$GITHUB_WORKSPACE/POSYDON + cd $PATH_TO_POSYDON + + # REQUIRED FOR OLDER VERSIONS OF POSYDON BEFORE THE INCLUSION OF + # THe _templates folder and versioning code. + # get the _templates folder and move it out of the POSYDON folder + cp -r $PATH_TO_POSYDON/docs/_source/_templates $GITHUB_WORKSPACE + + # get the injection code from the release + injection_code=$(awk '/# INJECTION GRAB START/,/# INJECTION GRAB END/' "${PATH_TO_POSYDON}/docs/_source/conf.py") + + # get the available tags + TAGS=$(git tag -l | sort -V | grep "v2" | grep -v "dev") + + echo "v2 filtered tags:" + echo $TAGS + + # build the documentation for each tag + for tag in $TAGS + do + echo "Checking out tag: $tag" + git checkout -f $tag + git clean -fdx + python -m pip install .[doc,vis] + + # copy the _templates folder and inject code only for specific tags + if [[ "$tag" == "v2.0.0-pre1" || "$tag" == "v2.0.0-pre2" ]]; then + # copy the _templates folder to the docs folder + cp -r $GITHUB_WORKSPACE/_templates $PATH_TO_POSYDON/docs + # remove the injection code from the conf.py file + echo "${injection_code}" >> "${PATH_TO_POSYDON}/docs/conf.py" + fi + + # build the documentation + cd docs + make html + touch _build/html/.nojekyll + + # move and copy docs out of the POSYDON folder + mv _build/html/ $output_folder/$tag + + # cleanup for the next build + make clean + cd ../ + done + + - name: Deploy to GitHub Pages + if: success() + uses: crazy-max/ghaction-github-pages@v4 + with: + target_branch: gh-pages + build_dir: _build/ + keep_history: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_WORKSPACE: ${{ github.workspace }} + + link_latest: + needs: [build_v1, build_v2] + runs-on: ubuntu-latest + steps: + - name: Download gh-pages + uses: actions/checkout@v4 + with: + ref: gh-pages + path: '_build' + + - name: Find latest version and create symlink + run: | + cd _build + + # List all version directories + echo "All directories:" + ls -la + + # Find all version directories that match our version pattern + VERSIONS=$(ls -d v[0-9]* 2>/dev/null || echo "") + + if [ -z "$VERSIONS" ]; then + echo "No version directories found!" + exit 1 + fi + + echo "Found versions: $VERSIONS" + + # First try to find the overall latest + LATEST=$(echo "$VERSIONS" | sort -V | tail -n 1) + + # Second try to find the latest non-pre-release version + LATESTNOPRE=$(echo "$VERSIONS" | grep -v "pre" | sort -V | tail -n 1) + + # Third get the main version part of $LATEST + LATESTWITHOUTPRE=$(echo "$LATEST" | awk -F "-pre" '{print $1}') + + # If there is a version release after the last pre-release version, use the non pre-release + if [[ "$LATESTNOPRE" == "$LATESTWITHOUTPRE" ]]; then + LATEST=$LATESTNOPRE + fi + + echo "Latest version: $LATEST" + + # Remove existing latest symlink or directory if it exists + if [ -e "latest" ]; then + rm -rf latest + fi + + # Create symlink to the latest version + ln -s "$LATEST" latest + + # Verify the symlink + ls -la latest + + - name: Deploy to GitHub Pages + if: success() + uses: crazy-max/ghaction-github-pages@v4 + with: + target_branch: gh-pages + build_dir: _build/ + keep_history: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy-github-pages-unstable.yml b/.github/workflows/deploy-github-pages-unstable.yml deleted file mode 100644 index fcdbdb6f76..0000000000 --- a/.github/workflows/deploy-github-pages-unstable.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Website Deploy (Unstable) - -on: - push: - branches: - - development - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: [3.11] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Get pip cache dir - id: pip-cache - run: | - python -m pip install --upgrade pip - echo "::set-output name=dir::$(pip cache dir)" - - name: pip cache - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py', '**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install gfortran swig libhdf5-serial-dev libmpich-dev pandoc - python -m pip install coverage cpp-coveralls flake8 pytest - python -m pip install .[doc] - python -m pip install .[ml] - - name: Make dependencies - env: - PATH_TO_POSYDON: /home/runner/work/POSYDON/POSYDON/ - run: | - cd docs && make html; cd ../ - touch docs/_build/html/.nojekyll - - name: Deploy to GitHub Pages - if: success() - uses: crazy-max/ghaction-github-pages@v2 - with: - target_branch: gh-pages-dev - build_dir: docs/_build/html/ - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy-github-pages.yml b/.github/workflows/deploy-github-pages.yml deleted file mode 100644 index b962ff1af3..0000000000 --- a/.github/workflows/deploy-github-pages.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Website Deploy - -on: - push: - branches: - - development - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: [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: Get pip cache dir - id: pip-cache - run: | - python -m pip install --upgrade pip - echo "::set-output name=dir::$(pip cache dir)" - - name: pip cache - uses: actions/cache@v4 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py', '**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install gfortran swig libhdf5-serial-dev libmpich-dev - sudo apt-get install pandoc - python -m pip install pandoc - python -m pip install coverage cpp-coveralls flake8 pytest - python -m pip install .[doc] - - name: Make dependencies - env: - PATH_TO_POSYDON: /home/runner/work/POSYDON/POSYDON/ - run: | - cd docs && make html; cd ../ - touch docs/_build/html/.nojekyll - - name: Deploy to GitHub Pages - if: success() - uses: crazy-max/ghaction-github-pages@v2 - with: - target_branch: gh-pages - build_dir: docs/_build/html/ - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/install_extras.yml b/.github/workflows/install_extras.yml index 4877e8fc19..1a0dd3884f 100644 --- a/.github/workflows/install_extras.yml +++ b/.github/workflows/install_extras.yml @@ -7,12 +7,15 @@ on: jobs: test: runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash -el {0} strategy: matrix: os: [macos-13, macos-latest, ubuntu-latest] python-version: ['3.11'] # can add more if we want to support - + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -23,15 +26,13 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install conda (via Miniconda) - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: auto-activate-base: true - name: Install POSYDON with extras run: | - conda create -n test-env python=3.11 mpi4py -y - source $(conda info --base)/etc/profile.d/conda.sh - conda activate test-env + conda create -n test-env python=3.11 mpi4py conda-forge::qt -y python -m pip install --upgrade pip + conda activate test-env pip install ".[doc,vis,ml,hpc]" - shell: bash diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 1ca915d0d0..869d175c52 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -12,5 +12,6 @@ Zepei Xing Juanga Serra Perez Philipp Moura Srivastava Tassos Fragos -Ying Qin < +Ying Qin Aaron Dotter +Max Briel \ No newline at end of file diff --git a/README.md b/README.md index 57f8200f05..9519857d4e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # POSYDON -POSYDON is a next-generation single- and binary-star population synthesis framework, incorporating, fully self-consistent, state-of-the-art stellar structure and evolution modelling, using the [MESA](https://docs.mesastar.org) code, throughout the evolution of both binary components. This allows for a more accurate treatment of physical processes in stellar and binary evolution, including: realistic mass-transfer calculations and assessment of stability, internal angular-momentum transport and tides, stellar core sizes, mass-transfer rates, and orbital periods. The code is modular in many aspects and the user can specify initial population properties and adopt choices that determine how the evolution of a binary proceeds. Machine-learning methods are incorporated and applied on the grids of detailes single- and binary-star models for various classification and interpolation calculations, and the development of irregular grids guided by active learning, for computational efficiency. +[POSYDON](https://posydon.org) is a next-generation single- and binary-star population synthesis framework, incorporating fully self-consistent, state-of-the-art stellar structure and evolution modelling using the [MESA](https://docs.mesastar.org) code, throughout the evolution of both binary components. This allows for a more accurate treatment of physical processes in stellar and binary evolution, including: realistic mass-transfer calculations and assessment of stability, internal angular momentum transport and tides, stellar core sizes, mass-transfer rates, and orbital periods. The code is modular in many aspects and the user can specify initial population properties and adopt choices that determine how the evolution of a binary proceeds. Machine-learning methods are incorporated and applied on the grids of detailed single- and binary-star models for various classification and interpolation calculations, and the development of irregular grids guided by active learning, for computational efficiency. POSYDON is being developed by a collaborative team of astrophysicists and computer scientists led by Principal Investigators Tassos Fragos (Université de Genève) and Vicky Kalogera (Northwestern University). -In [Fragos et al. (2023)](https://ui.adsabs.harvard.edu/abs/2023ApJS..264...45F/abstract) and [Andrews et al. (2024)](https://ui.adsabs.harvard.edu/abs/2024arXiv241102376A/abstract), we describe the detailed methodology and implementation of POSYDON, including the assumed physics of stellar and binary evolution, the extensive grids of detailed single- and binary-star models, the postprocessing, classification, and interpolation methods we developed for use with the grids, and the treatment of evolutionary phases that are not based on precalculated grids. +In [Fragos et al. (2023)](https://ui.adsabs.harvard.edu/abs/2023ApJS..264...45F/abstract) and [Andrews et al. (2024)](https://ui.adsabs.harvard.edu/abs/2024arXiv241102376A/abstract), we describe the detailed methodology and implementation of POSYDON, including the assumed physics of stellar and binary evolution, the extensive grids of detailed single- and binary-star models, the postprocessing, classification, and interpolation methods we developed for use with the grids, and the treatment of evolutionary phases that are not based on precalculated grids (such as common envelope evolution). - -### For instruction on how to install and use POSYDON [see here](https://posydon.org/docs/index.html). +### For instructions on how to install and use POSYDON [see here](https://posydon.org/POSYDON/latest/index.html). diff --git a/bin/posydon-popsyn b/bin/posydon-popsyn index d8d199b136..5a7032cff7 100644 --- a/bin/posydon-popsyn +++ b/bin/posydon-popsyn @@ -21,7 +21,7 @@ from posydon.config import PATH_TO_POSYDON, PATH_TO_POSYDON_DATA from posydon.popsyn.synthetic_population import Population from posydon.utils.common_functions import convert_metallicity_to_string from posydon.utils.posydonwarning import Pwarn -from posydon.grids.MODELS import get_MODEL_NAME +from posydon.grids.SN_MODELS import get_SN_MODEL_NAME import glob import numpy as np @@ -198,18 +198,20 @@ srun python ./merge_metallicity.py {metallicity} with open(filename, mode='w') as file: file.write(text) -def check_SN_MODEL_validity(ini_file): - '''Checks if the step_SN MODEL is valid for this script +def check_SN_MODEL_validity(ini_file, verbose_on_fail=True): + '''Checks if the step_SN model is valid for this script Parameters ---------- ini_file : str the path to the ini file + verbose_on_fail : bool (default: True) + if `True` rerun the model selection with verbose mode if this failed Returns ------- bool - True if the MODEL is valid or use_interp_values=False, False otherwise + True if the model is valid or use_interp_values=False, False otherwise ''' simprop_kwargs = simprop_kwargs_from_ini(ini_file) @@ -218,9 +220,11 @@ def check_SN_MODEL_validity(ini_file): if step_SN_MODEL['use_interp_values'] == False: return True # step_SN MODEL check - MODEL_NAME_SEL = get_MODEL_NAME(step_SN_MODEL) + SN_MODEL_NAME_SEL = get_SN_MODEL_NAME(step_SN_MODEL) - if MODEL_NAME_SEL is None: + if SN_MODEL_NAME_SEL is None: + if verbose_on_fail: + get_SN_MODEL_NAME(step_SN_MODEL, verbose=True) return False else: return True @@ -237,8 +241,8 @@ def setup_popsyn_function(args): raise FileNotFoundError(f'File {args.ini_file} not found') if not check_SN_MODEL_validity(args.ini_file): - raise ValueError("The step_SN MODEL is not valid for this script." - "Please check the MODEL in the ini file") + raise ValueError("The step_SN MODEL is not valid for this script. " + "Please check the MODEL in the ini file") synpop_params = binarypop_kwargs_from_ini(args.ini_file) metallicities = synpop_params['metallicity'] @@ -319,8 +323,7 @@ def check_population_files(run_folder, metallicities): dict Dictionary with metallicity as key and existence status as value """ - print("POPULATION FILES CHECK ....................") - print("") + print("POPULATION FILES CHECK ....................\n") print("MET\t\tPOP_FILE") all_exist = True @@ -365,8 +368,7 @@ def check_binary_counts(run_folder, metallicities, expected_count): dict Dictionary with metallicity as key and binary count as value """ - print("BINARY COUNT CHECK ....................") - print("") + print("BINARY COUNT CHECK ....................\n") print("MET\t\tEXPECTED\tFOUND\tSTATUS") all_match = True @@ -507,7 +509,7 @@ def check_batches(run_folder, metallicity, batch_folder_name): idx = int(input("\nEnter the index to the job ID: ")) if 0 <= idx < len(jobIDs): selected_job_idx = idx - jobID = [jobIDs[idx]] # Filter to only the selected job ID + jobID = jobIDs[idx] # Filter to only the selected job ID else: print("Invalid selection. Please try again.") except ValueError: @@ -631,17 +633,22 @@ def generate_rescue_script(args, batch_status): elif line.startswith("export PATH_TO_POSYDON_DATA="): path_to_posydon_data = line.split('=')[1].strip() - # create a new slurm script - rescue_script = os.path.join(run_folder, f'{str_met}_Zsun_rescue.slurm') - if os.path.exists(rescue_script): - Pwarn('Replace '+rescue_script, "OverwriteWarning") - # Extract missing indices from batch_status missing_indices = batch_status.get('missing_indices', []) # Format job array string for SBATCH if missing_indices: job_array_str = ','.join(map(str, sorted(missing_indices))) + else: + raise ValueError("No missing indices found in batch status.\n" + "Cannot generate rescue script.\n" + "Please run the check function to resubmit the merge jobs.\n\n" + "posydon-popsyn check \n") + + # create a new slurm script + rescue_script = os.path.join(run_folder, f'{str_met}_Zsun_rescue.slurm') + if os.path.exists(rescue_script): + Pwarn('Replace '+rescue_script, "OverwriteWarning") with open(rescue_script, 'w') as f: f.write(f'''#!/bin/bash @@ -697,8 +704,7 @@ def get_ini_file(args): # Handle multiple INI files if len(ini_files) > 1: - print("Multiple INI files found:") - print("") + print("Multiple INI files found:\n") for idx, file in enumerate(ini_files): print(f"{idx}: {file}") @@ -752,20 +758,17 @@ def check_popsyn_function(args): args : argparse.Namespace The arguments passed to the function """ - print(f"Checking the status of the population run in {args.run_folder}") - print("") + print(f"Checking the status of the population run in {args.run_folder}\n") ini_file = get_ini_file(args) - print("") - print(f"Using INI file:\n{ini_file}") + print(f"\nUsing INI file:\n{ini_file}") num_metallicities, number_of_binaries,\ metallicities, synpop_params= get_binary_params(ini_file) print("REQUESTED PARAMETERS") print(f"# metallicities: {num_metallicities}") - print(f"# binaries: {number_of_binaries}") - print("") + print(f"# binaries: {number_of_binaries}\n") print("Checking the status of the population run") print("-"*80) @@ -817,23 +820,21 @@ def check_popsyn_function(args): str_met = convert_metallicity_to_string(met) os.system( - f'sbatch{args.run_folder}/{str_met}_Zsun_merge_popsyn.slurm' + f'sbatch {args.run_folder}/{str_met}_Zsun_merge_popsyn.slurm' ) print("Merge jobs resubmitted.") else: print("Merge jobs not resubmitted.") else: - print("\nOne or more batch files are incomplete.") - print("We need to resubmit some of the batch jobs.") - print("Please use the following command to create and" - "submit a rescue script:") - print("") - print(f"posydon-popsyn rescue {args.run_folder}") - print("") - print("You can add additional arguments to the command, such as " - + "--email, --partition, etc.") - print("This will generate resubmission scripts that you can review " + print("\nOne or more batch files are incomplete.\n" + "We need to resubmit some of the batch jobs.\n" + "Please use the following command to create and\n" + "submit a rescue script:\n\n" + f"posydon-popsyn rescue {args.run_folder}\n\n" + "You can add additional arguments to the command, such as \n" + "--email, --partition, etc.\n" + "This will generate resubmission scripts that you can review\n" + "and run to resubmit failed jobs.") def rescue_popsyn_function(args): @@ -849,16 +850,14 @@ def rescue_popsyn_function(args): # 1. Check status of the run ini_file = get_ini_file(args) - print("") - print(f"Using INI file:\n{ini_file}") + print(f"\nUsing INI file:\n{ini_file}") num_metallicities, number_of_binaries,\ metallicities, synpop_params= get_binary_params(ini_file) print("REQUESTED PARAMETERS") print(f"# metallicities: {num_metallicities}") - print(f"# binaries: {number_of_binaries}") - print("") + print(f"# binaries: {number_of_binaries}\n") print("Checking the status of the population run") print("-"*80) @@ -896,8 +895,7 @@ def rescue_popsyn_function(args): ) print("#"*80) - print("") - print("GENERATING A RESCUE SCRIPT ....................") + print("\nGENERATING A RESCUE SCRIPT ....................") # Generate a rescue script rescue_scripts.append( @@ -907,8 +905,7 @@ def rescue_popsyn_function(args): print("-"*80) # Generate a resubmit_slurm.sh script - print("GENERATING A RESUBMIT SCRIPT ....................") - print("") + print("GENERATING A RESUBMIT SCRIPT ....................\n") resubmit_sh_file = os.path.join(args.run_folder, 'resubmit_slurm.sh') with open(resubmit_sh_file, 'w') as f: f.write("#!/bin/bash\n") @@ -916,14 +913,14 @@ def rescue_popsyn_function(args): script = os.path.basename(script) met = script.split('_')[0] f.write(f'resubmit=$(sbatch --parsable {script})\n') - f.write(f"echo 'Rescue script submitted as '${{resubmit}}\n") + f.write("echo 'Rescue script submitted as '${resubmit}\n") f.write("merge=$(sbatch --parsable " - "--dependency=afterok:${{resubmit}} " + "--dependency=afterok:${resubmit} " "--kill-on-invalid-dep=yes " f"{met}_Zsun_merge_popsyn.slurm)\n") - f.write(f"echo 'Merge job submitted as '${{merge}}\n") + f.write("echo 'Merge job submitted as '${merge}\n") - print(f"Rescue scripts ready to be resubmitted by {resubmit_sh_file}") + print(f"Rescue scripts ready to be resubmitted by 'sh {resubmit_sh_file}'") print("Would you like to submit the rescue scripts?") choice = input("Enter 'yes' or 'y' to submit the rescue scripts: ") if choice.lower() == 'yes' or choice.lower() == 'y': diff --git a/bin/posydon-run-pipeline b/bin/posydon-run-pipeline index 2ac7f9ff68..4111b3367d 100644 --- a/bin/posydon-run-pipeline +++ b/bin/posydon-run-pipeline @@ -54,6 +54,7 @@ STEP RERUN: rerun grid with a fix grid_low_res_combined.rerun(index=logic) --> grid_low_res_rerun_1/grid.csv --> grid_random_1_rerun_1/grid.csv -- run grid fix and do next post processing + ''' __authors__ = [ @@ -79,7 +80,7 @@ from posydon.grids.psygrid import (PSyGrid, EXTRA_COLS_DS_EXCLUDE) from posydon.grids.post_processing import (post_process_grid, add_post_processed_quantities) -from posydon.grids.MODELS import MODELS +from posydon.grids.SN_MODELS import SN_MODELS from posydon.config import PATH_TO_POSYDON from posydon.utils.gridutils import get_new_grid_name from posydon.utils.posydonwarning import (Pwarn,print_stats) @@ -373,21 +374,21 @@ def train_interpolators(i, path_to_csv_file, verbose=False): key != "model_number" and (type(grid.final_values[key][0]) != np.str_) and any(~np.isnan(grid.final_values[key])) - and "MODEL" not in key)] + and "SN_MODEL" not in key)] out_nan_keys = [key for key in grid.final_values.dtype.names if ( key != "model_number" and (type(grid.final_values[key][0]) != np.str_) and all(np.isnan(grid.final_values[key])) - and "MODEL" not in key)] + and "SN_MODEL" not in key)] # define all string keys in final values c_keys = ['interpolation_class', 'S1_state', 'S2_state', 'mt_history'] - for MODEL_NAME in MODELS.keys(): + for SN_MODEL_NAME in SN_MODELS.keys(): for i in range(1,3): - c_keys.append(f'S{i}_{MODEL_NAME}_SN_type') - c_keys.append(f'S{i}_{MODEL_NAME}_CO_type') + c_keys.append(f'S{i}_{SN_MODEL_NAME}_SN_type') + c_keys.append(f'S{i}_{SN_MODEL_NAME}_CO_type') # specify the interpolation methods interpolators = [ @@ -407,25 +408,25 @@ def train_interpolators(i, path_to_csv_file, verbose=False): # TODO: we need to train core collapse for secondary star as well # to catch reverse mass transfer cases where the secondary undergoes # core collapse first - for MODEL_NAME in MODELS.keys(): + for SN_MODEL_NAME in SN_MODELS.keys(): for i in range(1,2): #TODO: range(1,3): out_keys = [key for key in grid.final_values.dtype.names if ( key != "model_number" and (type(grid.final_values[key][0]) != np.str_) and any(~np.isnan(grid.final_values[key])) - and f"S{i}_{MODEL_NAME}" in key)] + and f"S{i}_{SN_MODEL_NAME}" in key)] out_nan_keys = [key for key in grid.final_values.dtype.names if ( key != "model_number" and (type(grid.final_values[key][0]) != np.str_) and all(np.isnan(grid.final_values[key])) - and f"S{i}_{MODEL_NAME}" in key)] + and f"S{i}_{SN_MODEL_NAME}" in key)] # get interpolations classes dynamically interp_method = [] interp_classes = [] for CO_interpolation_class in ["BH", "NS", "WD", "BH_reverse_MT"]: - if CO_interpolation_class in grid.final_values[f'S{i}_{MODEL_NAME}_CO_interpolation_class']: + if CO_interpolation_class in grid.final_values[f'S{i}_{SN_MODEL_NAME}_CO_interpolation_class']: interp_method.append(method) interp_classes.append(CO_interpolation_class) @@ -436,10 +437,10 @@ def train_interpolators(i, path_to_csv_file, verbose=False): "out_keys": out_keys, "out_nan_keys": out_nan_keys, "class_method": "kNN", - "c_keys": [f'S{i}_{MODEL_NAME}_CO_interpolation_class', - f'S{i}_{MODEL_NAME}_CO_type', - f'S{i}_{MODEL_NAME}_SN_type'], - "c_key": f'S{i}_{MODEL_NAME}_CO_interpolation_class' + "c_keys": [f'S{i}_{SN_MODEL_NAME}_CO_interpolation_class', + f'S{i}_{SN_MODEL_NAME}_CO_type', + f'S{i}_{SN_MODEL_NAME}_SN_type'], + "c_key": f'S{i}_{SN_MODEL_NAME}_CO_interpolation_class' }, ) @@ -1170,12 +1171,20 @@ def plot_grid(i, path_to_csv_file, verbose=False): else: Pwarn(f'{set_name} not in PRE_SET_PLOTS', "UnsupportedModelWarning") - elif '_MODEL' in quantity_to_plot: - short_quantity = quantity_to_plot.split('_MODEL')[-1] - short_quantity = short_quantity[short_quantity.find('_')+1:] + elif '_SN_MODEL' in quantity_to_plot: + short_quantity = "" + for SN_MODEL_NAME in SN_MODELS.keys(): + if SN_MODEL_NAME+'_' in quantity_to_plot: + short_quantity = quantity_to_plot.split( + SN_MODEL_NAME+'_')[-1] + if short_quantity == "": + Pwarn(f'{quantity_to_plot} not recognized use ' + 'SN_MODEL_DEFAULT_mass instead', + "ReplaceValueWarning") + short_quantity = "mass" model_name = quantity_to_plot.split('_'+short_quantity)[0] - set_name = quantity_to_plot.split('_MODEL')[0] +\ - '_MODEL_DEFAULT_' + short_quantity + set_name = quantity_to_plot.split('_SN_MODEL')[0] +\ + '_SN_MODEL_DEFAULT_' + short_quantity if set_name in PRE_SET_PLOTS: plot_attributes['plot_dir_name'] = os.path.join(model_name, short_quantity) @@ -1394,12 +1403,12 @@ def do_check(i, path_to_csv_file, verbose=False): print(f'Failure rate: {failure_rate_in_percent}% in {grid_path}') elif check_to_do=='CO_type': for quantity in grid.final_values.dtype.names: - if (('S1_MODEL' in quantity) and ('CO_type' in quantity)): + if (('S1_SN_MODEL' in quantity) and ('CO_type' in quantity)): count = Counter(grid.final_values[quantity]) print(f'{quantity}: {count} in {grid_path}') elif check_to_do=='SN_type': for quantity in grid.final_values.dtype.names: - if (('S1_MODEL' in quantity) and ('SN_type' in quantity)): + if (('S1_SN_MODEL' in quantity) and ('SN_type' in quantity)): count = Counter(grid.final_values[quantity]) print(f'{quantity}: {count} in {grid_path}') #TODO: add more checks diff --git a/bin/posydon-setup-pipeline b/bin/posydon-setup-pipeline index 5b16352656..8aa9c1e341 100644 --- a/bin/posydon-setup-pipeline +++ b/bin/posydon-setup-pipeline @@ -19,6 +19,7 @@ from posydon.popsyn.io import parse_inifile from posydon.config import PATH_TO_POSYDON from posydon.utils.gridutils import get_new_grid_name from posydon.utils.posydonwarning import Pwarn +from posydon.grids.SN_MODELS import SN_MODELS # this data processing pipeline was designed assuming POSYDON v2 data structure ''' @@ -71,36 +72,7 @@ PLOTTING_SETS = { 'termination_flag_2', 'termination_flag_3',\ 'termination_flag_4', 'rl_relative_overflow_1',\ 'rl_relative_overflow_2', 'lg_mtransfer_rate'], - 'PLOT_AFTER_EXTRA' : ['S1_MODEL01_CO_type', 'S1_MODEL01_SN_type',\ - 'S1_MODEL01_mass', 'S1_MODEL01_spin',\ - 'S1_MODEL01_m_disk_radiated', - 'S1_MODEL02_CO_type', 'S1_MODEL02_SN_type',\ - 'S1_MODEL02_mass', 'S1_MODEL02_spin',\ - 'S1_MODEL02_m_disk_radiated', - 'S1_MODEL03_CO_type', 'S1_MODEL03_SN_type',\ - 'S1_MODEL03_mass', 'S1_MODEL03_spin',\ - 'S1_MODEL03_m_disk_radiated', - 'S1_MODEL04_CO_type', 'S1_MODEL04_SN_type',\ - 'S1_MODEL04_mass', 'S1_MODEL04_spin',\ - 'S1_MODEL04_m_disk_radiated', - 'S1_MODEL05_CO_type', 'S1_MODEL05_SN_type',\ - 'S1_MODEL05_mass', 'S1_MODEL05_spin',\ - 'S1_MODEL05_m_disk_radiated', - 'S1_MODEL06_CO_type', 'S1_MODEL06_SN_type',\ - 'S1_MODEL06_mass', 'S1_MODEL06_spin',\ - 'S1_MODEL06_m_disk_radiated', - 'S1_MODEL07_CO_type', 'S1_MODEL07_SN_type',\ - 'S1_MODEL07_mass', 'S1_MODEL07_spin',\ - 'S1_MODEL07_m_disk_radiated', - 'S1_MODEL08_CO_type', 'S1_MODEL08_SN_type',\ - 'S1_MODEL08_mass', 'S1_MODEL08_spin',\ - 'S1_MODEL08_m_disk_radiated', - 'S1_MODEL09_CO_type', 'S1_MODEL09_SN_type',\ - 'S1_MODEL09_mass', 'S1_MODEL09_spin',\ - 'S1_MODEL09_m_disk_radiated', - 'S1_MODEL10_CO_type', 'S1_MODEL10_SN_type',\ - 'S1_MODEL10_mass', 'S1_MODEL10_spin',\ - 'S1_MODEL10_m_disk_radiated'], + 'PLOT_AFTER_EXTRA' : [], 'PLOT_AFTER_TRAINING' : ['INTERP_ERROR_age', 'INTERP_ERROR_star_1_mass',\ 'INTERP_ERROR_star_2_mass',\ 'INTERP_ERROR_period_days',\ @@ -129,21 +101,17 @@ PLOTTING_SETS = { 'INTERP_ERROR_S2_log_Teff',\ 'INTERP_ERROR_S2_log_L', 'INTERP_ERROR_S2_log_R',\ 'INTERP_ERROR_S2_spin_parameter',\ - 'INTERP_ERROR_S2_lambda_CE_10cent',\ - 'INTERP_ERROR_S1_MODEL01_mass',\ - 'INTERP_ERROR_S1_MODEL01_spin',\ - 'INTERP_ERROR_S1_MODEL01_m_disk_radiated',\ - 'INTERP_ERROR_S1_MODEL05_mass',\ - 'INTERP_ERROR_S1_MODEL05_spin',\ - 'INTERP_ERROR_S1_MODEL05_m_disk_radiated',\ - 'INTERP_ERROR_S1_MODEL06_mass',\ - 'INTERP_ERROR_S1_MODEL06_spin',\ - 'INTERP_ERROR_S1_MODEL06_m_disk_radiated',\ - 'INTERP_ERROR_S1_MODEL10_mass',\ - 'INTERP_ERROR_S1_MODEL10_spin',\ - 'INTERP_ERROR_S1_MODEL10_m_disk_radiated'], + 'INTERP_ERROR_S2_lambda_CE_10cent'], 'PLOT_AFTER_PROFILE_TRAINING' : [] } +for SN_MODEL_NAME in SN_MODELS.keys(): + for quantity in ['CO_type', 'SN_type', 'mass', 'spin', 'm_disk_radiated']: + PLOTTING_SETS['PLOT_AFTER_EXTRA'].append( + f'S1_{SN_MODEL_NAME}_{quantity}') + if quantity not in ['CO_type', 'SN_type']: + PLOTTING_SETS['PLOT_AFTER_TRAINING'].append( + f'INTERP_ERROR_S1_{SN_MODEL_NAME}_{quantity}') + # predefined checking sets CHECKING_SETS = { 'CHECK_AFTER_CREATE' : [], diff --git a/docs/Makefile b/docs/Makefile index 8ad69ca4de..5a1df6356c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -227,11 +227,16 @@ pseudoxml: checkautodoc .PHONY: apidoc apidoc: -#old: sphinx-apidoc -o api/ ../posydon --no-toc --separate --no-headings --force # remove old files rm -f ./_source/api_reference/*.rst -# generate posydon reference (ignore config.py file) - sphinx-apidoc --module-first --templatedir=_source/sphinx-apidoc-templates -d 2 --force --no-toc -o ./_source/api_reference ../posydon ../posydon/config.py +# generate posydon reference (ignore config.py and tests file) + sphinx-apidoc --module-first \ + --templatedir=_source/sphinx-apidoc-templates \ + -d 2 \ + --force \ + --no-toc \ + -o ./_source/api_reference ../posydon \ + ../posydon/config.py ../posydon/unit_tests ../posydon/tests # create "_tmp/executables" directory if not existing mkdir -p _tmp/executables # copy script files from "bin" directory to "_tmp/executables", on the fly replace "-" with "_" and add extension ".py" diff --git a/docs/_source/_templates/versions.html b/docs/_source/_templates/versions.html new file mode 100644 index 0000000000..c8a013700e --- /dev/null +++ b/docs/_source/_templates/versions.html @@ -0,0 +1,24 @@ +
+ + Version: {{ current_version }} + + +
+ {% if version | length > 1 %} +
+
{{ _('v1 versions') }}
+ {% for the_version, url in v1_versions %} +
{{ the_version }}
+ {% endfor %} +
+ {% endif %} + {% if version | length > 1 %} +
+
{{ _('v2 versions') }}
+ {% for the_version, url in v2_versions %} +
{{ the_version }}
+ {% endfor %} +
+ {% endif %} +
+
\ No newline at end of file diff --git a/docs/_source/components-overview/machine-learning-components.rst b/docs/_source/components-overview/machine-learning-components.rst index ed945cbe91..b91a497259 100644 --- a/docs/_source/components-overview/machine-learning-components.rst +++ b/docs/_source/components-overview/machine-learning-components.rst @@ -21,7 +21,7 @@ Profile Interpolation .. warning:: - This feature is experimental and may not be suited for scientific use. If + This feature is experimental and may not be suitable for scientific use. If you want to use this feature, please install the relevant dependencies with `pip install -e .[ml]`. diff --git a/docs/_source/components-overview/machine_learning/initial_final_interp.rst b/docs/_source/components-overview/machine_learning/initial_final_interp.rst index dd63a3c0d0..5e6c8b6dba 100644 --- a/docs/_source/components-overview/machine_learning/initial_final_interp.rst +++ b/docs/_source/components-overview/machine_learning/initial_final_interp.rst @@ -1,8 +1,8 @@ .. _initial-final-interp: -########################################### +############################################ Initial-Final Classification & Interpolation -########################################### +############################################ We showcase the initial-final interpolator which plays a critical role in the evolving binary populations. To use the initial-final interpolator we first diff --git a/docs/_source/components-overview/mesa-grids.rst b/docs/_source/components-overview/mesa-grids.rst index 40fc7c6f11..e7714a84d7 100644 --- a/docs/_source/components-overview/mesa-grids.rst +++ b/docs/_source/components-overview/mesa-grids.rst @@ -27,8 +27,8 @@ Learn how to run dynamic type grids using the POSYDON API. TODO mesa_grids/dynamic -Ini file documetation -~~~~~~~~~~~~~~~~~~~~~ +Ini file documentation +~~~~~~~~~~~~~~~~~~~~~~ Learn more about the MESA ini file content and how to use it in your own code. diff --git a/docs/_source/components-overview/mesa_grids/dynamic.rst b/docs/_source/components-overview/mesa_grids/dynamic.rst index d22140f8c8..44d9b27a86 100644 --- a/docs/_source/components-overview/mesa_grids/dynamic.rst +++ b/docs/_source/components-overview/mesa_grids/dynamic.rst @@ -6,8 +6,9 @@ Run a dynamic MESA grid If you are interested in using a dynamic MESA grid as described in :cite:`Rocha et al. 2022 `, please reach out to us via the `POSYDON mailing list `_. -.. role:: cite +.. role::`cite`` + +#### References -#### References .. [Rocha2022] Rocha et al. 2022, *The Astrophysical Journal*, 938, 64. [`DOI:10.3847/1538-4357/ac8b05`](https://doi.org/10.3847/1538-4357/ac8b05). diff --git a/docs/_source/components-overview/pop_syn/binary_star.rst b/docs/_source/components-overview/pop_syn/binary_star.rst index 17eea7a2ae..a45e975907 100644 --- a/docs/_source/components-overview/pop_syn/binary_star.rst +++ b/docs/_source/components-overview/pop_syn/binary_star.rst @@ -48,7 +48,7 @@ The binary properties are defined as follows * - ``eccentricity`` - Orbital eccentricity. * - ``V_sys`` - - Velocity of the centre of mass of the binary [Vx, Vy, Vz] in km/s. + - Velocity of the center of mass of the binary [Vx, Vy, Vz] in km/s. * - ``mass_transfer_case`` - Mass transfer case, see MT case options. * - ``lg_mtransfer_rate`` @@ -172,25 +172,25 @@ Binary events are defined according to the following table: * - ``oCE2`` - The binary is at the onset of Common Envelope initiated by star 2. * - ``oDoubleCE1`` - - The binary is at the onset of Double Common Envelope initiated by star 1. - Both stars are post main-sequence. + - | The binary is at the onset of Double Common Envelope initiated by star 1. + | Both stars are post main-sequence. * - ``oDoubleCE2`` - - The binary is at the onset of Double Common Envelope initiated by star 2. - Both stars are post main-sequence. + - | The binary is at the onset of Double Common Envelope initiated by star 2. + | Both stars are post main-sequence. * - ``CO_contact`` - The binary reached contact in the compact object phase. * - ``redirect_from_ZAMS`` - - The binary was redirected from ZAMS for a variety of reasons. - - Only recorded if history_verbose = True + - | The binary was redirected from ZAMS for a variety of reasons. + | Only recorded if history_verbose = True * - ``redirect_from_CO_HMS_RLO`` - - The binary was redirected from CO_HMS_RLO for a variety of reasons. - - Only recorded if history_verbose = True + - | The binary was redirected from CO_HMS_RLO for a variety of reasons. + | Only recorded if history_verbose = True * - ``redirect_from_CO_HeMS`` - - The binary was redirected from CO_HeMS for a variety of reasons. - - Only recorded if history_verbose = True + - | The binary was redirected from CO_HeMS for a variety of reasons. + | Only recorded if history_verbose = True * - ``redirect_from_CO_HeMS_RLO`` - - The binary was redirected from CO_HeMS_RLO for a variety of reasons. - - Only recorded if history_verbose = True + - | The binary was redirected from CO_HeMS_RLO for a variety of reasons. + | Only recorded if `history_verbose = True` * - ``MaxTime_exceeded`` - The maximum time of the evolution was exceeded. * - ``maxtime`` diff --git a/docs/_source/components-overview/pop_syn/flow_chart.rst b/docs/_source/components-overview/pop_syn/flow_chart.rst index add2b93c34..4dcf3e62cc 100644 --- a/docs/_source/components-overview/pop_syn/flow_chart.rst +++ b/docs/_source/components-overview/pop_syn/flow_chart.rst @@ -3,6 +3,6 @@ The Flow Chart Object --------------------- -Along with the BinaryStar object the flow chart object is the most important building block of POSYDON, such that it is featured in the center of the POSYDON logo. The flow chart object maps the evolution of a BinaryStar object to its corresponing evolutionary step. +Along with the BinaryStar object the flow chart object is the most important building block of POSYDON, such that it is featured in the center of the POSYDON logo. The flow chart object maps the evolution of a BinaryStar object to its corresponding evolutionary step. TODO: continue the description of the flow chart object \ No newline at end of file diff --git a/docs/_source/components-overview/pop_syn/population_params.rst b/docs/_source/components-overview/pop_syn/population_params.rst index 01204d3e0e..9aa22bfb3e 100644 --- a/docs/_source/components-overview/pop_syn/population_params.rst +++ b/docs/_source/components-overview/pop_syn/population_params.rst @@ -1,7 +1,7 @@ .. _pop-params-guide: ================================================ -POSDYON Population Synthesis Configuration Guide +POSYDON Population Synthesis Configuration Guide ================================================ This documentation provides a detailed overview of the configuration options available in the Posydon software package. @@ -47,7 +47,7 @@ Note that the loading of the simulation steps is separate from creating the obje from posydon.popsyn.io import simprop_kwargs_from_ini # read from file - sim_props = simprop_kwargs_from_ini('population_params.ini', vebose=False) + sim_props = simprop_kwargs_from_ini('population_params.ini', verbose=False) # create SimulationProperties object sim = SimulationProperties(**sim_props) @@ -66,7 +66,6 @@ It controls the mapping between a POSYDON binary object and its step evolution, :widths: 10 80 10 :class: population-params-table :header-rows: 1 - * - Parameter - Description @@ -237,7 +236,7 @@ It evolves the binary object in isolation until Roche lobe overflow occurs. Step Disrupted ~~~~~~~~~~~~~~ -The dirtupted step evolves a system when a supernova has unbound the binary components. +The disrupted step evolves a system when a supernova has unbound the binary components. This class inherits from the detached step, but only evolves the remaining star in isolation. This means that this step uses the single stars loaded by the detached step. @@ -361,7 +360,7 @@ If the energy budget is less than the binding energy, the system is merged. * ``0.01`` * ``0.1`` * ``0.3`` - - ``0.1`` + - ``0.3`` * - ``core_definition_He_fraction`` - | The helium fraction for defining the core. @@ -374,11 +373,10 @@ If the energy budget is less than the binding energy, the system is merged. * - ``common_envelope_option_after_succ_CEE`` - | The option for handling the system after a successful common envelope ejection. - * ``'core_not_replaced_noMT'`` : core not replaced, no mass transfer - * ``'core_replaced_noMT'`` : core replaced, no mass transfer - * ``'core_not_replaced_stableMT'`` : core not replaced, stable mass transfer - * ``'core_not_replaced_windloss'`` : core not replaced, wind loss - - ``'core_not_replaced_noMT'`` + * ``'one_phase_variable_core_definition'`` : lose mass till the variable core definition (see ``core_definition_H_fraction``/``core_definition_He_fraction``) with the given ``prescription``; no mass transfer (use this defined core mass as the stripped remnant mass) + * ``'two_phases_stableMT'`` : first lose mass till the variable core definition (see ``core_definition_H_fraction``/``core_definition_He_fraction``) with the given ``prescription``; second have a stable and fully non-conservative mass transfer from the core to the companion until stripped to the core defined in MESA; in the case of a double CE redirect to ``'two_phases_windloss'`` + * ``'two_phases_windloss'`` : first lose mass till the variable core definition (see ``core_definition_H_fraction``/``core_definition_He_fraction``) with the given ``prescription``; second lose the mass remaining mass above the core defined in MESA as a fast wind + - ``'two_phases_stableMT'`` * - ``verbose`` - | Enables verbose mode. @@ -428,12 +426,19 @@ The collection of trained prescriptions can be found in the ``MODELS.py`` file a * ``'Fryer+12-delayed'`` * ``'Sukhbold+16-engine'`` * ``'Patton&Sukhbold20-engine'`` - - ``'Patton&Sukhbold20-engine'`` + - ``'Fryer+12-delayed'`` * - ``engine`` - | The engine used for the supernova. | Relevant for ``'Sukhbold+16-engine'`` and ``'Patton&Sukhbold20-engine'`` mechanisms. - - ``'N20'`` + + * ``''`` + * ``'N20'`` + * ``'S19.8'`` + * ``'W15'`` + * ``'W20'`` + * ``'W18'`` + - ``''`` * - ``PISN`` - | The prescription used for pair-instability supernova. @@ -441,7 +446,7 @@ The collection of trained prescriptions can be found in the ``MODELS.py`` file a * ``None`` * ``'Marchant+19'`` * ``'Hendriks+23'`` - - ``'Marchant+19'`` + - ``'Hendriks+23'`` * - ``PISN_CO_shift`` - | The shift in CO core mass for the start of the Hendriks+23 PPI prescription @@ -449,18 +454,18 @@ The collection of trained prescriptions can be found in the ``MODELS.py`` file a * - ``PPI_extra_mass_loss`` - | Additional PPI mass loss for the Hendriks+23 prescription - - ``0.0`` + - ``-20.0`` * - ``ECSN`` - | The prescription used for electron-capture supernova. * ``'Tauris+15'`` * ``'Podsiadlowksi+04'`` - - ``'Podsiadlowksi+04'`` + - ``'Tauris+15'`` * - ``conserve_hydrogen_envelope`` - | Conserve the hydrogen envelope during the supernova. - - ``True`` + - ``False`` * - ``conserve_hydrogen_PPI`` - | Include the hydrogen envelope during the calculation of the @@ -487,6 +492,10 @@ The collection of trained prescriptions can be found in the ``MODELS.py`` file a - | Use core masses for the supernova. - ``True`` + * - ``allow_spin_None`` + - | Allow compact objects to have undetermined spin values in the lack of profile data. + - ``False`` + * - ``approx_at_he_depletion`` - | Approximate at helium depletion. - ``False`` @@ -634,7 +643,7 @@ When reading the binary population arguments from a ``population_params.ini`` fi from posydon.popsyn.io import binarypop_kwargs_from_ini # read from file - pop_params = binarypop_kwargs_from_ini('population_params.ini', vebose=False) + pop_params = binarypop_kwargs_from_ini('population_params.ini', verbose=False) # create BinaryPopulation object pop = BinaryPopulation(**pop_params) @@ -666,7 +675,7 @@ It also contains which sampling distributions to use for the initial conditions * - ``dump_rate`` - | Batch save after evolving N binaries. - - | To facilitate I/O performance, this should be at least 500 for populations of 100.000 binaries or more. + | To facilitate I/O performance, this should be at least 500 for populations of 100.000 binaries or more. - ``2000`` * - ``temp_directory`` @@ -699,7 +708,7 @@ It also contains which sampling distributions to use for the initial conditions * - ``history_verbose`` - | If True, record extra functional steps in the output DataFrames - - | (These extra steps represent internal workings of POSYDON rather than physical phases of evolution) + | (These extra steps represent internal workings of POSYDON rather than physical phases of evolution) - ``False`` * - ``entropy`` diff --git a/docs/_source/components-overview/pop_syn/single_star.rst b/docs/_source/components-overview/pop_syn/single_star.rst index 3438da4f62..4fbc516d2f 100644 --- a/docs/_source/components-overview/pop_syn/single_star.rst +++ b/docs/_source/components-overview/pop_syn/single_star.rst @@ -76,11 +76,11 @@ The star properties are defined as follows * - ``surface_o16`` - Oxygen surface mass fraction abundance. * - ``log_LH`` - - log10 total thermal power from PP and CNO, excluding neutrinos devided by L_sun. + - log10 total thermal power from PP and CNO, excluding neutrinos divided by L_sun. * - ``log_LHe`` - - log10 total thermal power from triple-alpha, excluding neutrinos devided by L_sun. + - log10 total thermal power from triple-alpha, excluding neutrinos divided by L_sun. * - ``log_LZ`` - - log10 total burning power excluding LH and LHe and photodisintegrations devided by L_sun. + - log10 total burning power excluding LH and LHe and photodisintegrations divided by L_sun. * - ``log_Lnuc`` - log10 total nuclear reaction luminosity (LH + LHe LZ) in L_sun. * - ``c12_c12`` @@ -127,7 +127,7 @@ Additional scalar properties are added during the evolution depending on which s - The spin-orbit tilt after the second SN, if a second SN has occurred. * - ``m_disk_radiated`` - The mass of the disk radiated in the collapse of the star. - * = ``m_disk_accreted`` + * - ``m_disk_accreted`` - The mass of the disk accreted in the collapse of the star. diff --git a/docs/_source/components-overview/pop_syn/synthetic_population.rst b/docs/_source/components-overview/pop_syn/synthetic_population.rst index e95430de28..5b0498f6b3 100644 --- a/docs/_source/components-overview/pop_syn/synthetic_population.rst +++ b/docs/_source/components-overview/pop_syn/synthetic_population.rst @@ -2,9 +2,12 @@ .. py:currentmodule:: posydon.popsyn.synthetic_population +Synthetic Populations +##################### -The Population Object -=============================== + +Population +========== The :class:`~Population` object is an interface for the population data, which is stored in a HDF5 file. You can find more information about the file structure here: :ref:`population-file-structure`. @@ -32,7 +35,7 @@ The :code:`formation_channels` is not immediately available, but can be created :func:`~Population.calculate_formation_channels` function of the :class:`~Population` object. -Additionally, :meth:`Population.mass_per_metallicity` contains some essential metadata calculated from the populaiton synthesis run. +Additionally, :meth:`Population.mass_per_metallicity` contains some essential metadata calculated from the population synthesis run. It is a pandas.DataFrame with the metallicity (in solar units) as the index and 3 columns: 1. :code:`count`, the number of systems at that metallicity in the file. @@ -85,11 +88,12 @@ A more powerful feature is the :func:`History.select` function, which allows you If you want to have a peak, you can use the :meth:`~Population.head` or :meth:`~Population.tail` functions. .. code-block:: python + pop.history.head(10) pop.history.tail(10) -Additional functions are made available for easy of use. +Additional functions are made available for ease of use. If you want to check the length of the history of a system, you can use :attr:`Population.history.lengths` or :attr:`Population.history_lengths`. @@ -136,6 +140,7 @@ Additionally, ``WARNING``, ``FAILED``, and metallicity columns are available in .. csv-table:: Additional columns :header: "Properties", "Descriptions" :widths: 50, 150 + ``FAILED``, Indicates if the system failed during the population synthesis run. ``WARNING``, Indicates if there were any warnings for the system during the population synthesis run. ``metallicity``, The metallicity of the system. @@ -236,8 +241,7 @@ The :class:`~TransientPopulation` class has been designed to handle these events Creating a TransientPopulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +------------------------------ The transient population is created using the :func:`Population.create_transient_population` function of the :class:`~Population` object. This function creates a separate table with each transient in the population file. @@ -247,9 +251,11 @@ The :func:`Population.create_transient_population` function takes a function as The :code:`selection_function` takes 3 arguments: :code:`history_chunk`, :code:`oneline_chunk`, and :code:`formation_channels_chunk` (optional). These chunks are cut based on a given chunksize, which is set to 1000000 by default, and are cut on system. -This means that always a complete history of a system is passed to the function by :func:`Population.create_transient_population`. +This means that a complete history of a system is always passed to the function by :func:`Population.create_transient_population`. + +:code:`selection_function` is a function you can adapt to your own needs, and +examples of building one are given in the `BBH <../../tutorials-examples/population-synthesis/bbh_analysis.html>`_ or `LGRB tutorial <../../tutorials-examples/population-synthesis/lgrb_pop_syn.html>`_. -:code:`selection_function` is a function you can adapt to your own needs, and examples of building one are given in the :ref:`tutorial-examples.population-synthesis.bbh-analysis` or :ref:`tutorial-examples.population-synthesis.lgrb_pop_syn`. .. note:: @@ -262,7 +268,7 @@ These functions are available in the :mod:`posydon.popsyn.transient_select_funcs Accessing TransientPopulation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- After loading a transient population, you keep access to the history and oneline data of the population. Now, you can access the transient data of the population using :attr:`TrannsientPopulation.population`. @@ -274,7 +280,7 @@ Now, you can access the transient data of the population using :attr:`Trannsient Calculating Efficiencies -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ With this population, you can calculate additional information, such as the efficiency over metallicity. @@ -285,11 +291,12 @@ With this population, you can calculate additional information, such as the effi :code:`channels=True` includes the formation channels in the efficiency calculation. Plotting -~~~~~~~~~~ +-------- The :class:`~TransientPopulation` contains a few plotting functions for ease. .. code-block:: python + # plots the efficiency over metallicity per channel trans_pop.plot_efficiency_over_metallicity(channel=True) @@ -333,7 +340,7 @@ It also allows the user to calculate the intrinsic rate density of the events in Creating a Rates object -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- Cosmic weights are added to the population file using the :func:`~TransientPopulation.calculate_cosmic_weights` function. This function calculates the cosmic weights of the events in the population based on the birth redshifts and the population weight. @@ -356,7 +363,7 @@ The table below shows the Default values and the supported values. Accessing rates data -~~~~~~~~~~~~~~~~~~~~~~ +-------------------- The cosmic rate data is stored in 3 different tables in the population file: @@ -385,7 +392,7 @@ The :class:`~Rates` object also contains information about the metallicity and r print(rates.edges_redshift_bins) Plotting Rates -~~~~~~~~~~~~~~~~~~~~~~ +-------------- Besides plotting the intrinsic rate, you can plot the distribution of properties of the population. You can use any property in the TransientPopulation table. @@ -396,7 +403,7 @@ You can use any property in the TransientPopulation table. Applying observational effects -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------ Although the intrinsic rate density is a useful quantity, it is not directly observable, especially for binary black holes. @@ -427,10 +434,10 @@ However, since that function requires a detection argument, it requires a wrappe Accessing Observable Population data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------ The observable population is accesed through the :code:`observable_population` attribute of the :class:`~Rates` object. -You require to know the observable_identifier to access the data, which can be accessed with :attr:`Rates.observable_population_names` +You need the observable_identifier to access the data, which can be accessed with :attr:`Rates.observable_population_names` .. code-block:: python @@ -438,10 +445,10 @@ You require to know the observable_identifier to access the data, which can be a print(rates.observable_population('design_H1L1V1')) Plotting the observable population -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------- The observable population can be plotted in the same way as the intrinsic rate density. -However, you require to define which observable population you want to plot. +However, you need to define which observable population you want to plot. .. code-block:: python @@ -463,8 +470,8 @@ If you like to overplot multiple properties, you can set ``show=False`` and manu .. _population-file-structure: -The Structure of Population Files -================================= +The Structure of Generated Population Files +########################################### The main output of a population synthesis run is a HDF5 population file. diff --git a/docs/_source/components-overview/pop_syn/user_modules.rst b/docs/_source/components-overview/pop_syn/user_modules.rst index 67f1443ba8..fefeb0aaa1 100644 --- a/docs/_source/components-overview/pop_syn/user_modules.rst +++ b/docs/_source/components-overview/pop_syn/user_modules.rst @@ -3,6 +3,6 @@ User Modules ------------ -To allow users to add code easily within the POSYDON namespace without modifying the code core we added a directory called :samp:`user_modules`. There users can put any own code and simply import it as any other function within the posydon name space. +To allow users to add code easily within the POSYDON namespace without modifying the code core we added a directory called :samp:`user_modules`. There users can put their own code and simply import it as any other function within the posydon namespace. -Additionally, we have there an example file of a case which modifies the :ref:`flow chart `. +Additionally, we have an example file there which demonstrates how to modify the :ref:`flow chart `. diff --git a/docs/_source/components-overview/post_processing/pipeline_ini.rst b/docs/_source/components-overview/post_processing/pipeline_ini.rst index 56a54208a0..2e4fe86c8c 100644 --- a/docs/_source/components-overview/post_processing/pipeline_ini.rst +++ b/docs/_source/components-overview/post_processing/pipeline_ini.rst @@ -1,16 +1,16 @@ .. _pipeline_ini: -############################################################## +####################################################################### Documentation of the :samp:`ini` file for the post-processing pipeline -############################################################## +####################################################################### Aim of the :samp:`ini` file -=================== +=========================== Using an :samp:`ini` file should help to keep an overview on large grid -repositories and ensures that all workflows will be setup the same way. +repositories and ensures that all workflows will be set up the same way. -There is a script to setup the pipeline, it takes one argument: +There is a script to set up the pipeline, it takes one argument: .. code-block:: bash @@ -40,7 +40,7 @@ all :ref:`tasks `. Hence, you can run all .. _pipeline_ini_sections: Sections in the :samp:`ini` file -======================== +================================= Account and slurm settings -------------------------- @@ -84,7 +84,7 @@ as pages in a single PDF. [pipeline setup] PATH_TO_GRIDS = '/srv/beegfs/scratch/shares/astro/posydon/POSYDON_GRIDS_v2/' - VERSION = '' # to have a verion below the grid type level + VERSION = '' # to have a version below the grid type level PATH = '.' # working dir VERBOSE = True diff --git a/docs/_source/components-overview/post_processing/psygrid.rst b/docs/_source/components-overview/post_processing/psygrid.rst index ede8264da1..6c8f57cc11 100644 --- a/docs/_source/components-overview/post_processing/psygrid.rst +++ b/docs/_source/components-overview/post_processing/psygrid.rst @@ -299,7 +299,7 @@ Work on/with a `PSyGrid` object ------------------------------- Loop over a `PSyGrid` object -~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similarly to accessing a single value in the :samp:`PSyGrid` object, we can loop over a :samp:`PSyGrid` object, which will loop over the individual runs diff --git a/docs/_source/components-overview/processing-pipeline.rst b/docs/_source/components-overview/processing-pipeline.rst index 53a415238d..3e040394fb 100644 --- a/docs/_source/components-overview/processing-pipeline.rst +++ b/docs/_source/components-overview/processing-pipeline.rst @@ -30,9 +30,9 @@ what you can do with this tool. post_processing/pipeline :samp:`ini` file documentation -~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Learn how to use an :samp:`ini` file to setup the post-processing pipeline. +Learn how to use an :samp:`ini` file to set up the post-processing pipeline. .. toctree:: diff --git a/docs/_source/components-overview/stellar-binary-simulation.rst b/docs/_source/components-overview/stellar-binary-simulation.rst index 81ee6b9f61..e581c1ee4e 100644 --- a/docs/_source/components-overview/stellar-binary-simulation.rst +++ b/docs/_source/components-overview/stellar-binary-simulation.rst @@ -10,7 +10,7 @@ Stellar & Binary-star Simulation POSYDON: Building Populations with Object-Oriented Design ========================================================= -POSYDON provide a fully customizable framework to population synthesis modeling. By leveraging the power and flexibility of object-oriented design, it offers a modular and scalable architecture that enables researchers to customize, extend, and integrate various astrophysical processes with ease. This section delves into the intricacies of the POSYDON hierarchy, outlining the key classes, their interrelationships, and their roles in crafting sophisticated population synthesis models. +POSYDON provides a fully customizable framework to population synthesis modeling. By leveraging the power and flexibility of object-oriented design, it offers a modular and scalable architecture that enables researchers to customize, extend, and integrate various astrophysical processes with ease. This section delves into the intricacies of the POSYDON hierarchy, outlining the key classes, their interrelationships, and their roles in crafting sophisticated population synthesis models. The Single Star Object ---------------------- @@ -56,7 +56,7 @@ The Simulation Properties object -------------------------------- -POSDYON Population Synthesis Configuration Guide +POSYDON Population Synthesis Configuration Guide ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/_source/conf.py b/docs/_source/conf.py index 6dab9a759b..abeba92fb1 100644 --- a/docs/_source/conf.py +++ b/docs/_source/conf.py @@ -16,7 +16,7 @@ from posydon import __version__ as posydon_version # Rewrite posydon_version for docs developement -posydon_version = 'v2.0.0-dev' +#posydon_version = 'v2.0.0-dev' # -- Project information ----------------------------------------------------- @@ -25,10 +25,83 @@ copyright =f'{datetime.datetime.now().year}, Tassos Fragos' author = u'POSYDON Collaboration' +# -- Multi-version configuration ------------------------------------------- +# INJECTION GRAB START +import subprocess + +# Get versions from github tags +def get_github_tags(): + '''Get the tags from the github repository + + Returns + ------- + list of str + A list of tags sorted by version, without the 'v' prefix + + ''' + try: + # Get the tags from the github repository + # this looks + tags = subprocess.check_output(['git', 'tag'], universal_newlines=True).strip().split('\n') + + version_tags = [] + for tag in tags: + # Skip tags that don't look like versions + if tag.startswith('v'): + tag_clean = tag[1:] + version_tags.append(tag_clean) + else: + continue + version_tags.sort() + return version_tags + + except Exception as e: + print(f"Error getting tags from github: {e}") + return [] + +github_tags = get_github_tags() + +# 2.0.0-dev needs to be removed from the list and replaced with development +github_tags.remove('2.0.0-dev') + +# get a clean current version +# some old versions require the dirty tag to be removed, because files need +# to be added to compile correctly +# only allow old tag to be dirty +old_tags = ['1.0.0', '1.0.4', '1.0.5', '2.0.0-pre1', '2.0.0-pre2'] +if posydon_version.startswith('2.0.0-dev'): + current_version = 'dev' + posydon_version = 'development' +elif 'dirty' in posydon_version and posydon_version.split('+')[0] in old_tags: + current_version = posydon_version.split('+')[0] + posydon_version = posydon_version.split('+')[0] +else: + current_version = posydon_version + +# absolute path to the documentation +base_url = 'https://posydon.org/POSYDON' + +# get the list of versions +url_list = [f'{base_url}/v{tag}' for tag in github_tags] +v1_versions = [[tag, url] for tag, url in zip(github_tags, url_list) if tag.startswith('1.')] +v2_versions = [[tag, url] for tag, url in zip(github_tags, url_list) if tag.startswith('2.')] + +# add development version +v2_versions.append(['dev', f'{base_url}/development']) + +# Versions to be shown in the version dropdown +html_context = { + 'current_version' : current_version, + 'v1_versions' : v1_versions, + 'v2_versions' : v2_versions, +} + # The short X.Y version. version = posydon_version # The full version, including alpha/beta/rc tags. release = posydon_version +# INJECTION GRAB END +# -- VERSION END --------------------------------------------------------- # -- Path setup ---------------------------------------------------------- @@ -103,9 +176,6 @@ parts.extend(('fig.show()', 'plot.show()')) docscrape_sphinx.IMPORT_MATPLOTLIB_RE = r'\b({})\b'.format('|'.join(parts)) -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] @@ -122,17 +192,15 @@ # General information about the project. project = u'posydon' -copyright = u'2024, Tassos Fragos' + +# Get the current year +current_year = datetime.datetime.now().year +copyright = u'{}, Tassos Fragos'.format(current_year) author = u'POSYDON Collaboration' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -# -# The short X.Y version. -version = posydon_version -# The full version, including alpha/beta/rc tags. -release = posydon_version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -282,7 +350,6 @@ html_logo = 'posydon_logo.png' html_theme_options = { 'logo_only': True, - 'display_version': True, } # -- Options for LaTeX output --------------------------------------------- diff --git a/docs/_source/contact-support/contact-information.rst b/docs/_source/contact-support/contact-information.rst index 4ac17756f3..2aed426be3 100644 --- a/docs/_source/contact-support/contact-information.rst +++ b/docs/_source/contact-support/contact-information.rst @@ -3,12 +3,14 @@ Contact Information ------------------- -We're committed to assisting the POSYDON community. If you have questions or need assistance, please refer to the following channels: +We're committed to helping the POSYDON community. If you have questions or +need assistance, please use the following channels: Official Website ~~~~~~~~~~~~~~~~ -For the latest news, updates, and comprehensive information about POSYDON, visit our official website: +For the latest news, updates, and comprehensive information about POSYDON, +visit our official website: - `POSYDON Official Website `_ @@ -16,26 +18,41 @@ For the latest news, updates, and comprehensive information about POSYDON, visit Report Issues or Contribute ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you've encountered a bug or would like to suggest improvements, consider contributing directly on our GitHub repository: +If you've encountered a bug or would like to suggest improvements, consider +contributing directly on our GitHub repository: - `POSYDON on GitHub `_. -When reporting issues, please ensure you provide sufficient information and context to help us understand and address the issue efficiently. +When reporting issues, please ensure you provide sufficient information and +context to help us understand and address the issue efficiently. -Mailing List -~~~~~~~~~~~~ +Need help or have questions? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Before reaching out, we recommend checking our FAQ section on :ref:`installation ` and :ref:`code usage `. If your query is not addressed there, join our mailing list where you can ask questions and seek support from the POSYDON community: +Common questions and issues can be found in the following sections: +- `Github Discussions `_. +- :ref:`installation ` for installation-related queries. +- :ref:`code usage ` for some common code usage questions. -- Join the `POSYDON mailing list `_. +If your question is not addressed in the above sections, you can ask for help +in the `Github Discussions `_ section. +We will try our best to answer your question as soon as possible. -Please be respectful and specific in your questions. This will help the community provide more accurate and timely responses. +As a last resort, if you're unable to ask your question on GitHub Discussions, +you can reach out via the POSYDON mailing list. +However, we encourage you to use GitHub Discussions, as it allows for better +tracking of questions, and helps build a knowledge base. -It is also advised to write a short description on how do you plan to use POSYDON. This will help us to understand the impact of this tool in different fields. +`POSYDON mailing list `_. + +It is also advised to write a short description on how do you plan to use POSYDON. +This will help us to understand the impact of this tool in different fields. Feedback ~~~~~~~~ -We continually strive to improve POSYDON, and your feedback is invaluable. If you have any suggestions, praise, or constructive criticism, please share it with us through the `POSYDON mailing list `_. +We continually strive to improve POSYDON, and your feedback is invaluable. +If you have any suggestions, praise, or constructive criticism, please share it +with us. Thank you for being part of the POSYDON community! diff --git a/docs/_source/contributing/code-style-guidelines.rst b/docs/_source/contributing/code-style-guidelines.rst deleted file mode 100644 index 14ac698e01..0000000000 --- a/docs/_source/contributing/code-style-guidelines.rst +++ /dev/null @@ -1,61 +0,0 @@ -.. _code-style: - -Code Style Guidelines ---------------------- - -Adhering to a consistent code style helps maintain readability and eases collaboration. Here are the guidelines you should follow when contributing to POSYDON. - -General Principles -~~~~~~~~~~~~~~~~~~ -1. **Clarity and Readability**: Code should be easy to understand. Opt for clarity over cleverness. -2. **Consistency**: Follow the style of the existing codebase. When in doubt, consistency with the existing code is more important than adhering to external style guides. -3. **Comments**: Write meaningful comments, especially for complex logic. Avoid obvious comments. - -Python Style Guide -~~~~~~~~~~~~~~~~~~ -We follow the `PEP 8 `_ style guide for Python code with some exceptions and additions as outlined below. - -1. **Indentation**: Use 4 spaces per indentation level. -2. **Line Length**: Limit lines to 79 characters. -3. **Imports**: Group imports in the following order: standard libraries, third-party libraries, and local application imports. Each group should be separated by a blank line. -4. **Whitespace**: Avoid extraneous whitespace in the following situations: - - Immediately inside parentheses, brackets, or braces. - - Immediately before a comma, colon, or semicolon. -5. **Comments**: Comments should be complete sentences and should be used sparingly, i.e., only when necessary to explain complex pieces of logic or decisions that could seem non-obvious to other developers. - -Naming Conventions -~~~~~~~~~~~~~~~~~~ -1. **Functions and Variables**: Use `snake_case`. -2. **Classes and Exceptions**: Use `CamelCase`. -3. **Constants**: Use `UPPER_SNAKE_CASE`. -4. **Module-level globals** (like loggers or constants): Prefix with an underscore `_`. - -Docstrings -~~~~~~~~~~ -We adhere to the `NumPy docstring style guide `_. Every function, class, and module should have a docstring that adheres to this style. This helps in generating consistent and readable documentation. Ensure: - -1. Every module, class, and function/method has an accompanying docstring. -2. Always describe parameters, return types, and raised exceptions. -3. Include examples in function/method docstrings when possible. - -Testing -~~~~~~~ -Ensure that your code does not introduce new bugs: - -1. Write unit tests for your new features. -2. Run all the tests to make sure they pass. -3. Follow the existing testing conventions in the codebase. - -Commit Guidelines -~~~~~~~~~~~~~~~~~ -1. Commit messages should be concise and descriptive. -2. Use the present tense ("Add feature" not "Added feature"). -3. Use the imperative mood ("Move cursor to..." not "Moves cursor to..."). -4. Limit the first line to 72 characters or less. -5. Reference issues and pull requests liberally after the first line. - -Final Thoughts -~~~~~~~~~~~~~~ -Before submitting your code, review these guidelines and check your code against them. Taking the time to ensure your code adheres to these standards will make the review process smoother and faster. - -Thank you for your contribution and for making POSYDON's codebase clean and consistent! diff --git a/docs/_source/contributing/reporting-issues.rst b/docs/_source/contributing/reporting-issues.rst deleted file mode 100644 index 4f04e29605..0000000000 --- a/docs/_source/contributing/reporting-issues.rst +++ /dev/null @@ -1,52 +0,0 @@ -.. _report-issues: - -Reporting issues ----------------- - -If you encounter any problems while using POSYDON, we encourage you to report them. By doing so, you can help us improve the software for everyone. This page provides guidelines on how to report issues effectively. - -Before Reporting an Issue -~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. **Check the FAQ**: Before submitting a new issue, please review our [FAQ](link-to-faq-page) to see if your problem has been addressed before. - -2. **Update to the Latest Version**: Ensure that you're using the latest version of POSYDON. Some issues may have been resolved in newer releases. - -3. **Search Existing Issues**: Look through the `issues on our GitHub repository `_ to check if someone else has already reported the same problem. If you find a match, you can comment on it with any additional information you have. - -How to Report an Issue -~~~~~~~~~~~~~~~~~~~~~~~ - -1. **Be Specific**: The title of the issue should be descriptive. Instead of writing "Program crashes," write "Program crashes when processing XYZ data." - -2. **Provide Detailed Information**: - - **Operating System and Version**: E.g., "Windows 10 version 1903" or "Ubuntu 20.04". - - **POSYDON Version**: Mention the version you are using. - - **Steps to Reproduce**: List the steps you took that led to the issue. - - **Expected Behavior vs. Observed Behavior**: Explain what you expected to happen and what actually occurred. - - **Error Messages**: If there was an error message, please include it (preferably in a text format or as a screenshot). - -3. **Include Screenshots or Logs**: If applicable, add screenshots or logs to provide a visual context. This can be especially helpful for UI issues or visual glitches. - -4. **Label the Issue**: If you're familiar with GitHub, label your issue (e.g., "bug", "enhancement", "documentation", etc.). If you're unsure, don't worry; our team will label it appropriately after review. - -5. **Be Respectful**: Remember that POSYDON is an open-source project. The people helping you are volunteers. Always approach the conversation with respect and patience. - -Submitting the Issue -~~~~~~~~~~~~~~~~~~~~~ -Once you've gathered all the necessary information: - -1. Navigate to the `POSYDON GitHub Issues page `_. -2. Click on the "New issue" button. -3. Fill out the template provided (if any) or use the guidelines above. -4. Click "Submit" to create the issue. - -After Reporting -~~~~~~~~~~~~~~~~ -Once the issue is reported: - -1. **Stay Engaged**: Respond to any questions or feedback from the development team. Your insights can help expedite the resolution. -2. **Updates**: If you find more information or discover a workaround, update the issue with a new comment. -3. **Resolution**: Once the issue is resolved, it'll be closed. You'll be notified via the GitHub notification system. - -Thank you for helping make POSYDON better for everyone! diff --git a/docs/_source/getting-started/installation-guide.rst b/docs/_source/getting-started/installation-guide.rst index 21d1b261a5..8345feeaa9 100644 --- a/docs/_source/getting-started/installation-guide.rst +++ b/docs/_source/getting-started/installation-guide.rst @@ -1,15 +1,18 @@ .. _installation-guide: Installation Guide ------------------- +****************** -This guide will help you step by step in installing POSYDON. We recommend using Anaconda, a package manager, to manage the installation and dependencies, ensuring a smooth setup process. +This guide will help you install POSYDON step-by-step. We recommend using Anaconda, a package manager, to manage the installation and dependencies, ensuring a smooth setup process. .. contents:: Table of Contents :local: -Using Anaconda (Recommended) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Installing POSYDON +================== + +Anaconda (Recommended) +---------------------- 1. **Install Anaconda** @@ -60,7 +63,8 @@ Using Anaconda (Recommended) export PATH_TO_POSYDON=/path/to/your/posydon/installation export PATH_TO_POSYDON_DATA=/path/where/you/want/to/store/data - The path for the data location is up to your choice but we recommend :code:`PATH_TO_POSYDON_DATA=$PATH_TO_POSYDON/data`. + The path for the data location is up to you, but keeping the data separate + from the code is recommended for better organization. .. note:: You can add these lines to your :code:`~/.bashrc` or :code:`~/.bash_profile` or your shell equivalent to ensure the environment variables are set every time you open a new terminal. @@ -68,22 +72,23 @@ Using Anaconda (Recommended) 5. **Download the Dataset** .. warning:: - The POSYDON v2.0.0 dataset is not yet available on Zenodo. The instructions currently point to the POSYDON v1.0.0 dataset release. Please refer to the development version of the dataset available on Northwestern and UNIGE HPC facilities for now. To have access to latest pre-release dataset (241028) you must be a POSYDON core developer, please refer to the #developers Slack channel. + The POSYDON v2.0.0 dataset is not yet available on Zenodo. The instructions currently point to the POSYDON v1.0.0 dataset release. + Please refer to the development version of the dataset available on Northwestern and UNIGE HPC facilities for now. To have access to latest pre-release dataset (241028) you must be a POSYDON core developer, please refer to the #developers Slack channel. - You can use POSYDON's built-in API command (the downloaded data will be saved in the directory specified by :code:`PATH_TO_POSYDON_DATA`): + You can use POSYDON's built-in API command (the downloaded data will be downloaded to the directory specified by :code:`PATH_TO_POSYDON_DATA`): .. code-block:: bash get-posydon-data - May use :code:`get-posydon-data -h` to see all the options for this command, which allows to list all the datasets and download the one of your choice. + You may use :code:`get-posydon-data -h` to see all the options for this command, which allows you to list all the datasets and download the one of your choice. Alternatively, you can manually download the datasets from Zenodo. You can find the POSYDON datasets on the `POSYDON community `_ on Zenodo. .. _dev-version: -Using the Development Version -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +GitHub (Development Version) +---------------------------- For users interested in the latest features and developments, you can install POSYDON directly from its GitHub repository: @@ -117,61 +122,94 @@ For users interested in the latest features and developments, you can install PO Refer back to the recommended installation steps, starting from :ref:`point 4 `, to download the required dataset and set the necessary environment variables. -Running grids using POSYDON on HPC Facilities -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Installing Jupyter for Tutorials +================================= -If you are planning to create MESA grids using POSYDON on HPC facilities, it's essential to have ``mpi4py`` installed to take advantage of parallel computations. -You do not need to have ``mpi4py`` installed if you are only running population synthesis simulations. +Our tutorials are provided as Jupyter notebooks. If you want to run these notebooks interactively, you will need to have either Jupyter Lab or Jupyter Notebook installed. + +1. **Using Anaconda (Recommended)** -1. **Install mpi4py via Anaconda (Recommended)**: + + If you have already installed Anaconda as suggested earlier in the installation guide, installing Jupyter Lab or Notebook is straightforward: .. code-block:: bash - conda install mpi4py + conda install -c conda-forge jupyterlab -2. **Alternatively, via pip**: + Or, for the classic Jupyter Notebook: .. code-block:: bash - pip install ".[hpc]" + conda install -c conda-forge notebook +2. **Alternatively, via pip** -.. warning:: - Users have reported issues when trying to install `mpi4py` via pip. If you encounter any issues, try installing `mpi4py` through Anaconda. If you cannot solve the issue, please refer to the :ref:`Troubleshooting Guide ` or seek support from the community or developers, see the :ref:`contact us ` page. -Machine Learning Modules Installation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + If you prefer using `pip`, you can also install Jupyter Lab or Notebook using the following commands: + + .. code-block:: bash -For users who wish to utilize POSYDON's latest machine learning features: + pip install jupyterlab -1. **Navigate to your POSYDON directory** (where the `setup.py` is located) and run: + Or, for the classic Jupyter Notebook: .. code-block:: bash - pip install ".[ml]" + pip install notebook +3. **After Installation** -Installing Experimental Visualization Libraries -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -POSYDON provides experimental visualization libraries to enhance the experience of data analysis and results visualization. While these libraries offer advanced features, please note that they might still be in development and could be subject to changes. + Once installed, you can start Jupyter Lab or Notebook by running: -To install these experimental visualization libraries + .. code-block:: bash -1. **Navigate to your POSYDON directory** (where the `setup.py` is located) and run: + jupyter lab + + Or: .. code-block:: bash - - pip install ".[vis]" - After installing these libraries, you can access various visualization tools and features integrated within POSYDON. Ensure to consult the documentation or any guides associated with these features for their optimal usage. + jupyter notebook + + From the terminal or command prompt. This will open a browser window where you can navigate to the downloaded notebooks and run them interactively. .. note:: - As these are experimental features, feedback, and bug reports regarding the visualization tools are highly appreciated. It will aid the development and optimization of these features for future stable releases. + Remember to navigate to the directory containing the Jupyter notebooks or you won't see them listed in the Jupyter interface. + + + +Installing additional dependencies (Optional) +============================================= + +For some specific functionalities, you may need to install additional dependencies. +Below are the instructions for installing these dependencies and what they are used for. + +Running grids of MESA models using POSYDON +------------------------------------------ + +If you are planning to create MESA grids using POSYDON on HPC facilities, it's essential to have ``mpi4py`` installed to take advantage of parallel computations. +You do not need to have ``mpi4py`` installed if you are only running population synthesis simulations. + +1. **Install mpi4py via Anaconda (Recommended)**: + + .. code-block:: bash + + conda install mpi4py + +2. **Alternatively, via pip**: + + .. code-block:: bash + + pip install ".[hpc]" + + +.. warning:: + Users have reported issues when trying to install `mpi4py` via pip. If you encounter any issues, try installing `mpi4py` through Anaconda. If you cannot solve the issue, please refer to the :ref:`Troubleshooting Guide ` or seek support from the community or developers, see the :ref:`contact us ` page. -Documentation Installation & Compilation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Documentation Building +------------------------ If you're interested in building the POSYDON documentation locally: @@ -201,78 +239,58 @@ If you're interested in building the POSYDON documentation locally: conda install pandoc -4. **Open the Compiled Documentation**: +4. **Make the documentation**: - After successfully building the documentation, you can view it in your preferred browser. Navigate to the build directory and open the `index.html`: + After installing `pandoc`, you can make the documentation using Sphinx: .. code-block:: bash - open _build/html/index.html - - .. note:: - The `open` command works on macOS. If you're using a different OS, you might need to open the `index.html` using your file manager or use a different command. - - -Installing Jupyter for Tutorials -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Our tutorials are provided as Jupyter notebooks. If you want to run these notebooks interactively, you will need to have either Jupyter Lab or Jupyter Notebook installed. - -1. **Using Anaconda (Recommended)** - - - If you have already installed Anaconda as suggested earlier in the installation guide, installing Jupyter Lab or Notebook is straightforward: + cd docs + make html - .. code-block:: bash + This command will generate the HTML documentation in the `_build/html` directory within the `docs` folder. - conda install -c conda-forge jupyterlab +4. **Open the Compiled Documentation**: - Or, for the classic Jupyter Notebook: + After successfully building the documentation, you can view it in your preferred browser. Navigate to the build directory and open the `index.html`: .. code-block:: bash - conda install -c conda-forge notebook + open _build/html/index.html -2. **Alternatively, via pip** + .. note:: + The `open` command works on macOS. If you're using a different OS, you might need to open the `index.html` using your file manager or use a different command. - If you prefer using `pip`, you can also install Jupyter Lab or Notebook using the following commands: - .. code-block:: bash +Machine Learning Dependencies +--------------------------------------- - pip install jupyterlab +For users who wish to utilize POSYDON's latest machine learning features. +This is specifically used in the active learning module and profile interpolation. +You do not require these dependencies if you are using the provided Initial-Final interpolators. - Or, for the classic Jupyter Notebook: +1. **Navigate to your POSYDON directory** (where the `setup.py` is located) and run: .. code-block:: bash - pip install notebook - -3. **After Installation** + pip install ".[ml]" - Once installed, you can start Jupyter Lab or Notebook by running: +Installing Experimental Visualization Libraries +----------------------------------------------- - .. code-block:: bash +POSYDON provides experimental visualization libraries to enhance the experience of data analysis and results visualization. While these libraries offer advanced features, please note that they might still be in development and could be subject to changes. - jupyter lab +To install these experimental visualization libraries - Or: +1. **Navigate to your POSYDON directory** (where the `setup.py` is located) and run: .. code-block:: bash + + pip install ".[vis]" - jupyter notebook - - From the terminal or command prompt. This will open a browser window where you can navigate to the downloaded notebooks and run them interactively. + After installing these libraries, you can access various visualization tools and features integrated within POSYDON. Ensure to consult the documentation or any guides associated with these features for their optimal usage. .. note:: - Remember to navigate to the directory containing the Jupyter notebooks or you won't see them listed in the Jupyter interface. - - -Additional Notes -~~~~~~~~~~~~~~~~~ - -- After installation, ensure you verify the setup by following our :ref:`Verification Guide `. -- Always ensure you activate the `posydon_env` environment before running POSYDON. -- If you encounter issues during the installation, consult our :ref:`Troubleshooting Guide ` or seek support from the community or developers, see the :ref:`contact us ` page. - + As these are experimental features, feedback, and bug reports regarding the visualization tools are highly appreciated. It will aid the development and optimization of these features for future stable releases. diff --git a/docs/_source/getting-started/verification.rst b/docs/_source/getting-started/verification.rst deleted file mode 100644 index d358735d0f..0000000000 --- a/docs/_source/getting-started/verification.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. _verification: - -Verification ------------- - -After installing POSYDON, it's essential to verify that the software is functioning correctly. This page provides steps to help you confirm that your installation is successful and the software is operating as intended. - -1. **Initial Check** - - - Start POSYDON. You should see [describe the expected initial interface or message]. - - Check for any error messages in the console or log files. - -2. **Run a Test Simulation** - - Follow the steps below to run a basic test simulation: - - a. [Step-by-step instructions to start a basic simulation. This could be a pre-defined scenario or test case included with the software.] - - b. Once the simulation is complete, review the results. They should match [provide a brief description of expected results or show an example output]. - -3. **Check the Version** - - - In the POSYDON interface (or console), go to [specific location or command] to check the software version. Ensure it matches the version you intended to install. - -4. **Additional Diagnostics** (if applicable) - - - [Any other diagnostic tools or steps that can help verify the software's integrity or functionality.] - -**Troubleshooting** - -If you encounter any discrepancies during verification: - -- Review the :ref:`Installation Guide ` to ensure all steps were followed correctly. -- Consult the :ref:`Troubleshooting Guide ` for common issues and solutions. -- If you continue to experience issues, :ref:`contact us `. - diff --git a/docs/_source/index.rst b/docs/_source/index.rst index b6f6d048c3..47684f530c 100644 --- a/docs/_source/index.rst +++ b/docs/_source/index.rst @@ -16,9 +16,9 @@ Welcome to the official documentation of **POSYDON** - *POpulation SYnthesis wit About POSYDON ------------- -POSYDON is a next-generation single and binary-star population synthesis tool. Our vision is to provide researchers and enthusiasts with a state-of-the-art platform to delve into the intricacies of stellar structures and binary evolutions using MESA. With full stellar structure modeling, advanced machine learning techniques, and a modular architecture, POSYDON stands at the forefront of astrophysical simulations. +POSYDON is a next-generation single- and binary-star population synthesis tool. Our vision is to provide researchers and enthusiasts with a state-of-the-art platform to delve into the intricacies of stellar structures and binary evolutions using MESA. With full stellar structure modeling, advanced machine learning techniques, and a modular architecture, POSYDON stands at the forefront of astrophysical simulations. -To stay up to date with the latest news about POSYDON, check out our `official website `_ for more details. +To stay up-to-date with the latest news about POSYDON, check out our `official website `_ for more details. How is this Documentation Structured? ------------------------------------- @@ -26,7 +26,7 @@ How is this Documentation Structured? - **Introductions** Discover what POSYDON is, its objectives, and its science scope. - **Getting Started:** A quick guide to get POSYDON up and running on your machine. - **User Guides:** Detailed guides on using various features. -- **Tutorials and Examples:** Step-by-step walkthroughs of typical use-cases. +- **Tutorials and Examples:** Step-by-step walk-throughs of typical use-cases. - **In-Depth Components Overview:** Delve deep into the core components of POSYDON. - **API Reference:** A comprehensive reference for developers. - **Troubleshooting and FAQs:** Answers to common questions and problems. @@ -41,8 +41,8 @@ How is this Documentation Structured? :caption: Introduction introduction-acknowledgements/intro - introduction-acknowledgements/collaborative-team - Publications + Collaborative Team + Publications .. toctree:: :maxdepth: 1 @@ -50,14 +50,6 @@ How is this Documentation Structured? getting-started/prerequisites getting-started/installation-guide - getting-started/verification - - -.. toctree:: - :maxdepth: 1 - :caption: User Guides - - user-guides/user-roadmap .. toctree:: :maxdepth: 1 @@ -68,7 +60,6 @@ How is this Documentation Structured? tutorials-examples/generating-datasets/generating-datasets tutorials-examples/MESA-grids/running-grids - .. toctree:: :maxdepth: 1 :titlesonly: @@ -79,16 +70,6 @@ How is this Documentation Structured? components-overview/machine-learning-components components-overview/stellar-binary-simulation -.. .. toctree:: -.. :maxdepth: 1 -.. :caption: POSYDON Workflow - -.. posydon-workflow/population-specification -.. posydon-workflow/stellar-evolution-choices -.. posydon-workflow/mesa-evolutionary-tracks -.. posydon-workflow/interacting-binary-grids -.. posydon-workflow/running-simulations - .. toctree:: :maxdepth: 1 :caption: API Reference @@ -96,22 +77,6 @@ How is this Documentation Structured? api_reference/posydon api_reference/bin -.. toctree:: - :maxdepth: 1 - :caption: Troubleshooting and FAQs - - troubleshooting-faqs/installation-issues - troubleshooting-faqs/code-questions - - -.. toctree:: - :maxdepth: 1 - :caption: Contributing to POSYDON - - contributing/how-to-contribute - contributing/code-style-guidelines - contributing/reporting-issues - .. toctree:: :maxdepth: 1 @@ -123,18 +88,18 @@ How is this Documentation Structured? .. toctree:: :maxdepth: 1 - :caption: Contact and Support + :caption: Support and Contact contact-support/contact-information - - - - + troubleshooting-faqs/installation-issues + troubleshooting-faqs/code-questions + Report an Issue + contributing/how-to-contribute Acknowledgments --------------- -**Team Members:** POSYDON is being led and developed by a dedicated team of astrophysicists and computer scientists. At the helm are Principal Investigators Tassos Fragos (Université de Genève) and Vicky Kalogera (Northwestern University), along with many talented individuals. You can read more about the team and they research on :ref:`Collaborative Team ` page. +**Team Members:** POSYDON is being led and developed by a dedicated team of astrophysicists and computer scientists. At the helm are Principal Investigators Tassos Fragos (Université de Genève) and Vicky Kalogera (Northwestern University), along with many talented individuals. You can read more about the team on the `Collaborative Team `_ page. **Funding Agencies:** The POSYDON project is supported primarily by two sources: the Swiss National Science Foundation Professorship grant (PI Fragos) and the Gordon and Betty Moore Foundation (PI Kalogera). diff --git a/docs/_source/introduction-acknowledgements/collaborative-team.rst b/docs/_source/introduction-acknowledgements/collaborative-team.rst deleted file mode 100644 index e2942c3f87..0000000000 --- a/docs/_source/introduction-acknowledgements/collaborative-team.rst +++ /dev/null @@ -1,107 +0,0 @@ -.. _team-page: - -Collaborative Team ------------------- - -Our team consists of dedicated scientists and researchers, working together to shape and develop POSYDON. Here, we list all current members of the collaboration, with a special indicator for those who are part of the core development team. We also acknowledge and honor our past members for their contributions. - -We're grateful to all our team members, both current and past, for their relentless efforts and dedication to the POSYDON project. - -.. image:: UF_F2F_02_2023.jpg - :align: center - :width: 800 - :alt: POSYDON Team, F2F meeting - University of Florida, February 2023 - - -POSYDON Team, F2F meeting - University of Florida, February 2023 - - -Current Team Members -~~~~~~~~~~~~~~~~~~~~ - -- **Jeff Answers\***, *University of Florida*: Core developer focused on coordinating the development MESA grid simulations and the overall POSYDON structure. - -- `Simone Bavera\* `_, *University of Geneva*: Leading the design and development of POSYDON since its conseption. Simone uses POSYDON to study the formation mechanism of merging binary black holes, sources of gravitational waves. - -- `Max M. Briel `_, *University of Geneva*. - -- `Christopher Berry `_, *University of Glasgow*. - -- **Abhishek Chattaraj**, *University of Florida*. - -- **Tassos Fragos (co-PI)**, *University of Geneva*. - -- `Monica Gallegos-Garcia `_, *Northwestern University*: Assisted with early planning for the interstructure to generate MESA grids, running MESA simulations for POSYDON v2, and contributed to the analysis of compact object populations. Monica uses POSYDON to investigate the formation of compact object mergers. - -- `Seth Gossage\* `_, *Northwestern University*: Core developer focused on development of MESA stellar evolution grids, especially for stars less massive than 10 solar masses. Seth employs POSYDON in his research to find constraints on the physics of stellar rotation, convection, and magnetic fields. - -- `Vicky Kalogera (co-PI) `_, *Northwestern University*. - -- **Eirini Kasdagli**, *University of Florida*. - -- `Aggelos Katsaggelos `_, *Northwestern University*. - -- **Chase Kimball**, *Northwestern University*. - -- `Dean Kousiounelos `_, *Lake Forest College*: Developing data visualization for Binary Stellar Evolution for POSYDON to aid in understanding an evolutionary narrative, and bridge the gap between astrophysics and broader audiences. Under the mentorship of Dr. Vicky Kalogera and Dr. Seth Gossage. - -- `Konstantinos Kovlakas* `_, *Institute of Space Sciences (ICE/CSIC,IEEC)*: Core developer responsible for core infrastructure, the encoding of MESA output in POSYDON, and the characterization of stars based on their properties. His research focuses on the modeling of accreting compact objects and comparisons against observations. - -- **Matthias Kruckow\***, *University of Geneva*. - -- **Shamal Lalvani**, *Northwestern University*. - -- `Camille Liotine* `_, *Northwestern University*: Integrating pulsar modeling into POSYDON and facilitating general code development for POSYDON. - -- **Devina Mishra\***, *Norwegian University of Science and Technology*. - -- **Kyle Rocha\***, *Northwestern University*: Development of the psy-cris active learning module, binary population API, evolutionray hooks, IO for binary stars / populations and integration with pandas data frame. - -- `Jaime Román-Garza `_, *University of Geneva*. - -- **Philipp M. Srivastava\***, *Northwestern University*. - -- `Meng Sun* `_, *Northwestern University*: Most of the contributions involved preparing detailed MESA binary modeling and assisting in the analysis of single-star and binary evolution results: reviewed the error distribution after MESA binary tracks were trained by the IFinterpolator; assisted in constructing MESA binary grids with metal-poor, metal-rich, and low-mass stars; resolved code convergence issues. - -- `Elizabeth Teng* `_, *Northwestern University*: Developed stellar profile interpolation and consulted on time-series interpolation. - -- `Goce Trajcevski `_, *Iowa State University*. - -- `Zepei Xing* `_, *University of Geneva*. - -- `Manos Zapartas `_, *University of Geneva*. - - - - - - -.. note:: - Members marked with an asterisk (*) are part of the core development team. - - -Past Team Members -~~~~~~~~~~~~~~~~~ - -- `Aaron Dotter `_, *Northwestern University*. - -- `Prabin Giri `_, *Iowa State University*. - -- **Ying Qin**, *Formerly,Northwestern University*: Contributed to the v1 development of MESA grids. Ying used POSYDON for his post-doctoral research to study X-ray binaries. - -- `Juan Gabriel Serra Perez `_, *Northwestern University*. - -- `Xu Teng `_, *Iowa State University*. - -- **Nam Tran**, *Formerly, University of Copenhagen*: Assisted early development of the POSYDON core infrastructure. Nam used POSYDON in his master thesis to study X-ray binaries. - - - - - - -Students Contributions -~~~~~~~~~~~~~~~~~~~~~~ - -- `Petter Stahle `_, *Formerly, University of Geneva*: Developed the POSYDON web-application API interphase to run POSYDON v1 population synthesis simulations. - diff --git a/docs/_source/introduction-acknowledgements/intro.rst b/docs/_source/introduction-acknowledgements/intro.rst index 18ed8945cc..579b457d55 100644 --- a/docs/_source/introduction-acknowledgements/intro.rst +++ b/docs/_source/introduction-acknowledgements/intro.rst @@ -6,7 +6,7 @@ Introduction, Objectives, and Scope What is POSYDON? ~~~~~~~~~~~~~~~~ -POSYDON, an acronym for **POpulation SYnthesis with Detailed binary-evolution simulatiONs**, is a next-generation single and binary-star population synthesis code. Designed to seamlessly incorporate full stellar structure and evolution modeling, it leverages the advanced capabilities of MESA for a comprehensive and accurate simulation of stellar systems. +POSYDON, an acronym for **POpulation SYnthesis with Detailed binary-evolution simulatiONs**, is a next-generation single- and binary-star population synthesis code. Designed to seamlessly incorporate full stellar structure and evolution modeling, it leverages the advanced capabilities of MESA for a comprehensive and accurate simulation of stellar systems. Code Objectives ~~~~~~~~~~~~~~~~ @@ -16,7 +16,7 @@ The primary goal of POSYDON is to simulate stellar populations with unparalleled Science Scope ~~~~~~~~~~~~~ -With its sophisticated algorithms and detailed simulations, POSYDON has broad science applicability. The version 2 of POSYDON is tailored towards the evolution of massive binary stars. This includes their intricate paths leading to the formation of neutron stars and black holes. Furthermore, the code is equipped to handle and analyze these evolutions across a vast range of metallicities, accommodating various stellar environments and conditions. +With its sophisticated algorithms and detailed simulations, POSYDON has broad scientific applicability. Version 2 of POSYDON is tailored towards the evolution of massive binary stars. This includes their intricate paths leading to the formation of neutron stars and black holes. Furthermore, the code is equipped to handle and analyze these evolutions across a vast range of metallicities, accommodating various stellar environments and conditions. Citation Disclaimer ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/_source/introduction-acknowledgements/published-works.rst b/docs/_source/introduction-acknowledgements/published-works.rst deleted file mode 100644 index 346752f22e..0000000000 --- a/docs/_source/introduction-acknowledgements/published-works.rst +++ /dev/null @@ -1,11 +0,0 @@ -.. _published-works: - -Published Works ---------------- - -This page lists published works that utilized the POSYDON code. Each entry provides a brief description summarizing the main findings of each study. Researchers using POSYDON have made significant contributions to our understanding of binary stars and their evolutionary paths. - -- `The formation of merging black holes with masses beyond 30 M⊙ at solar metallicity `_, **Bavera et al. (2023)**, The study unveils the impact of stellar winds and binary interactions on the maximum mass of black holes formed in isolated binaries at solar metallicity. Leveraging detailed stellar structure and binary evolution calculations in population-synthesis models, a distinct perspective emerges for the formation of black-hole binaries, contrasting prior rapid population synthesis models. - -(TODO Continue with other referenced works using POSYDON, following the same structure...) - diff --git a/docs/_source/posydon-workflow/interacting-binary-grids.rst b/docs/_source/posydon-workflow/interacting-binary-grids.rst deleted file mode 100644 index 556bd830a1..0000000000 --- a/docs/_source/posydon-workflow/interacting-binary-grids.rst +++ /dev/null @@ -1,2 +0,0 @@ -Interacting Binary Grids ------------------------- \ No newline at end of file diff --git a/docs/_source/posydon-workflow/mesa-evolutionary-tracks.rst b/docs/_source/posydon-workflow/mesa-evolutionary-tracks.rst deleted file mode 100644 index c622ec2f4c..0000000000 --- a/docs/_source/posydon-workflow/mesa-evolutionary-tracks.rst +++ /dev/null @@ -1,2 +0,0 @@ -MESA Evolutionary Tracks ------------------------- \ No newline at end of file diff --git a/docs/_source/posydon-workflow/population-specification.rst b/docs/_source/posydon-workflow/population-specification.rst deleted file mode 100644 index 42a65da39e..0000000000 --- a/docs/_source/posydon-workflow/population-specification.rst +++ /dev/null @@ -1,2 +0,0 @@ -Population Specification ------------------------- \ No newline at end of file diff --git a/docs/_source/posydon-workflow/running-simulations.rst b/docs/_source/posydon-workflow/running-simulations.rst deleted file mode 100644 index 236e2ef76a..0000000000 --- a/docs/_source/posydon-workflow/running-simulations.rst +++ /dev/null @@ -1,2 +0,0 @@ -Running Simulations -------------------- \ No newline at end of file diff --git a/docs/_source/posydon-workflow/stellar-evolution-choices.rst b/docs/_source/posydon-workflow/stellar-evolution-choices.rst deleted file mode 100644 index 124c670950..0000000000 --- a/docs/_source/posydon-workflow/stellar-evolution-choices.rst +++ /dev/null @@ -1,2 +0,0 @@ -Stellar Evolution Choices -------------------------- \ No newline at end of file diff --git a/docs/_source/release-notes/major-updates.rst b/docs/_source/release-notes/major-updates.rst deleted file mode 100644 index 613fea589a..0000000000 --- a/docs/_source/release-notes/major-updates.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. _major-updates: - -Major Updates -------------- - -This page provides an overview of the major versions of POSYDON, highlighting the key features and improvements introduced in each release. - -v2.0.0 -~~~~~~ -**Release Date:** [TODO wirte date here] - -**Description:** -The v2.0.0 release of POSYDON has expanded its capabilities to cover a diverse range of metallicities. Specifically, this version allows for population synthesis of massive binary stars across 8 different metallicities, ranging from \(10^{-4}\) times solar metallicity up to 2 times solar metallicity. This significant enhancement caters to a broader array of astrophysical scenarios and environments. - -v1.0.0 -~~~~~~ -**Release Date:** [6 feb. 2023] - -**Description:** -The v1.0.0 marked the first stable release of POSYDON. It was primarily focused on binary population synthesis of massive binary stars, with a target metallicity of solar. This version set the foundational framework upon which subsequent versions and features were built. - -Future Updates -~~~~~~~~~~~~~~ -Stay updated with the latest news and version releases by visiting our official website `POSYDON Official Website `_ and by following our repository on GitHub `POSYDON on GitHub `_. - diff --git a/docs/_source/release-notes/version-history.rst b/docs/_source/release-notes/version-history.rst deleted file mode 100644 index 073448d4e9..0000000000 --- a/docs/_source/release-notes/version-history.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. _version-history: - -Version History of POSYDON --------------------------- - -This page chronicles the detailed version history of POSYDON, including minor versions and patches. Each entry includes a brief summary of the changes, enhancements, and bug fixes made in that release. - -v2.0.0 -~~~~~~ -**Release Date:** [TODO add date here] - -**Summary:** -- Introduced capability for population synthesis across 8 different metallicities, from \(10^{-4}\) times solar metallicity up to 2 times solar metallicity. -- [Other features or improvements made in v2.0.0] -- Bug fixes: - - - [List of significant bug fixes] - -v1.0.0 -~~~~~~ -**Release Date:** [6 feb. 2023] - -**Summary:** -- First stable release of POSYDON. -- Focused on binary population synthesis of massive binary stars at solar metallicities. -- [Other features or improvements made in v1.0.0] -- Bug fixes: - - - [List of significant bug fixes] - -Stay Informed -~~~~~~~~~~~~~ -For a broader overview of major version updates, refer to our :ref:`Major Updates ` page. To keep up with the latest changes and releases, visit our official website `POSYDON Official Website `_ and follow our repository on GitHub `POSYDON on GitHub `_. diff --git a/docs/_source/troubleshooting-faqs/code-questions.rst b/docs/_source/troubleshooting-faqs/code-questions.rst index a58291c52c..d22dc1e5d9 100644 --- a/docs/_source/troubleshooting-faqs/code-questions.rst +++ b/docs/_source/troubleshooting-faqs/code-questions.rst @@ -12,8 +12,7 @@ Frequently Asked Questions Start with the :ref:`binary population synthesis guide ` to learn how to run POSYDON. 2. **I'm getting an error when using the** ``where`` **parameter in the** ``select`` **function** - - If you're trying to select based on values in an unsupported column, you'll receive an error message like below. + If you're trying to select based on values in an unsupported column, you'll receive an error message like the one below. Only specific columns are supported for selection. These are limited to string columns, the indices, and the column names. Please perform the selection in-memory instead. @@ -23,35 +22,25 @@ Frequently Asked Questions The above code will produce the following error: - .. code-block:: python + .. code-block:: ValueError: The passed where expression S1_mass > 10 contains an invalid variable reference. All of the variable references - must be a reference to an axis (e.g. ‘index’ or ‘columns’), or a data_column. + must be a reference to an axis (e.g. 'index' or 'columns'), or a data_column. The currently defined references are: index, columns, state, event, step_names, S1_state, S2_state 3. **How should I tune my memory usage for a population synthesis run?** - - A population run requires a bare minimum of 4GB of memory per CPU. - However, this restricts the number of binaries you can keep in memory and requires a :code:`dump_rate < 1000` to keep the memory usage low, which slows down the simulation. - - As such, 5GB per CPU is a better starting point. This allows you to keep more binaries in memory and increases the speed of the population synthesis run. - The figure below can be used to fine-tune your memory usage. - It shows the memory usage of two large population synthesis runs with a 7GB and a 8GB limit with a :code:`dump_rate` of 8000 and 10,000 binaries respectively. - - .. image:: ./large_pop_runs_memory.png - :alt: Memory usage of two large population synthesis runs. - :width: 700px + A population run requires a bare minimum of 8GB of memory per CPU. The DR2 POSYDON grids need to be loaded in completely. At the moment, the binary grids are loaded in at startup, but single star models are loaded when needed. + However, 8GB allows you to run a small population, but it restricts the number of binaries you can keep in memory and requires a :code:`dump_rate < 1000` to keep the memory usage low, which slows down the simulation. - The memory usage of the 8,000 :code:`dump_rate` run is stable at around 6GB, while the 10,000 :code:`dump_rate` run is stable at around 6.8GB. + As such, 9GB or 10GB per CPU is a better starting point. This allows you to keep more binaries in memory and increases the speed of the population synthesis run and work well with with a :code:`dump_rate` of 5.000 binaries. In general, the :code:`dump_rate` should be at least 500 for populations of 100,000 binaries or more. Setting a very low :code:`dump_rate` for larger populations can potentially introduce I/O issues during the reading, writing, and merging of output files. 4. **What should the walltime and job array size be for my population synthesis run?** - The :code:`walltime` and job array size are dependent on the number of binaries you want to simulate and the memory usage of the simulation. The job array size should be set such that the number of binaries per job is at least 1000, since there's a minimum overhead per job due to loading the grids. @@ -65,43 +54,39 @@ Frequently Asked Questions .. note:: The processing time increases if you make the :code:`dump_rate` too low due to many I/O operations. -5. **I am unable to open HDF5 files created by POSYDON. What should I do?** +5. **I am unable to open HDF5 files created by POSYDON. What should I do?** If you're on a Mac, there might be an issue with the HDF5 installation. Make sure you have the :code:`hdf5` and :code:`pytables` packages installed through conda in your environment with :code:`conda install hdf5 pytables` before running POSYDON! - Although they are dependencies of POSYDON, sometimes they're not installed correctly on Mac. - -6. **Are there any examples or tutorials available?** - Yes, you can check our :ref:`roadmap ` for tutorials related to different POSYDON components, including population synthesis, creating core datasets, and running your own MESA grids with POSYDON. + Although they are dependencies of POSYDON, sometimes they are not installed correctly on Mac. -7. **Can I run POSYDON on an HPC facility?** +6. **Can I run POSYDON on an HPC facility?** Absolutely! Refer to `our HPC guide <../tutorials-examples/population-synthesis/pop_syn.ipynb>`_ for detailed instructions on running POSYDON in an HPC environment. -8. **Help, I'm stuck! Where can I get support?** - Please check `our email group `_ if your question hasn't been answered yet. - Otherwise, please email us at posydon-users [at] googlegroups.com +7. **Help, I'm stuck! Where can I get support?** + Please check if your question has been answered already on the the POSYDON `Github Discussions `_. + If it has not yet been answered, please make a new discussion with your question and a detailed description of your problem. + + Are you unable to post on Github? Please check `our email group `_ or email us at posydon-users [at] googlegroups.com -9. **How can I stay updated with the latest features and updates?** +8. **How can I stay updated with the latest features and updates?** You can regularly visit our `official website `_ for news and updates. -10. **I've come across a FAILED binary. What does this mean?** - A :code:`FAILED` binary has encountered an error during the simulation due to POSYDON being unable to evolve it. This can be due to a variety of reasons: +9. **I've come across a FAILED binary. What does this mean?** + A :code:`FAILED` binary has encountered an error during the simulation because POSYDON was unable to evolve it. This can be due to a variety of reasons: - - The evolutionary state of the binary is not represented in the currently-supported stellar evolution grids. For example, we do not have a grid for Roche lobe overflow between two helium stars. + - The evolutionary state of the binary is not represented in the currently supported stellar evolution grids. For example, we do not have a grid for Roche lobe overflow between two helium stars. - The binary has masses outside the grid range. For example, the HMS-HMS grid does not contain binaries with a secondary mass below 0.5. - - The binary could not be matched to single star or a binary due to a too large matching error. + - The binary could not be matched to a single star or a binary due to a too large matching error, preventing further evolution. -11. **What approximations does POSYDON make?** +10. **What approximations does POSYDON make?** This is a complex question and the best answer is provided in the POSYDON papers: `Fragos et al. (2023) `_ and `Andrews et al. (submitted) `_. Additional Resources ~~~~~~~~~~~~~~~~~~~~ - -1. **User Guide**: For detailed instructions on all features of POSYDON, visit our comprehensive :ref:`roadmap `. - +1. **Examples and Tutorials**: Learn by doing! 2. **API Reference**: Dive deep into the functionality provided by POSYDON with our :ref:`API Reference `. - -3. **Examples and Tutorials**: Learn by doing! Visit :ref:`our roadmap page ` for hands-on learning. +3. **Github Discussions**: Engage with the community, ask questions, and share your experiences on our `GitHub Discussions `_ page. Still Have Questions? ~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/_source/troubleshooting-faqs/installation-issues.rst b/docs/_source/troubleshooting-faqs/installation-issues.rst index a81a53dd98..aeed294d70 100644 --- a/docs/_source/troubleshooting-faqs/installation-issues.rst +++ b/docs/_source/troubleshooting-faqs/installation-issues.rst @@ -1,17 +1,14 @@ .. _installation-issues: -Installation Issues +Common Installation Issues ------------------- From time to time, users might encounter issues during the installation of POSYDON. This page aims to address common installation problems and offer solutions. If your problem isn't covered here, please `report the issue `_ so we can assist you and possibly update this page for the benefit of others. -Common Installation Issues -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. **Slow `conda` solving: +1. **Slow `conda` solving:** `conda` can be very slow and sometimes gets stuck on "Verifying transaction" or "Executing transaction", especially when installing packages on a cluster. -It creates many small files, which can be difficult for HPC clusters to solve. +It creates many small files, which can be difficult for HPC clusters to handle. One way to speed up the installation is to use the `mamba` package manager, which is a drop-in replacement for `conda` but is much faster (`click here for more details `_). Please proceed at your own discretion, as this has not been fully vetted. Alternatively, you can install the `libmamba` solver to speed up the solving process for new installations in a `conda` environment. To install the `libmamba` solver, run the following command in your `conda` environment of choice or `base` `conda` environment: @@ -21,7 +18,7 @@ conda install conda-libmamba-solver ``` This will install the `libmamba` solver, which is a drop-in replacement for the default `conda` solver. -This should speed up solving the environment and installing packages, but is not guaranteed to work in all cases. +This should speed up solving the environment and installing packages but is not guaranteed to work in all cases. 2. **Failed Dependencies**: - **Description**: Sometimes, certain dependencies might fail to install or conflict with pre-existing ones. @@ -34,18 +31,9 @@ This should speed up solving the environment and installing packages, but is not - **Solution**: Refer back to the :ref:`installation guide ` to ensure all post-installation steps are followed. 4. **Insufficient Storage Space**: - - **Description**: POSYDON requires around 40GB of free storage space for the lite MESA simulation library and related files. + - **Description**: POSYDON requires around 140GB of free storage space for the down-sampled MESA simulation library and related files. - **Solution**: Ensure you have enough space on your installation drive. Delete unnecessary files or consider using a larger storage solution. -5. **Proxy or Network Issues**: - - **Description**: Installation might fail if you're behind a strict network proxy or firewall. - - **Solution**: If you're using ``conda``, you can set proxy settings using the ``--proxy`` flag. For pip, you can use the ``--proxy`` flag as well. - - -6. **Error with Experimental Visualization Libraries**: - - **Description**: Issues after installing the experimental visualization libraries with ``pip install ".[vis]"``. - - **Solution**: These libraries are experimental. Please ensure you have all required system dependencies. Consider reaching out to our support channels for more guidance or troubleshooting. - Troubleshooting Tips ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/_source/tutorials-examples/MESA-grids/1_hms_hms.ipynb b/docs/_source/tutorials-examples/MESA-grids/1_hms_hms.ipynb index 188075f0ac..b826aa98ac 100644 --- a/docs/_source/tutorials-examples/MESA-grids/1_hms_hms.ipynb +++ b/docs/_source/tutorials-examples/MESA-grids/1_hms_hms.ipynb @@ -11,9 +11,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you haven't done it already, export all the relevan POSYDON and MESA enviroment variables.\n", + "If you haven't done it already, export all the relevant POSYDON and MESA environment variables.\n", "\n", - "Tip: create a file called `.bash_POSYDON_MESA` with the following content (please adopt the PATHs to your system):\n", + "Tip: create a file called `.bash_POSYDON_MESA` with the following content (please adapt the PATHs to your system):\n", "```bash\n", "export PATH_TO_POSYDON=~/software/POSYDON/\n", "export PATH_TO_POSYDON_DATA=~/software/POSYDON/data/\n", @@ -33,7 +33,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you haven't done it already, please download the `development` branch of the `POSYDON-MESA-INLISTS` submodule, see [Architecting MESA Simulation Grids with POSYDON](https://posydon.org/POSYDON/tutorials-examples/MESA-grids/running-grids.html). This is the branch that contains the simulation properties of all POSYDON MESA grids." + "If you haven't done it already, please download the `development` branch of the `POSYDON-MESA-INLISTS` submodule, see [Architecting MESA Simulation Grids with POSYDON](running-grids.rst). This is the branch that contains the simulation properties of all POSYDON MESA grids." ] }, { @@ -82,15 +82,15 @@ "metadata": {}, "source": [ "Open the file and edit the following parameters:\n", - "- `user=your_username`\n", + "- `user=your_username (can be skipped if not run via SLURM)`\n", "- `partition=the_slurm_partition_to_run_on` (can be skipped if not run via SLURM)\n", "- `account=your_slurm_account` (can be skipped if not run via SLURM)\n", - "- `email=your_email`\n", + "- `email=your_email` (can be skipped if not run via SLURM)\n", "- `posydon_github_root=your_path_to_POSYDON`\n", "- `grid=your_grid_csv_file` (will get created in the next section)\n", "\n", - "The `scanrio` parameter of the ini file is where the magic happens. This parameter let's us decide which simulation we want to run. The syntax is the following:\n", - "- the first argument is the name of the submodule `posydon` which points to the POSYDON-MESA-INLISTS submodule (other options are avaialble, e.g. `user` which points to a private submodule)\n", + "The `scenario` parameter of the ini file is the main parameter that controls the simulation setup. This parameter let's us decide which simulation we want to run. The syntax is the following:\n", + "- the first argument is the name of the submodule `posydon` which points to the POSYDON-MESA-INLISTS submodule (other options are available, e.g. `user` which points to a private submodule)\n", "- the second argument is the name of the branch and commit of the submodule we want to use connected by a dash `-`. In this case we want to use the `development` branch of the submodule and it's latest commit, so we write `development-c1f75acacc3bd645197c5d3e920a121e94d95a80`\n", "- the third argument is the name of the simulation grid we want to run, in this case we want to run a HMS-HMS grid. The name of the grid is `HMS-HMS`.\n", "\n", @@ -98,14 +98,14 @@ "\n", "The `zams_file` points to the ZAMS model used to generate the HMS stars in the binary system. POSYDON v2.0.0 supports 8 different metallicities, here we would like to use the 0.1Zsun POSYDON MESA ZAMS model `${posydon_github_root}/grid_params/POSYDON-MESA-INLISTS/r11701/ZAMS_models/zams_z1.42m3_y0.2511.data`\n", "\n", - "The last parameter of this file is `grid` and points to the `csv` file containing the initial coditions of the grid. In this case we want to run a `HMS-HMS` grid consisting of one system, so we set `grid = grid_test.csv` and generate such file specifying the metallicity, the two star masses and the initial orbital period." + "The last parameter of this file is `grid` and points to the `csv` file containing the initial conditions of the grid. In this case we want to run a `HMS-HMS` grid consisting of one system, so we set `grid = grid_test.csv` and generate such file specifying the metallicity, the two star masses and the initial orbital period." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Craeting the Initial Simulation Points CSV File" + "## Creating the Initial Simulation Points CSV File" ] }, { @@ -133,6 +133,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "The above script will create a .csv file for the parameters of a MESA 'grid' with one row, i.e. practically with one binary system.\n", "You should now have the following files in your working directory:" ] }, @@ -157,7 +158,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's now use the magic of POSYDON to set up the MESA simulation. In your termianal run\n", + "Let's now use POSYDON to set up the MESA simulation. In your terminal run\n", "\n", "```bash\n", "posydon-setup-grid --grid-type fixed --inifile HMS-HMS.ini --submission-type slurm\n", @@ -189,7 +190,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Running the MESA Model and Exploring the Simulaiton Output" + "## Running the MESA Model and Exploring the Simulation Output" ] }, { @@ -639,7 +640,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Congratulation, you now know how to harvast the power of POSYDON to run a grid of simulations on a supercomputer!" + "The above output is standard output of the beginning of a MESA binary run.\n", + "Congratulations, you now know how to leverage POSYDON to run a grid of MESA simulations on a computer cluster!" ] } ], diff --git a/docs/_source/tutorials-examples/MESA-grids/laptop.ipynb b/docs/_source/tutorials-examples/MESA-grids/laptop.ipynb index 80e8b259bd..4c970dea0b 100644 --- a/docs/_source/tutorials-examples/MESA-grids/laptop.ipynb +++ b/docs/_source/tutorials-examples/MESA-grids/laptop.ipynb @@ -11,7 +11,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For debugging purposes you might want to run a single binary on your laptop to test a quick modification of the inlists, run_star_extras.f or run_binary_extras.f. Assume we want to run the same binary as in the previous tutorial. To use the POSYDON API to run a single binary, you can use the following command:\n", + "For debugging purposes you might want to run a single binary on your laptop to test a quick modification of the inlists, run_star_extras.f or run_binary_extras.f. Assume we want to run the same binary as in the previous tutorial, having already created a .ini initialization file and pointing to a .csv grid file (the details on the .ini that had the comment `(can be skipped if not run via SLURM)` do not matter as we will not run it in the cluster.)\n", + "To use the POSYDON API to run a single binary, you can use the following command:\n", "\n", "```bash\n", "posydon-setup-grid --grid-type fixed --inifile HMS-HMS.ini --submission-type shell\n", @@ -445,7 +446,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Congratulation, you now know how to harvast the power of POSYDON to run a grid of simulations on a supercomputer!" + "The above output is standard output of the beginning of a MESA binary simulation.\n", + "Congratulation, you now know how to leverage POSYDON to run one MESA simulation locally at your laptop!" ] } ], diff --git a/docs/_source/tutorials-examples/MESA-grids/running-grids.rst b/docs/_source/tutorials-examples/MESA-grids/running-grids.rst index a7d08ff68b..472b971321 100644 --- a/docs/_source/tutorials-examples/MESA-grids/running-grids.rst +++ b/docs/_source/tutorials-examples/MESA-grids/running-grids.rst @@ -3,7 +3,7 @@ Architecting MESA Simulation Grids with POSYDON =============================================== -Boost your MESA simulation grid prowess by diving into the intricacies of POSYDON's grid creation techniques and the dedicated MESA configuration repository. Harness the power of our API to craft precise and customized grids. +You can use POSYDON framework to run your own, customized MESA simulations of single and binary systems. Here we will go through POSYDON's dedicated MESA configuration repository and infrastructure for one or even big grids of MESA runs. Getting Started Tutorials ------------------------- @@ -23,7 +23,7 @@ Similarly to the POSYDON code repository, there is a `main` branch that points t cd grid_params/POSYDON-MESA-INLISTS/ git checkout development -To follow the next step of the tutorial, you will need to have MESA installed on your machine. If you do not have MESA installed, please follow the instructions on the `MESA website `_. +To follow the next step of the tutorial, you will need to have a MESA version compatible with POSYDON installed on your machine. If you do not have MESA installed, please follow the instructions on the `MESA website `_. .. warning:: @@ -33,18 +33,18 @@ To follow the next step of the tutorial, you will need to have MESA installed on I. Running your first MESA simulation using POSYDON ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Immerse yourself in our Jupyter Notebook which guides you through our dedicated MESA submission API, showcasing how to simulate a HMS-HMS binary systems. +The following Jupyter Notebook guides you through our dedicated MESA submission API, showcasing how to simulate a HMS-HMS binary systems. -The first version of this tutorial showcases how to run the simulation using SLURM on HPC. +The first version of this tutorial showcases how to run the simulation using SLURM on HPC. .. toctree:: - + 1_hms_hms In case you want to run the simulation locally, you can follow the second version of this tutorial. .. toctree:: - + laptop To gather more information about the MESA simulation submission API ini file, please refer to the :ref:`MESA Grids API ` section of the documentation. @@ -55,7 +55,7 @@ II. Running your first MESA grid simulation using POSYDON The following tutorial will show you how to run a grid of MESA simulations using POSYDON. The grid will consist of 100 simulations of HMS-HMS binary systems at 0.1Zsun metallicity in the mass ratio slice 0.7 each with different primary masses and orbital periods. The simulations will be run on a HPC using SLURM. .. toctree:: - + running_a_grid Now that you have run your first grid, you can process, visualize and explore the results using the POSYDON post-processing pipeline API. TODO: link @@ -66,7 +66,7 @@ III. Running single stars with POSYDON This tutorials shows how to run a grid of single star simulations using POSYDON. .. toctree:: - + running_single_hms We show how to export EEPs and make a PSyGrid object in this tutorial TODO: link the advanced tutorial of step 2. @@ -81,9 +81,13 @@ Step-by-step guide on how to leverage our MESA simulation submission API for cre Dive deeper into advanced techniques for grid creation, by layering MESA and POSYDON default inlist parameters and customizing your inlists variables on top to your needs. -.. toctree:: - - notebook: Creating and Customizing MESA Grids layer by layer +.. warning:: + + This feature is only partially implemented. Please contact us if you require more information. + +.. .. toctree:: + +.. notebook: Creating and Customizing MESA Grids layer by layer Running a dynamically sampled grid of MESA simulations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -92,15 +96,10 @@ Running a dynamically sampled grid of MESA simulations This feature is experimental. Please contact us if you encounter any issues. -Provided a sparsed, rectilinearly sampled MESA grid, POSYDON allows to dynamically sample the parameter space to henence the coverage of the paramter space to imporve classification and interpolation accuracy. - -.. toctree:: - - dynamic +Provided a sparse, rectilinearly sampled MESA grid, POSYDON allows to dynamically sample the parameter space to enhance the coverage of the parameter space to improve classification and interpolation accuracy. Support & Feedback ------------------ Encountered obstacles or have questions? Ensure you consult our [FAQ](link-to-FAQ) or drop by the [contact-information.rst](contact-information.rst) page. - diff --git a/docs/_source/tutorials-examples/MESA-grids/running_a_grid.ipynb b/docs/_source/tutorials-examples/MESA-grids/running_a_grid.ipynb index 0181545089..af5c42c32e 100644 --- a/docs/_source/tutorials-examples/MESA-grids/running_a_grid.ipynb +++ b/docs/_source/tutorials-examples/MESA-grids/running_a_grid.ipynb @@ -4,14 +4,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Running a real POSYDON MESA HMS-HMS gird" + "# Running a real POSYDON MESA HMS-HMS grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "It is now time to become a POSYDON MESA architect. In this notebook, we will run a real HMS-HMS grid. We will run a downsampled version (100 models) of the HMS-HMS, 0.1Zsun, q=0.7 grid slice, using the same `HMS-HMS.ini` file as in the [first notebook](https://posydon.org/POSYDON/tutorials-examples/MESA-grids/1_hms_hms.html), where we run one binary." + "It is now time to become a POSYDON MESA architect. In this notebook, we will run a real HMS-HMS grid. We will run a downsampled version (100 models) of the HMS-HMS, 0.1Zsun, q=0.7 grid slice, using the same `HMS-HMS.ini` file as in the [first notebook](1_hms_hms.ipynb), where we ran one binary." ] }, { @@ -96,7 +96,7 @@ "./run_grid.sh\n", "```\n", "\n", - "Sit back relax, and wait for the grid to finish." + "Sit back, relax, and wait for the grid to finish." ] }, { diff --git a/docs/_source/tutorials-examples/MESA-grids/running_single_hms.ipynb b/docs/_source/tutorials-examples/MESA-grids/running_single_hms.ipynb index 959d9eae92..7d787c8c7b 100644 --- a/docs/_source/tutorials-examples/MESA-grids/running_single_hms.ipynb +++ b/docs/_source/tutorials-examples/MESA-grids/running_single_hms.ipynb @@ -46,7 +46,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We now need to generate the `grid.csv` containing 10 initial values for 10 stars, in this case the initial values are just the mass and metallicity paramters. You can do this by running the following code cell. The code that follows was copied from `$PATH_TO_POSYDON/grid_params/POSYDON-MESA-INLISTS/r11701/running_scripts/parameter_space_v2/create_csv.ipynb` an adopted to the needs here." + "We now need to generate the `grid.csv` containing 10 initial values for 10 stars, in this case the initial values are just the mass and metallicity parameters. You can do this by running the following code cell. The code that follows was copied from `$PATH_TO_POSYDON/grid_params/POSYDON-MESA-INLISTS/r11701/running_scripts/parameter_space_v2/create_csv.ipynb` and adapted to the needs here." ] }, { @@ -111,7 +111,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Runing the MESA Grid with the POSYDON Submission Script" + "## Running the MESA Grid with the POSYDON Submission Script" ] }, { diff --git a/docs/_source/tutorials-examples/generating-datasets/generating-datasets.rst b/docs/_source/tutorials-examples/generating-datasets/generating-datasets.rst index f899f5669a..0e22dcf205 100644 --- a/docs/_source/tutorials-examples/generating-datasets/generating-datasets.rst +++ b/docs/_source/tutorials-examples/generating-datasets/generating-datasets.rst @@ -20,16 +20,16 @@ Dive into our Jupyter Notebook that will show you how to create a MESA PSyGrid d To learn more about the PSyGrid object or the Processing Pipeline API ini file, check out the [API Documentation](api.rst). -II. The Full POSYDON Processing Pipeline Experince -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +II. The Full POSYDON Processing Pipeline Experience +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -From A to Z, this tutorial shows you how to process, concatenate, downsample, plot the grids and check failure rate, train the interpolation object and export the POSYDON dataset for population synthesis. +From A to Z, this tutorial shows you how to process, concatenate, down sample, plot grids and check failure rates, train interpolation objects and export the POSYDON dataset for population synthesis. .. toctree:: - run_full_piepeline + run_full_pipeline -Congratulations! You now master the POSYDON Processing Pipeline. To learn more about the PSyGrid object or the Processing Pipeline API ini file, check out the [API Documentation](api.rst) and the indepth components overview. +Congratulations! You've now mastered the POSYDON Processing Pipeline. To learn more about the PSyGrid object or the Processing Pipeline API ini file, check out the [API Documentation](api.rst) and the indepth components overview. .. _plot_1d: @@ -71,11 +71,11 @@ Learn how to export MESA simulation points to rerun using the Processing Pipelin Export Single Star PSyGrid Datasets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Learn how to export single star PSyGrid datasets. This is an advaced tuotorial because it requires knowledge about the EEP code. +Learn how to export single star PSyGrid datasets. This is an advanced tutorial because it requires knowledge about the EEP code. .. note:: - POSYDON v2.0.0 does not embed a python interface to compute EEPs but relyes on the Fortran code of Aaron Dotter (2016). Feature POSYDON code releases will include a python interface to compute EEPs and embed the export of single stellar grid into the post-processig pipeline. + POSYDON v2.0.0 does not embed a python interface to compute EEPs but relies on the Fortran code of Aaron Dotter (2016). Future POSYDON code releases will include a python interface to compute EEPs and embed the export of single stellar grid into the post-processing pipeline. .. toctree:: diff --git a/docs/_source/tutorials-examples/generating-datasets/plot_2D.ipynb b/docs/_source/tutorials-examples/generating-datasets/plot_2D.ipynb index 34a99c6add..f8ce8373bf 100644 --- a/docs/_source/tutorials-examples/generating-datasets/plot_2D.ipynb +++ b/docs/_source/tutorials-examples/generating-datasets/plot_2D.ipynb @@ -13,7 +13,7 @@ "source": [ "This tutorial shows you how to plot single and binary stellar tracks using the `plot2D` visualization library. The `plot2D` method supports either a MESA grid which samples the 2D parameter space of binary initial conditions in a 2D, 3D or 4D MESA space, e.g., star_1_mass, star_2_mass (or mass ratio), period_days, metallicity. In the 3D and 4D cases the MESA grid will be sliced along the dimensions specified by the user.\n", "\n", - "If you haven't done it already, export the environemnt variables." + "If you haven't done it already, export the environment variables." ] }, { @@ -46,7 +46,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Example: the HMS-HMS gird" + "## Example: the HMS-HMS grid" ] }, { @@ -66,8 +66,8 @@ "from posydon.config import PATH_TO_POSYDON_DATA\n", "from posydon.grids.psygrid import PSyGrid\n", "\n", - "path_to_gird = os.path.join(PATH_TO_POSYDON_DATA, 'POSYDON_data/HMS-HMS/1e-01_Zsun.h5')\n", - "grid = PSyGrid(path_to_gird)\n", + "path_to_grid = os.path.join(PATH_TO_POSYDON_DATA, 'POSYDON_data/HMS-HMS/1e-01_Zsun.h5')\n", + "grid = PSyGrid(path_to_grid)\n", "grid.load()" ] }, @@ -75,14 +75,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### MESA Termination Flags 1 and 2 Combied" + "### MESA Termination Flags 1 and 2 combined" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This plot summarise the binary evolutioary simulation. " + "This plot summarizes the binary evolutionary simulation. " ] }, { @@ -409,14 +409,12 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "The missig points in the above plots are NaN values associated to disruped stars due to pair instability supernovae (PISN). \n", + "The missing points in the above plots are NaN values associated with disrupted stars due to pair instability supernovae (PISN). \n", "\n", - "To display supernova type (SN_type) and compact object type (CO_type) are stored as strings in the dataset. These quantities can be plotted using the ``termination_flag`` option of ``plot2D``, as" + "The supernova type (SN_type) and compact object type (CO_type) are stored as strings in the dataset. These quantities can be plotted using the ``termination_flag`` option of ``plot2D``, as" ] }, { diff --git a/docs/_source/tutorials-examples/generating-datasets/run_full_piepeline.ipynb b/docs/_source/tutorials-examples/generating-datasets/run_full_pipeline.ipynb similarity index 99% rename from docs/_source/tutorials-examples/generating-datasets/run_full_piepeline.ipynb rename to docs/_source/tutorials-examples/generating-datasets/run_full_pipeline.ipynb index 434ccc7db1..f3252e0232 100644 --- a/docs/_source/tutorials-examples/generating-datasets/run_full_piepeline.ipynb +++ b/docs/_source/tutorials-examples/generating-datasets/run_full_pipeline.ipynb @@ -4,27 +4,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# The fullt POSYDON Processing Pipeline experince" + "# The full POSYDON Processing Pipeline experience" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "From A to Z: in this tutorial you learn to run all steps of the POSYDON post processing pipeline. This includes:\n", - "- step 1: create the h5 file of each MESA grid, this include the base grid and any expantion or rerun\n", - "- step 2: stack the grids togeather by concatenating them in the corret order\n", - "- step 2 plot: plot 2D grid splices to visaulize termination flags, final values and other properties\n", + "From A to Z: this tutorial shows you how to run all steps of the POSYDON post processing pipeline. This includes:\n", + "- step 1: create the h5 file of each MESA grid, this includes the base grid and any expansion or rerun\n", + "- step 2: stack the grids together by concatenating them in the correct order\n", + "- step 2 plot: plot 2D grid slices to visualize termination flags, final values and other properties\n", "- step 2 check failure rate: check the failure rate of the grid\n", "- step 3: compute post processing values on the ORIGINAL grid (these values are stored as additional final values columns)\n", "- step 4: train the initial final interpolators and classifiers\n", "- step F: export the entire POSYDON dataset in the working directory \n", "\n", - "All these steps are linked togeather and, when possible, they will be executed in parrallel, else sequencially.\n", + "All these steps are linked together and, when possible, they will be executed in parallel, or else sequentially.\n", "\n", - "We support two types of compression LITE and ORIGINAL, the former resamples the stellar histories, the binary history and the fial stellar profile. Plase refer to the POSYDON v1.0.0 paper for more details.\n", + "We support two types of compression LITE and ORIGINAL, the former resamples the stellar histories, the binary history and the final stellar profile. Please refer to the POSYDON v1.0.0 paper for more details.\n", "\n", - "We illustrate this example using the 100 HMS-MHS MESA models which we refer to as the `test_grid` and the opacity_max rerun layer, `rerun_opacity_max_text_grid`, we generated by rerunning failed models in the `test_grid` with a lower hier opacity max limit as illustrative `fix`." + "We illustrate this example using the 100 HMS-HMS MESA models which we refer to as the `test_grid` and the opacity_max rerun layer, `rerun_opacity_max_test_grid`, we generated by rerunning failed models in the `test_grid` with a lower opacity max limit as illustrative `fix`." ] }, { @@ -38,16 +38,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The following cell export the entire `pipeline.ini`, the purpuse of this tutorial is to get you acquinted with the pipeline and not to teach you how to export the entire POSYDON v2.0.0 dataset. \n", + "The following cell exports the entire `pipeline.ini`, the purpose of this tutorial is to get you acquainted with the pipeline and not to teach you how to export the entire POSYDON v2.0.0 dataset. \n", "\n", - "EXTRA: If you are a POSYDON developer or just a curius person, you can find the full POSYDON v2.0.0 `pipeline.ini` file in `$PATH_TO_POSYDON/grid_params/pipeline_quest.ini` and `$PATH_TO_POSYDON/grid_params/pipeline_yggdrasil.ini`. At the time of writing this tutorial we support 5 layers of reruns and store the raw MESA data of v2.0.0 across two HPC facility. Exporting the entire v2.0.0 dataset from start to finish might require more than 24 hours computing time. !!! WARNING !!! before exportig the entire POSYDON v2.0.0 dataset, make sure you know what you are doing! \n", + "EXTRA: If you are a POSYDON developer or just a curious person, you can find the full POSYDON v2.0.0 `pipeline.ini` file in `$PATH_TO_POSYDON/grid_params/pipeline_quest.ini` and `$PATH_TO_POSYDON/grid_params/pipeline_yggdrasil.ini`. At the time of writing this tutorial we support 5 layers of reruns and store the raw MESA data of v2.0.0 across two HPC facilities. Exporting the entire v2.0.0 dataset from start to finish might require more than 24 hours computing time. !!! WARNING !!! before exporting the entire POSYDON v2.0.0 dataset, make sure you know what you are doing! \n", "\n", - "Now sit back relax and enjoy the POSYDON experince!" + "Now sit back, relax and enjoy the POSYDON experience!" ] }, { "cell_type": "code", - "execution_count": 67, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -69,7 +69,7 @@ "\n", "[pipeline setup]\n", " PATH_TO_GRIDS = '/srv/beegfs/scratch/shares/astro/posydon/POSYDON_GRIDS_v2/POSYDON_data/230914/POSYDON_data/tutorials/processing-pipeline/'\n", - " VERSION = '' # to have a verion below the grid type level\n", + " VERSION = '' # to have a version below the grid type level\n", " PATH = '.' # working dir\n", " VERBOSE = True\n", " \n", @@ -221,7 +221,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We are all set, let's setup the pipeline and run it!" + "We are all set, let's set up the pipeline and run it!" ] }, { @@ -669,9 +669,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this tutorial we did not create a randomly sampled grid to test the accuracy of the interolators. If you create a radomly sampled grid, then the pipeline will also export 2D error maps evaluated on the randomly sampled gird. This is left as an exercise to the reader. \n", + "In this tutorial we did not create a randomly sampled grid to test the accuracy of the interpolators. If you create a randomly sampled grid, then the pipeline will also export 2D error maps evaluated on the randomly sampled grid. This is left as an exercise to the reader. \n", "\n", - "TODO: add the automatic export of violin plots and cofiusion matrices to the plotting.\n", + "TODO: add the automatic export of violin plots and confusion matrices to the plotting.\n", "\n", "Check that you obtained the dataset." ] diff --git a/docs/_source/tutorials-examples/population-synthesis/10_binaries_pop_syn.ipynb b/docs/_source/tutorials-examples/population-synthesis/10_binaries_pop_syn.ipynb index 82e1ba3c04..555444e604 100644 --- a/docs/_source/tutorials-examples/population-synthesis/10_binaries_pop_syn.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/10_binaries_pop_syn.ipynb @@ -12,14 +12,14 @@ "metadata": {}, "source": [ "**Tutorial goal:**\n", - "In this tutorial, we will run a small population of 10 binaries locally, and explore how to manipulate the output data from your population." + "In this tutorial, we will run a small population of 10 binaries locally and explore how to manipulate the output data from your population." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "If you haven't done so yet, export the path POSYDON environment variables.\n", + "If you haven't done so yet, export the POSYDON path environment variables.\n", "Set these parameters in your `.bash_profile` or `.zshrc` if you use POSYDON regularly." ] }, @@ -43,30 +43,30 @@ "## Initialisation File\n", "\n", "To run population synthesis with POSYDON, a `population_params.ini` file is required.\n", - "This file described how the stellar population is created and what prescriptions and parameters are implemented in specific steps.\n", + "This file describes how the stellar population is created and what prescriptions and parameters are implemented in specific steps.\n", "\n", - "POSYDON comes with a default `population_params_default.ini` file found at `PATH_TO_POSYDON/posydon/popsyn` or have a look [here](../../../../../posydon/popsyn/population_params_default.ini).\n", + "POSYDON comes with a default `population_params_default.ini` file found at `$PATH_TO_POSYDON/posydon/popsyn`.\n", "\n", "
\n", " \n", " **Default values**\n", " \n", - " The default values in the population_params.ini file included in POSYDON \n", - " have not been calibrated, validated, or are used by the POSYDON team. They are\n", - " often an ad-hoc choice, and the user should carefully consider the values\n", + " The default values in the `population_params_default.ini` file included in POSYDON \n", + " have not been calibrated, validated, or used by the POSYDON team. They are\n", + " often an ad hoc choice, and the user should carefully consider the values\n", " of each parameter for their science case.\n", "\n", "
\n", "\n", - "The parameters in this file can be found [here](../../components-overview/pop_syn/population_params.html), and is split in three parts. You can find more details about their properties by clicking on their links.\n", + "The parameters in this file can be found [here](../../components-overview/pop_syn/population_params.rst) and are split into three parts. You can find more details about their properties by clicking on their links.\n", "\n", "1. **[SimulationProperties](../../components-overview/pop_syn/population_params.rst#simulationproperties):**\n", - " - describe the properties and parameters of different steps in the evolution of a binary system.\n", + " - Describes the properties and parameters of different steps in the evolution of a binary system.\n", "2. **[BinaryPopulation](../../components-overview/pop_syn/population_params.rst#binarypopulation):** \n", " - Parameters of the initial sampling of the binary population, such as initial mass function, period distribution, and metallicity.\n", - " - contains parameters on how the population is run, such as how many binaries are kept in memory.\n", + " - Contains parameters on how the population is run, such as how many binaries are kept in memory.\n", "3. **[SavingOutput](../../components-overview/pop_syn/population_params.rst#saving-output)**\n", - " - Describes what data from the binary and each individual star is written to the output file.\n", + " - Describes what data from the binary and each individual star are written to the output file.\n", "\n", "We will copy the default population run parameter file to the current folder." ] @@ -106,8 +106,8 @@ "\n", "\n", "\n", - "The copied `population_params.ini` contains the parameters to run 10 binaries at the one of the metallicities currently supported by POSYDON.\n", - "Open the `population_params.ini` file, and go down to the **BinaryPopulation** section. This is where the properties of the simulation are set.\n", + "The copied `population_params.ini` contains the parameters to run 10 binaries at one of the metallicities currently supported by POSYDON.\n", + "Open the `population_params.ini` file and go down to the **BinaryPopulation** section. This is where the properties of the simulation are set.\n", "There you should find:\n", "\n", "```\n", @@ -117,9 +117,9 @@ "number_of_binaries = 10\n", "# int\n", "```\n", - "Let's leave the settings for now, but you can always change these if you would like a different metallicity or larger population. For the latter, also see the [HPC tutorial](pop_syn.ipynb).\n", + "Let's leave the settings for now, but you can always change these if you would like a different metallicity or a larger population. For the latter, also see the [HPC tutorial](pop_syn.ipynb).\n", "\n", - "If you like to run a small population in a notebook, you can use the `PopulationRunner` ([documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.PopulationRunner)) to do this. If you want to run a specific binary instead, have a look at the [Binary Tutorial](evolve_single_binaries.ipynb) instead. The `PopulationRunner` class takes the `population_params.ini` file and sets-up a multi-metallicity population run for inside a notebook.\n", + "If you would like to run a small population in a notebook, you can use the `PopulationRunner` ([documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.PopulationRunner)) to do this. If you want to run a specific binary instead, have a look at the [Binary Tutorial](evolve_single_binaries.ipynb). The `PopulationRunner` class takes the `population_params.ini` file and sets up a multi-metallicity population run for inside a notebook.\n", "\n", "It will create `BinaryPopulation`s ([documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.binarypopulation.BinaryPopulation)) for each metallicity defined in the `population_params.ini` file. In our case, we can check and see that a single `BinaryPopulation` is created.\n", "We can check if the metallicity and number of binaries are correctly set before starting our simulation. " @@ -151,7 +151,7 @@ "metadata": {}, "source": [ "\n", - "If you changed the parameters in the `population_params.ini` file, you should have the following output:\n", + "If you did not change the parameters in the `population_params.ini` file, you should have the following output:\n", "\n", "```\n", "> Number of binary populations: 1\n", @@ -159,11 +159,11 @@ "> Number of binaries: 10\n", "```\n", "\n", - "The `BinaryPopulation` class does the actual simulation setup and evolution of the binaries at a specific metallicity, but does not take of file cleanup after a succesfull population run. This is why `PopulationRunner` is used in a local environment. In a HPC facility, a setup script is available `posydon-popsyn setup` that will create the required folders and scripts to run a large population. See the [HPC tutorial](pop_syn.ipynb) for more information.\n", + "The `BinaryPopulation` class does the actual simulation setup and evolution of the binaries at a specific metallicity but does not take care of file cleanup after a successful population run. This is why `PopulationRunner` is used in a local environment. In an HPC facility, a setup script is available, `posydon-popsyn setup`, that will create the required folders and scripts to run a large population. See the [HPC tutorial](pop_syn.ipynb) for more information.\n", "\n", "For this tutorial, we set `verbose=True`, which shows you the progress of the population run.\n", - "This overwrites the population verbose set inside the `population_params.ini` file.\n", - "Now we are ready to evolve the binary population. This should take about 30 seconds depending on your machine." + "This overwrites the population verbose setting inside the `population_params.ini` file.\n", + "Now we are ready to evolve the binary population. This should take about 30 seconds, depending on your machine." ] }, { @@ -184,24 +184,24 @@ "\n", "## Inspecting the population: Population class\n", "\n", - "When you ran the population, you might have seen that a temporary folder with the name `1e+00_Zsun_batches` was created while the binaries were being evolved. This is a temporary folder in which populations are temporarly saved.\n", - "After the binary evolution has finished, the binaries in the folder are moved to a single file named `1e+00_Zsun_popululation.h5`. This is done automatically when you run a population using the `PopulationRunner` class.\n", - "When you run multiple metallicity a file will be created for each metallicity.\n", + "When you ran the population, you might have seen that a temporary folder with the name `1e+00_Zsun_batches` was created while the binaries were being evolved. This is a temporary folder in which populations are temporarily saved.\n", + "After the binary evolution has finished, the binaries in the folder are moved to a single file named `1e+00_Zsun_population.h5`. This is done automatically when you run a population using the `PopulationRunner` class.\n", + "When you run multiple metallicities, a file will be created for each metallicity.\n", "\n", - "The created file contains 3 main components:\n", + "The created file contains three main components:\n", "\n", - "1. **history:** the evolution of an individual binary in a pandas DataFrame\n", - "2. **oneline:** a single line to describe the initial and final conditions and some one-of parameters, such as the metallicity.\n", - "3. **mass_per_metallicity:** some metadata on the population, such as the total simulated mass, the actual underlying mass of the population, and the number of binaries in the file.\n", + "1. **history:** The evolution of an individual binary in a pandas DataFrame.\n", + "2. **oneline:** A single line to describe the initial and final conditions and some one-off parameters, such as the metallicity.\n", + "3. **mass_per_metallicity:** Some metadata on the population, such as the total simulated mass, the actual underlying mass of the population, and the number of binaries in the file.\n", "\n", - "The `Population` class provides an interface to these components in the file, such that you're able to share the populations runs and can work with large populations that do not fit in memory. We will now explore the population file using the `Population` class. You can find a more extensive description [here](../../components-overview/pop_syn/synthetic_population.rst) or look at the class [documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.Population).\n", + "The `Population` class provides an interface to these components in the file, such that you're able to share the population runs and can work with large populations that do not fit in memory. We will now explore the population file using the `Population` class. You can find a more extensive description [here](../../components-overview/pop_syn/synthetic_population.rst) or look at the class [documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.Population).\n", "\n", "\n", "
\n", "\n", "**Older Population Files**\n", "\n", - "If you're using older population files from before the Population class rework, you can make them compatible with the `Population` class by calling `Population(pop_file, metallicity,ini_file)`, where the `metallicity` is in solar units. You will only need to do this once; afterwards you can initialise the class like normal.\n", + "If you're using older population files from before the Population class rework, you can make them compatible with the `Population` class by calling `Population(pop_file, metallicity, ini_file)`, where the `metallicity` is in solar units. You will only need to do this once; afterwards, you can initialize the class as normal.\n", "\n", "
" ] @@ -221,14 +221,14 @@ "metadata": {}, "source": [ "\n", - "Let's start with the `pop.mass_per_metallicity`. \n", + "Let's start with `pop.mass_per_metallicity`. \n", "It contains some basic information about the population you've just created.\n", "\n", "1. The index (`metallicity`) is the metallicity of your population in solar units.\n", - "2. `simulated mass` the total ZAMS mass that has been evolved in the population.\n", - "3. `simulated_mass_single` the total ZAMS mass from initially single stars.\n", - "4. `simulated_mass_binaries` the total ZAMS mass from initially binaries.\n", - "5. `number_of_systems` shows the number of systems in the file.\n" + "2. `simulated mass`: The total ZAMS mass that has been evolved in the population.\n", + "3. `simulated_mass_single`: The total ZAMS mass from initially single stars.\n", + "4. `simulated_mass_binaries`: The total ZAMS mass from initially binary stars.\n", + "5. `number_of_systems`: Shows the number of systems in the file.\n" ] }, { @@ -247,12 +247,12 @@ "\n", "There are some additional metadata properties available, such as:\n", "\n", - "- `metallicities` the metallicity in absolute metallicity\n", - "- `solar_metallicities` the metallicities in the file in solar metallicity\n", - "- `number_of_systems` the total number of systems in the Population file\n", - "- `indices` the indices of the binaries in the file\n", - "- `columns` the columns available in the `history` and `oneline` dataframes\n", - "- `ini_params` the parameters from the `ini` file that describe the initial sampling of your population.\n" + "- `metallicities`: The metallicity in absolute metallicity.\n", + "- `solar_metallicities`: The metallicities in the file in solar metallicity.\n", + "- `number_of_systems`: The total number of systems in the Population file.\n", + "- `indices`: The indices of the binaries in the file.\n", + "- `columns`: The columns available in the `history` and `oneline` DataFrames.\n", + "- `ini_params`: The parameters from the `ini` file that describe the initial sampling of your population.\n" ] }, { @@ -271,7 +271,7 @@ "source": [ "## Population.history\n", "\n", - "`pop.history` contains the evolutionary histories of each binary, as it was evolved by POSYDON. You can find more information about this [here](../../components-overview/pop_syn/synthetic_population.rst#history) (or look at the [documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.History) of the class).\n", + "`pop.history` contains the evolutionary histories of each binary as it was evolved by POSYDON. You can find more information about this [here](../../components-overview/pop_syn/synthetic_population.rst#history) (or look at the [documentation](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.History) of the class).\n", "\n", "\n", "It allows you to load specific information or binaries into memory without having to load them all at once.\n", @@ -284,12 +284,12 @@ "\n", "\n", "\n", - "You can access individual or selections of the population using several methods:\n", + "You can access individual binaries or selections of the population using several methods:\n", "\n", "```\n", "1. pop.history[5]\n", "2. pop.history[[0,4]]\n", - "3. pop.history['time]\n", + "3. pop.history['time']\n", "4. pop.history.select()\n", "```\n" ] @@ -354,16 +354,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `select` function is the most powerful way to access the binaries, because it allows you to perform selections based on the specific columns available in the history dataframe.\n", - "For example, below we can select on `state == 'RLO1'`, which gives us all the rows with RLO1 occuring.\n", + "The `select` function is the most powerful way to access the binaries because it allows you to perform selections based on the specific columns available in the history DataFrame.\n", + "For example, below we can select on `state == 'RLO1'`, which gives us all the rows with RLO1 occurring.\n", "\n", - "The available identifiers are limited to string columns (`state`, `event`, `step_names`, `S1_state`, `S2_state`), index, and columns names.\n", + "The available identifiers are limited to string columns (`state`, `event`, `step_names`, `S1_state`, `S2_state`), index, and column names.\n", "\n", "
\n", "\n", "**Not all columns are available**\n", "\n", - "It's not currently possible to select on all columns in the population file. **Only string columns, the indices and columns names are available!** \n", + "It's not currently possible to select on all columns in the population file. **Only string columns, the indices, and column names are available!** \n", "\n", "
\n" ] @@ -384,7 +384,7 @@ "metadata": {}, "outputs": [], "source": [ - "# selecting all RLO1 states and only time and state columns\n", + "# selecting all RLO2 states and only time and state columns\n", "pop.history.select(where='state == RLO2', columns=['time', 'state'])" ] }, @@ -402,7 +402,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You might have notices while using the above functions that not all the binaries will have the same length in the history.\n", + "You might have noticed while using the above functions that not all the binaries will have the same length in the history.\n", "You can access these with `pop.history_lengths` or `pop.history.lengths`. They provide the same information." ] }, @@ -433,17 +433,17 @@ "\n", "## Population.oneline\n", "\n", - "`Population.oneline` provides a similar interface to accessing the DataFrame in the population file as `Population.history`, with similar functionality being available.\n", + "`Population.oneline` provides a similar interface for accessing the DataFrame in the population file as `Population.history`, with similar functionality being available.\n", "\n", - "The `oneline` DataFrame contains, as the name suggests, a single line per binary. It contains initial and final conditions and some additional varibales, such as the `SN_type`.\n", - "You can find more information about this [here](../../components-overview/pop_syn/synthetic_population.rst#oneline) or look at the [api reference](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.Oneline) of the class.\n", + "The `oneline` DataFrame contains, as the name suggests, a single line per binary. It contains initial and final conditions and some additional variables, such as the `SN_type`.\n", + "You can find more information about this [here](../../components-overview/pop_syn/synthetic_population.rst#oneline) or look at the [API reference](../../api_reference/posydon.popsyn.rst#posydon.popsyn.synthetic_population.Oneline) of the class.\n", "\n", "\n", "The `select` function only has access to:\n", "\n", "- `index`\n", - "- column names \n", - "- string columns: `state_i`, `state_f`, `event_i`, `event_f`, `step_names_i`, `step_names_f`, `S1_state_i`, `S1_state_f`, `S2_state_i`, `S2_state_f`, `S1_SN_type`, `S2_SN_type`, `interp_class_HMS_HMS`, `interp_class_CO_HeMS`, `interp_class_CO_HMS_RLO`, `interp_class_CO_HeMS_RLO`, `mt_history_HMS_HMS`, `mt_history_CO_HeMS`, `mt_history_CO_HMS_RLO`, `mt_history_CO_HeMS_RLO`" + "- Column names.\n", + "- String columns: `state_i`, `state_f`, `event_i`, `event_f`, `step_names_i`, `step_names_f`, `S1_state_i`, `S1_state_f`, `S2_state_i`, `S2_state_f`, `S1_SN_type`, `S2_SN_type`, `interp_class_HMS_HMS`, `interp_class_CO_HeMS`, `interp_class_CO_HMS_RLO`, `interp_class_CO_HeMS_RLO`, `mt_history_HMS_HMS`, `mt_history_CO_HeMS`, `mt_history_CO_HMS_RLO`, `mt_history_CO_HeMS_RLO`." ] }, { @@ -502,11 +502,11 @@ "\n", "## Population.formation_channels\n", "\n", - "While you can see the all the main evolutionary steps in the evolution of a binary, it is useful to have a summary overview of a binary's evolutionary pathway, also known as formation channel\n", + "While you can see all the main evolutionary steps in the evolution of a binary, it is useful to have a summary overview of a binary's evolutionary pathway, also known as a formation channel.\n", "\n", - "You might be interested in figuring out what sort of formation pathways/channels a binary has followed through its evolution.\n", + "You might be interested in figuring out what sort of formation pathways/channels a binary has followed throughout its evolution.\n", "\n", - "This is not a standard output of the population synthesis, but you can include it into the population file by calculating it. \n", + "This is not a standard output of the population synthesis, but you can include it in the population file by calculating it. \n", "If you would like more detail on the initial mass transfer, you can set `mt_history=True`.\n", "\n", "This will write the formation channels to the Population file, which can be accessed by `Population.formation_channels`.\n" @@ -554,12 +554,12 @@ "source": [ "## Selecting a sub-population\n", "\n", - "You might just want a small sub-selection of the full population, especially if you're working with large population and multi-metallicity runs.\n", + "You might just want a small sub-selection of the full population, especially if you're working with large populations and multi-metallicity runs.\n", "\n", "The `Population.export_selection()` function will export just the indices of the binaries you're interested in into a new file.\n", - "The simulated mass will remain the same, since they are dependent on the population run.\n", + "The simulated mass will remain the same since they are dependent on the population run.\n", "\n", - "If we select just 2 binaries and export them, we create a new population of just the binaries you're interested in.\n", + "If we select just two binaries and export them, we create a new population of just the binaries you're interested in.\n", "In the [BBH analysis](bbh_analysis.ipynb) and [GRB analysis](lgrb_pop_syn.ipynb) tutorials, we show how to perform a selection with multiple criteria and across metallicities.\n", "\n" ] @@ -588,7 +588,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you would like to know the simulated mass of just your population, you can calulate this using the online ZAMS values." + "If you would like to know the simulated mass of just your population, you can calculate this using the oneline ZAMS values." ] }, { @@ -606,11 +606,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "By default this export-selection will not overwrite nor append if the output file is already present.\n", - "You have to explicitly state what you would like to append to or overwrite the population file.\n", + "By default, this export-selection will neither overwrite nor append if the output file is already present.\n", + "You have to explicitly state whether you would like to append to or overwrite the population file.\n", "\n", "\n", - "With the `append=True` you are able to combine multiple stellar populations into a single file. \n", + "With `append=True`, you are able to combine multiple stellar populations into a single file. \n", "This is especially useful when creating multi-metallicity populations." ] }, @@ -647,7 +647,7 @@ "\n", "Feel free to explore the small binary population you've just created!\n", "\n", - "If you want to learn more about population synthesis and how to perform more complex selection and population, continue with [Large scale population on a HPC setup](pop_syn.ipynb) or with [BBH analysis](bbh_analysis.ipynb)." + "If you want to learn more about population synthesis and how to perform more complex selections and populations, continue with [Large scale population on an HPC setup](pop_syn.ipynb) or with [BBH analysis](bbh_analysis.ipynb)." ] }, { @@ -661,9 +661,9 @@ "\n", "To speed up population synthesis runs, you can run on a computing cluster, as described in [HPC Facilities](pop_syn.ipynb), or you can distribute the population synthesis across multiple cores on your local machine using MPI.\n", "\n", - "To enable local MPI runs, go into the `population_params.ini` and change `use_MPI` to `True`.\n", + "To enable local MPI runs, go into the `population_params.ini` and change `use_MPI` to `True`.\n", "\n", - "It's important to note that you cannot run have this option enabled for cluster runs!\n", + "It's important to note that you cannot have this option enabled for cluster runs!\n", "\n", "We create a binary population simulation script to run the population:" ] @@ -693,7 +693,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, "outputs": [], "source": [ "mpiexec -n ${NR_processors} python script.py" @@ -703,7 +707,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This will create a folder for each metallicity in the population and store output of the parallel runs in it.\n", + "This will create a folder for each metallicity in the population and store the output of the parallel runs in it.\n", "\n", "You will have to concatenate these runs manually into a single population file per metallicity, which can be achieved using the following code:" ] diff --git a/docs/_source/tutorials-examples/population-synthesis/VHD.rst b/docs/_source/tutorials-examples/population-synthesis/VHD.rst index 6757276c9d..f97c828364 100644 --- a/docs/_source/tutorials-examples/population-synthesis/VHD.rst +++ b/docs/_source/tutorials-examples/population-synthesis/VHD.rst @@ -10,7 +10,7 @@ Van den Heuvel diagrams with POSYDON To use the visualization functionalities, install the optional visualization modules `pip install .[vis]` -`VHdiagram` provides a visual representation of individual POSYDON binaries, offering a more intuitive sense of their properties. This tutorial uses the 'population.h5' dataset as an example. TODO: this need to be tested on a v2.0.0 dataset. +`VHdiagram` provides a visual representation of individual POSYDON binaries, offering a more intuitive sense of their properties. This tutorial uses the 'population.h5' dataset as an example. You can use the population in the `Stellar Transient Populations` tutorial. Visualizing a Specific Binary ----------------------------- @@ -75,7 +75,7 @@ Advanced Visualization Techniques Visualizing Multiple Binaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The `VDdiagramm_m` module allows multiple binary visualizations to be arranged horizontally in a single plot: +The `VHdiagramm_m` class allows multiple binary visualizations to be arranged horizontally in a single plot: .. code-block:: python @@ -84,8 +84,7 @@ The `VDdiagramm_m` module allows multiple binary visualizations to be arranged h from posydon.visualization.VH_diagram.PresenterMode import PresenterMode VHD = VHdiagramm_m('./data/population.h5', - index=cnt[:,0], - frequency=parse_df.get_frequencies(), + index=[19627,19628,19629,19630], hierarchy=False, presentMode=PresenterMode.DIAGRAM, displayMode=DisplayMode.INLINE_B) @@ -104,8 +103,7 @@ This visualization style aggregates identical steps into a tree plot where nodes from posydon.visualization.VH_diagram.PresenterMode import PresenterMode VHD = VHdiagramm_m('./data/population.h5', - index=cnt[:,0], - frequency=parse_df.get_frequencies(), + index=[19627,19628,19629,19630], hierarchy=True, presentMode=PresenterMode.DIAGRAM, displayMode=DisplayMode.INLINE_B) diff --git a/docs/_source/tutorials-examples/population-synthesis/bbh_analysis.ipynb b/docs/_source/tutorials-examples/population-synthesis/bbh_analysis.ipynb index 7c9a564950..6d4437eb3a 100644 --- a/docs/_source/tutorials-examples/population-synthesis/bbh_analysis.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/bbh_analysis.ipynb @@ -47,12 +47,13 @@ } }, "source": [ - "In the previous tutorial, you generated 8 population files with 1000 binaries.\n", - "Since this is a small population of only 8.000 binaries, you could explore the complete population.\n", - "But the larger your populations get, the better it is to select specific binaries.\n", + "In the previous tutorial, you generated 8 population files, each at a different metallicity, with 1,000 binaries in each file.\n", + "Since this is a small population of only 8,000 binaries in total, you can explore the complete population.\n", + "However, the larger your population gets, the better it is to select a subset of the population to minimize memory usage.\n", "\n", - "For each population file, we can export the binaries we're interested in into a new file.\n", - "For this, we need to find the indices of the merging binaries. The relevant properties are that both S1_state and S2_state equal 'BH', while the binary has the `event == 'CO_contact`.\n", + "In this tutorial we will select the subset of the population that ends in a binary black hole merger.\n", + "For each population file, we will export these binaries into a new file.\n", + "For this, we need to find the indices of the merging binaries. The relevant conditions are that the properties S1_state and S2_state equal 'BH', while the binary has the `event == 'CO_contact'`.\n", "\n", "We will load one of the population files to build our merging binaries selection.\n", "\n", @@ -60,10 +61,14 @@ "\n", "
No indices? \n", "\n", - "It might be that the population you generated does not contain any merging BBHs, they're rare after all.\n", + "It might be that the population you generated does not contain any merging BBHs, they're rare after all!\n", "\n", - "As such, we have provided an example population run at `$PATH_TO_POSYDON_DATA/population-synthesis/example/`, this simulation contains 10.000 binaries at the 8 metallicity. \n", - "
\n" + "You can download an example population, which contains 10,000 binaries at each of the 8 metallicities.\n", + "\n", + "```get-posydon-data tutorial_population```\n", + "\n", + "This automatically downloads the tutorial populations from [Zenodo](https://zenodo.org/communities/posydon) into `$PATH_TO_POSYDON_DATA/tutorial_populations`\n", + "" ] }, { @@ -74,6 +79,8 @@ "source": [ "from posydon.popsyn.synthetic_population import Population\n", "\n", + "# change the path to $PATH_TO_POSYDON_DATA/tutorial_populations/1e+00_Zsun_population.h5\n", + "# when using the example population file.\n", "pop = Population('1e+00_Zsun_population.h5') \n", "tmp_data = pop.history.select(columns=['S1_state', 'S2_state', 'event'])\n" ] @@ -82,9 +89,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`pop.history.select` is a read operation on the population file and can take quite a lot of time if the population is large.\n", + "Note: `pop.history.select` is a read operation on the population file and can take quite a lot of time if the population is large.\n", "\n", - "If you have sufficient memory, it is more efficient to select several columns at the same time, as done here, instead of selecting a single column each time." + "We will now select only the relevant columns in this file. If you have sufficient memory, it is more efficient to select several columns at the same time instead of selecting a single column each time." ] }, { @@ -119,7 +126,7 @@ "source": [ "The `selected_indices` has to be a list for the selection to work correctly.\n", "\n", - "You can test you selection by doing the following:" + "You can test your selection by doing the following:" ] }, { @@ -138,7 +145,7 @@ "source": [ "If your selected indices are empty, no BBH mergers occurred in your population. They're quite rare after all!\n", "\n", - "Let's use an example set of populations to make sure there will be BBH in your population.\n", + "Let's use an example set of populations to make sure there will be BBHs in your population.\n", "\n", "**This can only be used with the multi-metallicity populations!**" ] @@ -151,6 +158,8 @@ "source": [ "from posydon.popsyn.synthetic_population import Population\n", "\n", + "# Change the path to the data you want to use\n", + "# or the path to the tutorial data: # $PATH_TO_POSYDON_DATA/tutorial_populations/\n", "data_path = f'PATH/TO/TUTORIAL/DATA/'\n", "\n", "# Let's load one of the populations\n", @@ -170,8 +179,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We don't just want to write these binaries for a single metallicity. We want to export them for each metallicity file.\n", - "As such, we loop over each Population file and export the binaries to the same file. It's important to have the same columns in the populations, otherwise it's not possible to add them together.\n", + "We don’t just want to find the BBH mergers at a single metallicity. We want to export them for each metallicity file. As such, we loop over each Population file and export all the BBH mergers to a new file. It’s important that the Population files have the same columns, otherwise it’s not possible to append them.\n", "\n", "The indices of the binaries will be reset, when being exported, such that the new file contains unique indices for each binary." ] @@ -224,9 +232,9 @@ "\n", "If you ran the population with the tutorial populations, you should have a total of 636 merging BBHs.\n", "\n", - "We can confirm this by adding up the values above or by opening the new file. `Population.number_of_systems` gives us the total number of binaries in the file. \n", + "We can confirm the number of BBH mergers by adding up the values above. Alternatively, we can open the new file, BBH_contact.h5, and use `Population.number_of_systems` to get the total number of binaries in the file. \n", "\n", - "You now see that the `mass_per_metallicity` property contains information about all the metallicities in the file.\n", + "Additionally, you now see that the `mass_per_metallicity` property contains information about all the metallicities in the file.\n", "\n", "You can even combine multiple runs at the same metallicity together, if you like. This will combine their simulated mass." ] @@ -257,15 +265,15 @@ "\n", "- Initial mass function\n", "- Period\n", - "- mass ratio\n", - "- binary fraction\n", + "- Mass ratio\n", + "- Binary fraction\n", "\n", "The default parameters do not sample the complete distributions.\n", - "For example, we sample the binary between 7 and 150 $M_\\odot$, and a binary fraction of 1.\n", - "While you can use the population as is, you should consider the weight of the distributions not sampled.\n", + "For example, we sample binaries with primary masses between 7 and 150 $M_\\odot$, and a binary fraction of 1.\n", + "While you can use the population as it is, you should consider the weight of the unsampled distributions.\n", "\n", "You can calculate the underlying mass of these distributions, which is the actual weight of the binary in our population.\n", - "Since we've stored essential information about the population run, we're able to calculate this even after our selection.\n", + "Since we've stored essential information about the population run, we are able to calculate this even after our selection.\n", "This will return the `underlying_mass` of the sampled population and add it to the population file.\n", "\n", "We will need these weights to be able to continue with the rest of the calculations.\n", @@ -278,7 +286,7 @@ "The initially sampled population can have any binary fraction.\n", "\n", "Additionally, only parts of the period, IMF, and mass ratio distribution are included.\n", - "We allow for different parameter limits to be set for sampling, but these are not included in this normalisation!\n", + "We allow for different parameter limits to be set for sampling, but these are not included in this normalization!\n", "\n", "" ] @@ -307,7 +315,7 @@ "This will be required to calculate merger rates across cosmic time.\n", "\n", "\n", - "For this we will create a `TransientPopulation`.\n", + "For this, we will create a `TransientPopulation`.\n", "This class is used to hold information about a specific event/moment in time.\n", "In our case, this is the moment of \"CO_contact\", the moment of the BBH merger.\n", "However, we might want to store and calculate some additional values, such as the $M_\\mathrm{chirp}$ or $\\chi_\\mathrm{eff}$.\n", @@ -316,14 +324,14 @@ "In short, it takes a `selection_function` and a `transient_name`.\n", "The `transient_name` is a string identifying the transient population in the file,\n", "while `selection_function` extracts the `TransientPopulation` for us.\n", - "This can be any custom function you want it to be, as long it outputs a pandas DataFrame with a 'time' and 'metallicity' column.\n", + "This can be any custom function you want it to be, as long as it outputs a pandas DataFrame with a 'time' and 'metallicity' column.\n", "\n", "Several selection functions are provided in `posydon.popsyn.transient_select_funcs`:\n", "\n", "1. [BBH_selection_function](../../api_reference/posydon.popsyn.rst#posydon.popsyn.transient_select_funcs.BBH_selection_function)\n", "2. [GRB_selection_function](../../api_reference/posydon.popsyn.rst#posydon.popsyn.transient_select_funcs.GRB_selection)\n", "\n", - "We have copied part of the `BBH_selection_function` below to explain how a `selections_function` works." + "We have copied part of the `BBH_selection_function` below to explain how a `selection_function` works." ] }, { @@ -349,10 +357,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "A `selection_function` always has as an input a chunk of the history, oneline, and formation_pathways (optional). For example, you set your `chunksize=10000` when you initialise the `Population`, like we've done above for `BBH_pop`, each chunk will contain 10000 binaries.\n", - "This means that the complete history, oneline and formation_pathways of those 10 binaries are passed to this function.\n", + "A `selection_function` always has as an input a chunk of the history, oneline, and formation_pathways (optional). For example, you set your `chunksize=10000` when you initialise the `Population`, like we've done above for `BBH_pop`, each chunk will contain 10,000 binaries.\n", + "This means that the complete history, oneline and formation_pathways of those 10,000 binaries are passed to this function.\n", "\n", - "The `BBH_selection_function` selects the moment the binary reaches CO_contact as the moment of merger, and stores it in the `time` columns in Myr.\n", + "The `BBH_selection_function` selects the moment the binary reaches CO_contact as the moment of merger, and stores it in the `time` column in Myr.\n", "The `metallicity` is also outputted, since this will be essential when combining it with the star formation history.\n", "\n", "We can test this function by inputting a single binary into it, as done below.\n", @@ -372,9 +380,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This gives us a dataframe containing our 1 binary, its index (0), the moment of merger, and its metallicity.\n", + "This gives us a DataFrame containing our 1 binary, its index (0), the moment of merger, and its metallicity.\n", "\n", - "Of course, we could like to know a bit more about our merger than just when it occurs.\n", + "Of course, we would like to know a bit more about our merger than just when it occurs.\n", "Let's expand our output with the BH masses, their spin, their tilt, and the orbital period at DCO formation." ] }, @@ -425,10 +433,10 @@ "source": [ "With this new function, we have a lot more information available in the TransientPopulation.\n", "\n", - "You can further customise this to your liking, if you want to store specific information.\n", + "You can further customize this to your liking, if you want to store specific information.\n", "It's also possible to calculate additional information based on any value in the history, oneline or formation_channels.\n", "\n", - "We will import some functions to calculate $\\chi_\\mathrm{eff}$, $q$ and $\\mathcal{M}_\\mathrm{chirp}$ and also add them to the dataframe.\n" + "We will import some functions to calculate $\\chi_\\mathrm{eff}$, $q$ and $\\mathcal{M}_\\mathrm{chirp}$ and also add them to the DataFrame.\n" ] }, { @@ -572,9 +580,9 @@ "source": [ "The example binary gives us a dataframe with all the information we're interested in. But if something is missing feel free to customize the selection further!\n", "\n", - "We now create the transient population with the `BBH_selection_function`, we've created and `create_transient_population`.\n", + "We now create a `TransientPopulation` instance with the `BBH_selection_function` and `create_transient_population`.\n", "\n", - "This will create a `TransientPopulation` instance and write the transient population to the current file.\n", + "This will write the transient population to the current file.\n", "\n", "`BBH_mergers.population` will load the complete dataframe we've just created. Each of the indices in this population refer back to the original binaries, which are still accessible through `BBH_mergers.history` or `BBH_mergers.oneline`." ] @@ -639,7 +647,7 @@ "\n", "\n", "\n", - "Applying the underlying mass normalisation assumes that no events occur outside the sampled parameter space. \n" + "Applying the underlying mass normalization assumes that no events occur outside the sampled parameter space. \n" ] }, { @@ -674,7 +682,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can also plot the delay time distribution based on the time columns in the TransientPopulation.\n" + "You can also plot the delay time distribution based on the time column in the TransientPopulation.\n" ] }, { @@ -694,14 +702,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You might also be interested in seeing the evolution of the binary in more detail.\n", + "You might also be interested in seeing the evolution of the binary in more detail.\n", "\n", - "Therefore, you can plot the TransientPopulation over a grid slice.\n", + "For this, you can plot the TransientPopulation over a grid slice.\n", "For example, below we plot binaries over the $q=0.7$ slice at $Z=10^{-4} Z_\\odot$ on the HMS-HMS grid.\n", "\n", - "If no slice is give, all mass ratios are plotted.\n", + "If no slice is given, all mass ratios are plotted.\n", "\n", - "You can also plot a property from the `TransientPopulation.population` DataFrame as a colourmap.\n", + "You can also plot a property from the `TransientPopulation.population` DataFrame as a colormap.\n", "And specific formation_channels if they're in the DataFrame." ] }, @@ -741,7 +749,7 @@ "- Madau+Fragos2017\n", "\n", "We can apply these to our population with the `calculate_cosmic_weights` function.\n", - "Here, we will the metallicity and SFR evolution of the IllustrisTNG, which is the default model used, if no `MODEL_in` is given.\n", + "Here, we will use the metallicity and SFR evolution of the IllustrisTNG, which is the default model used, if no `MODEL_in` is given.\n", "\n", "The function returns an instance of the `Rates` class, which gives us access to some new variables:\n", "\n", @@ -771,7 +779,6 @@ " 'delta_t' : 100, # Myr\n", " 'SFR' : 'IllustrisTNG', # Neijssel2019, Madau+Fragos2017\n", " 'sigma_SFR' : None,\n", - " 'Z_max' : 2.,\n", "}\n", "\n", "rates = BBH_mergers.calculate_cosmic_weights('IllustrisTNG', MODEL_in=MODEL)" @@ -841,7 +848,7 @@ "metadata": {}, "outputs": [], "source": [ - "out = rates.calculate_intrinsic_rate_density(mt_channels=True)" + "out = rates.calculate_intrinsic_rate_density(channels=True)" ] }, { @@ -868,7 +875,7 @@ "source": [ "### Properties of the systems\n", "\n", - "Sometime syou mgiht want some more details about the actual population, and its properties.\n", + "Sometimes you might want some more details about the actual population and its properties.\n", "\n", "`plot_hist_properties()` allows you to plot these.\n", "\n", @@ -905,7 +912,7 @@ "Sometimes, however, you're not just interested in the intrinsic population, and want an observable population.\n", "This could be a supernova detection fraction for a telescope survey, or the LVK detection efficiency for GW mergers.\n", "\n", - "You can apply these using the `calculate_observable_population`. Similar to the `create_transient_population`, this function takes a `observable_func`, which described the observability of a transient.\n", + "You can apply these using `calculate_observable_population`. Similar to the `create_transient_population`, this function takes a `observable_func`, which describes the observability of a transient.\n", "\n", "An `observable_func` takes chunks of\n", "\n", @@ -923,7 +930,7 @@ "metadata": {}, "outputs": [], "source": [ - "from posydon.popsyn.transient_select_funcs import DCO_detactability" + "from posydon.popsyn.transient_select_funcs import DCO_detectability" ] }, { @@ -934,7 +941,7 @@ "source": [ "def DCO_wrapper(transient_chunk, z_events_chunk, weights_chunk):\n", " sensitivity = 'design_H1L1V1'\n", - " return DCO_detactability(sensitivity, transient_chunk, z_events_chunk, weights_chunk, verbose=False)" + " return DCO_detectability(sensitivity, transient_chunk, z_events_chunk, weights_chunk, verbose=False)" ] }, { @@ -984,9 +991,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Cogratulations, you are now ready to analyze any DCO population data you generated with POSYDON. Feel free to further explore the BBH model or to use this tutorial to study other populations.\n", - "The next tutorials show you how to [select GRBs](lgrb_pop_syn.ipynb), perform a [SFH calculation at a single metallicity](one_met_pop_syn.ipynb), and [how to run an individual binary](debug_pop.ipynb)." + "Congratulations, you are now ready to analyze any DCO population data you generated with POSYDON. Feel free to further explore the BBH model or to use this tutorial to study other populations.\n", + "The next tutorials show you how to [select GRBs](lgrb_pop_syn.ipynb), perform a [SFH calculation at a single metallicity](one_met_pop_syn.ipynb), and [how to run an individual binary](evolve_single_binaries.ipynb)." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { diff --git a/docs/_source/tutorials-examples/population-synthesis/binary-pop-syn.rst b/docs/_source/tutorials-examples/population-synthesis/binary-pop-syn.rst index 47ca299a87..830eb573e3 100644 --- a/docs/_source/tutorials-examples/population-synthesis/binary-pop-syn.rst +++ b/docs/_source/tutorials-examples/population-synthesis/binary-pop-syn.rst @@ -24,7 +24,7 @@ Simulate your first 10 binaries and experience the power of POSYDON. 10_binaries_pop_syn -Now that you know how to run your first simulations, explore the different customization options reading the :ref:`population prameters ` documentation. +Now that you know how to run your first simulations, explore the different customization options reading the :ref:`population parameters ` documentation. II. Large-Scale Population Synthesis on HPC Facilities 🚀 diff --git a/docs/_source/tutorials-examples/population-synthesis/custom_step_and_flow.ipynb b/docs/_source/tutorials-examples/population-synthesis/custom_step_and_flow.ipynb index 5f1ae9ec51..3ede1c4b8e 100644 --- a/docs/_source/tutorials-examples/population-synthesis/custom_step_and_flow.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/custom_step_and_flow.ipynb @@ -6,16 +6,16 @@ "source": [ "# Custom POSYDON steps and flow chart\n", "\n", - "The evolution of binaries in POSYDON is determined by the flow chart.\n", - "It uses the stellar states, `S1_state` and `S2_state`, the binary `state` and `event` to determine to which step the binary goes next.\n", + "The evolution of binaries in POSYDON is determined by the flowchart.\n", + "It uses the stellar states, `S1_state` and `S2_state`, the binary `state` and `event` to determine to which step the binary goes to next.\n", "\n", - "For example, in the default flow chart, the state `('H-rich_Core_C_depleted','H-rich_Core_H_burning', 'detached', 'CC1')` will be sent to `step_SN`.\n", + "For example, in the default flowchart, the state `('H-rich_Core_C_depleted','H-rich_Core_H_burning', 'detached', 'CC1')` will be sent to `step_SN`.\n", "\n", - "If your specific need requires, it is possible to change the flow and introduce your own steps.\n", - "This is possible through\n", + "If your specific needs require it, it is possible to change the flow and introduce your own steps.\n", + "This is possible through:\n", "\n", - "1. Importing an additional flow chart and step from the `population_params.ini` file\n", - "2. Changing a step inside a notebook\n", + "1. Importing an additional flowchart and step from the `population_params.ini` file.\n", + "2. Changing a step inside a notebook.\n", "\n", "\n", "## Population_params.ini changes\n", @@ -24,7 +24,7 @@ "\n", "### Importing an adapted flowchart\n", "\n", - "One of the first things the `population_params.ini` file defines is the flow chart.\n", + "One of the first things the `population_params.ini` file defines is the flowchart.\n", "By default, the POSYDON default `flow_chart` is imported:\n", "```\n", "[flow]\n", @@ -34,9 +34,9 @@ " # If given, use an absolute filepath to user defined flow: ['', '']\n", "```\n", "\n", - "We can change this to import our custom flow. In this example, the binary is immediately send to `step_end` without any evolution. Make sure to change the path to your absolute path of the example file, if you want to run a population with it.\n", + "We can change this to import our custom flow. In this tutorial, we will use the example flow named `end_flow_chart` in [custom_step_and_flow.py](custom_step_and_flow.py). This flow immediately sends the binary to `step_end` without any evolution. To use it, we need to change the path to the absolute path of the example file.\n", "\n", - "Additionally, you can put the python file into the `user_modules` folder to make it part of the POSYDON namespace.\n", + "Alternatively, you can put the Python file into the `user_modules` folder to make it part of the POSYDON namespace.\n", "\n", "```\n", "[flow]\n", @@ -47,7 +47,7 @@ "```\n", "\n", "We can check this change by loading the Simulation Property arguments.\n", - "Below you can see the example, where we send all the states straight to `step_end`." + "You can follow the `Evolve individual binaries` tutorial to evolve a binary with each of these flowcharts." ] }, { @@ -82,8 +82,8 @@ "\n", "## Changing an existing step to a custom step\n", "\n", - "Similarly, you can change one of standard POSYDON steps to a custom one.\n", - "We will replace the common envelope step by one that halves the orbital period.\n", + "Similarly, you can change one of the standard POSYDON steps to a custom one.\n", + "We will replace the common envelope step with one that halves the orbital period.\n", "\n", "```\n", "[step_CE]\n", @@ -120,9 +120,9 @@ "source": [ "## Adapting the flow and steps inside your notebook\n", "\n", - "Although you can always load in the population parameters file into your notebook, maybe you want to create a unique flow inside a notebook. For this you need to set up all the steps manually inside the notebook.\n", + "Although you can always load the population parameters file into your notebook, you may want to create a unique flow inside the notebook. To do this, you need to set up all the steps manually inside the notebook.\n", "\n", - "This is definitely not the easiest way to change option within POSYDON." + "This is definitely not the easiest way to change parameters within POSYDON." ] }, { @@ -202,10 +202,10 @@ " common_envelope_lambda_default=0.5, # float in (0, inf) used only for option (1)\n", " common_envelope_option_for_HG_star=\"optimistic\", # 'optimistic', 'pessimistic'\n", " common_envelope_alpha_thermal=1.0, # float in (0, inf) used only for option for (4), (5)\n", - " core_definition_H_fraction=0.1, # 0.01, 0.1, 0.3\n", + " core_definition_H_fraction=0.3, # 0.01, 0.1, 0.3\n", " core_definition_He_fraction=0.1, # 0.1\n", " CEE_tolerance_err=0.001, # float (0, inf)\n", - " common_envelope_option_after_succ_CEE = 'core_not_replaced_noMT', # 'core_not_replaced_noMT' 'core_replaced_noMT' 'core_not_replaced_stableMT' 'core_not_replaced_windloss'\n", + " common_envelope_option_after_succ_CEE = 'two_phases_stableMT', # 'one_phase_variable_core_definition' 'two_phases_stableMT' 'two_phases_windloss'\n", " verbose = False, # True False\n", ")\n", "\n", @@ -225,6 +225,7 @@ " use_interp_values=True, # True, False\n", " use_profiles=True, # True, False\n", " use_core_masses=True, # True, False\n", + " allow_spin_None=False, # True, False\n", " approx_at_he_depletion=False, # True, False\n", " verbose = False, # True False\n", ")\n", @@ -482,9 +483,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Adapted flow chart\n", + "### Adapted flowchart\n", "\n", - "You might want to use the `detached_step` instead of a MESA step. Here is how you do it. As an example we replace the HMS-HMS MESA step with the detached step. Notice that the `detached_step` does not evolve the binary system post onset of RLO1. Hence, unless the `flow_chart` support such binary event, binary state and stellar states, else these systems will not be further evolved." + "You might want to use the `detached_step` instead of a MESA step. Here is how you can do it. As an example, we replace the HMS-HMS MESA step with the detached step. Note that the `detached_step` does not evolve the binary system post onset of RLO1. Hence, unless the `flow_chart` supports such binary events, binary states, and stellar states, these systems will not be further evolved." ] }, { @@ -526,7 +527,7 @@ "source": [ "### Adapted step\n", "\n", - "We implement a custom common envelope step, which is the same as the on in the `custom_step_and_flow.py` custom step." + "We implement a custom common envelope step, which is the same as the one in the `custom_step_and_flow.py` custom step." ] }, { @@ -544,7 +545,7 @@ " def __call__(self, binary):\n", "\n", " if self.verbose:\n", - " print('The orbital separation post CE is half the pre CE orbital separation!')\n", + " print('The orbital separation post CE is half the pre-CE orbital separation!')\n", "\n", " # Determine which star is the donor and which is the companion\n", " if binary.event in [\"oCE1\", \"oDoubleCE1\"]:\n", @@ -555,7 +556,7 @@ " comp_star = binary.star_1\n", " else:\n", " raise ValueError(\"CEE does not apply if `event` is not \"\n", - " \"`oCE1`, 'oDoubleCE1' or `oCE2`, 'oDoubleCE1'\")\n", + " \"`oCE1`, `oDoubleCE1`, `oCE2`, or `oDoubleCE2`\")\n", "\n", " binary.orbital_period /= 2.\n", " donor_star.mass = donor_star.he_core_mass # lose envelope\n", diff --git a/docs/_source/tutorials-examples/population-synthesis/evolve_single_binaries.ipynb b/docs/_source/tutorials-examples/population-synthesis/evolve_single_binaries.ipynb index 4130c85adc..df587896dc 100644 --- a/docs/_source/tutorials-examples/population-synthesis/evolve_single_binaries.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/evolve_single_binaries.ipynb @@ -12,7 +12,7 @@ "metadata": {}, "source": [ "It can be extremely useful to evolve a single binary from specific initial conditions or to start at a specific evolutionary state.\n", - "This is useful for debugging binaries and checking if your custom steps or flow are correctly working.\n", + "This is useful for debugging binaries and checking if your custom steps or flow are working correctly.\n", "\n", "This tutorial will cover:\n", "\n", @@ -116,7 +116,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You're now able to access the binary and evolutionary information. Using the function, `BinaryStar.to_df()` we can inspect the evolution of the systm.\n" + "You're now able to access the binary and evolutionary information. Using the function, `BinaryStar.to_df()` we can inspect the evolution of the system.\n" ] }, { @@ -165,7 +165,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Even thought we reset the binary, the natal kick properties stored in the natal kick array are not reset. This is because we want to be able to re-evolve the binary to the same final state. In case you want to reset the natal kick array values, then set the list to `None` values." + "Even though we reset the binary, the natal kick properties stored in the natal kick array are not reset. This is because we want to be able to re-evolve the binary to the same final state. If you want to reset the natal kick array values, then set its elements to `None`." ] }, { @@ -284,12 +284,12 @@ }, { "cell_type": "code", - "execution_count": 143, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Because the original population contains two extre columns. These have to be defined manually here.\n", - "# Otherwise, the reseting of the binaries fails.\n", + "# Because the original population contains two extra columns. These have to be defined manually here.\n", + "# Otherwise, resetting the binaries will fail.\n", "BINARY = BinaryStar.from_df(pop.history[0],\n", " extra_columns={'step_names':'string','step_times':'float'})\n", "\n", @@ -340,7 +340,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you want to evolve a custom binary star, you can do so by crafting yourself the `BinaryStar` object and providing the `SimulationProperties` object. For example, let's evolve a neutron star with a low mass helium star in Roche lobe overflow.\n", + "If you want to evolve a custom binary star, you can do so by manually creating the `BinaryStar` object and providing the `SimulationProperties` object. For example, let's evolve a neutron star with a low-mass helium star in Roche lobe overflow.\n", "And start a binary in the detached step, which requires a bit of additional input data.\n", "\n", "We will use the standard SimulationProperties, loaded earlier in this notebook.\n", diff --git a/docs/_source/tutorials-examples/population-synthesis/lgrb_pop_syn.ipynb b/docs/_source/tutorials-examples/population-synthesis/lgrb_pop_syn.ipynb index 332466684a..86d50fbe64 100644 --- a/docs/_source/tutorials-examples/population-synthesis/lgrb_pop_syn.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/lgrb_pop_syn.ipynb @@ -18,7 +18,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will use the same BBH population as in the `Analyzing Merging BBH Populations: Rates & Observations` tutorial to compute the long gamma-ray bursts (LGRB) rate associated with the formation of merging BBHs.\n", + "We will use the same BBH population as in the `Stellar Transient Populations` tutorial to compute the long gamma-ray bursts (LGRB) rate associated with the formation of merging BBHs.\n", "\n", "Make sure the `BBH_contact.h5` file is present to continue with this tutorial.\n", "You can find a copy of it in the example dataset." @@ -58,18 +58,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We had already selected only binaries that result in a merger in the `BBH_contact.h5` file, so we don't need to perform any selection on the binaries.\n", + "We already selected only binaries that result in a BBH merger in the `BBH_contact.h5` file, so we don't need to perform any selection on the binaries.\n", "\n", - "Thus, our next step is to create a selection function for LGRBs based on the properties in the history and onlines of the binaries.\n", + "Thus, our next step is to create a selection function for LGRBs based on the properties in the history and oneline data of the binaries.\n", "\n", - "We have already included a basic selection function in `posydon.popsyn.transient_select_func`, which select if a GRB occurs based on the presence of `m_disk_radiated` for either star.\n", - "It will also output some pre and post supernova properties.\n", + "POSYDON already includes a basic selection function for GRBs named `GRB_selection` in the `posydon.popsyn.transient_select_funcs` module, which identifies if a GRB occurred based on the presence of `m_disk_radiated` for either star.\n", + "`GRB_selection` also outputs some pre and post supernova properties.\n", "\n", - "As you can see below, the `GRB_selection` requires an additional input parameter of `S1_S2`. \n", - "The `Population.create_transient_population` function cannot pass these additional arguments to sub-function.\n", - "Thus, we will be required to wrap the `GRB_selection` functions.\n", + "However, as you can see below, the `GRB_selection` requires an additional input parameter of `S1_S2`. \n", + "The `Population.create_transient_population` function cannot pass these additional arguments to the sub-function, `GRB_selection`.\n", + "Thus, we need to wrap the `GRB_selection` function.\n", "\n", - "This is useful, if you would like to create similar population, but use different model parameters.\n" + "This is useful if you would like to create a similar population, but use different model parameters.\n" ] }, { @@ -130,7 +130,7 @@ "You've now created a LGRB population, where either the first and/or second star has some amount of radiative disk.\n", "\n", "This is, of course, just an example and the actual amount of disk required to power a LGRB will be higher than some of the values included in our \"LGRB\" rate.\n", - "Moreover, the number of binaries in this population is insufficient to actually see the all unique events.\n", + "Moreover, the number of binaries in this population is insufficient to actually see all unique events.\n", "\n", "For now, let's continue and calculate the metallicity bias function for the LGRBs.\n", "We don't need to calculate the `underlying_mass` of the population, because we already did this in the [BBH tutorial](bbh_analysis.ipynb)." @@ -160,7 +160,7 @@ "source": [ "After this selection, we follow similar steps to the BBH analysis for creating and plotting a cosmic star formation history weighted rate.\n", "\n", - "If you've followed the previous tutorial on the BBH analaysis, you will still have access to those populations too." + "If you've followed the previous tutorial on the BBH analysis, you will still have access to those populations too." ] }, { @@ -204,7 +204,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Below we plot the BBH rate and the un-normalised LGRB rate together, showing how you can access multiple event rates, and transient populations from the same file." + "Below we plot the BBH rate and the unnormalized LGRB rate together, showing how you can access multiple event rates, and transient populations from the same file." ] }, { @@ -253,12 +253,12 @@ "source": [ "It's possible to calculate more properties for the LGRBs using the `posydon.popsyn.GRB` module.\n", "\n", - "It contains the `get_GRB_properties` that can provide a more detailed GRB properties based on empirical or simulation relations for the LGRB energy and beaming factor.\n", + "It contains the `get_GRB_properties` that can provide more detailed GRB properties based on empirical or simulation relations for the LGRB energy and beaming factor.\n", "\n", "Enjoy exploring this function and creating a more detailed GRB population!\n", - "Keep in mind that it's not possible to add additional columns to the transient population without overwritting the population.\n", + "Keep in mind that it's not possible to add additional columns to the transient population without overwriting the population.\n", "\n", - "The next tutorial will focus on rerunning specific binaries in a population or setting up an unique binary yourself.\n", + "The next tutorial will focus on rerunning specific binaries in a population or setting up a unique binary yourself.\n", "\n" ] } diff --git a/docs/_source/tutorials-examples/population-synthesis/one_met_pop_syn.ipynb b/docs/_source/tutorials-examples/population-synthesis/one_met_pop_syn.ipynb index df134a1b5a..35a7e2bd8e 100644 --- a/docs/_source/tutorials-examples/population-synthesis/one_met_pop_syn.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/one_met_pop_syn.ipynb @@ -28,14 +28,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Creating the Synthetic Poupulation DataFrame" + "## Creating the Synthetic Population DataFrame" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We will use the same BBH population as in the `Analyzing Merging BBH Populations: Rates & Observations` tutorial. Instead of computing the distributions and rates at all metallicities, we will just focus on one metallicity, $Z_\\odot$, and integrate the star formation history around solar metallicity, say $[0.5Z_\\odot,2Z_\\odot]$. Let's extract the merging BBH population from the $Z_\\odot$ population synthesis model. In this sample there are only a few systems, to increase the statistics we suggest you run a population containing x50 more binaries, if you want to do a more thorough analysis." + "We will use the same BBH population as in the `Stellar Transient Populations` tutorial. Instead of computing the distributions and rates at all metallicities, we will just focus on one metallicity, $Z_\\odot$, and integrate the star formation history around solar metallicity, say $[0.5Z_\\odot,2Z_\\odot]$. Let's extract the merging BBH population from the $Z_\\odot$ population synthesis model. In this sample there are only a few systems; to increase the statistics we suggest you run a population containing more binaries, if you want to do a more thorough analysis." ] }, { @@ -59,9 +59,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "POSYDON supports different assumptions for the star formation history, e.g. instead of using the IllustrisTNG SFH, we can use the star formation rate from Madau & Fragos (2018) (see Andrews et al. in prep.) and assume a log-normal metallicity distribution around the empirically measured mean metallicity at each redshift from Madau & Fragos (2018), taking a dispersion of 0.5 dex (see Bavera et al. 2020). The IllustrisTNG star formation rate and metallicity evolution (solid line) is compared to the Madau & Fragos (2018) metallicity evolution (dashed line) in Fig. X of (see Andrews et al. in prep.), which figure is reproduced below. The dotted line corresponds to the metallicity distribution of Neijssel et al. (2019).\n", + "POSYDON supports different assumptions for the star formation history, e.g. instead of using the IllustrisTNG SFH, we can use the star formation rate from Madau & Fragos (2018) (see Andrews et al. 2024) and assume a log-normal metallicity distribution around the empirically measured mean metallicity at each redshift from Madau & Fragos (2018), taking a dispersion of 0.5 dex (see Bavera et al. 2020). The IllustrisTNG star formation rate and metallicity evolution (solid line) is compared to the Madau & Fragos (2018) metallicity evolution (dashed line) in Fig. 22 of (see Andrews et al. 2024), which is reproduced below. The dotted line corresponds to the metallicity distribution of Neijssel et al. (2019).\n", "\n", - "![Star Fromation Rate](./pictures/SFR.png \"Star Formation Rate\")\n", + "![Star Formation Rate](./pictures/SFR.png \"Star Formation Rate\")\n", "\n", "![Metallicity Distribution](./pictures/met_dist.png \"Metallicity Distribution\")" ] @@ -72,7 +72,7 @@ "source": [ "## Single metallicity population\n", "\n", - "Similar to in the multi-metallicity tutorial, we will setup a BBH selection from our $Z_\\odot$ population and then calculate the transient population." + "Similar to the multi-metallicity tutorial, we will set up a BBH selection from our $Z_\\odot$ population and then calculate the transient population." ] }, { @@ -190,7 +190,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Similar to the `Analyzing Merging BBH Populations: Rates & Observations` we compute the BBH merger rate density to obtain the BBH intrinsic population.\n", + "Similar to the `Stellar Transient Populations` we compute the BBH merger rate density to obtain the BBH intrinsic population.\n", "\n", "However, because we are only working with a single metallicity, we have to set the metallicity limits we would like to work with!\n", "\n", @@ -221,7 +221,7 @@ "metadata": {}, "outputs": [], "source": [ - "rates.calculate_intrinsic_rate_density(mt_channels=True)" + "rates.calculate_intrinsic_rate_density(channels=True)" ] }, { @@ -243,14 +243,14 @@ "metadata": {}, "outputs": [], "source": [ - "from posydon.popsyn.transient_select_funcs import DCO_detactability\n", + "from posydon.popsyn.transient_select_funcs import DCO_detectability\n", "from posydon.popsyn.synthetic_population import Rates\n", "\n", "rates = Rates('Zsun_BBH_contact.h5', 'BBH', 'IllustrisTNG',)\n", "\n", "def DCO_wrapper(transient_chunk, z_events_chunk, weights_chunk):\n", " sensitivity = 'design_H1L1V1'\n", - " return DCO_detactability(sensitivity, transient_chunk, z_events_chunk, weights_chunk, verbose=False)" + " return DCO_detectability(sensitivity, transient_chunk, z_events_chunk, weights_chunk, verbose=False)" ] }, { diff --git a/docs/_source/tutorials-examples/population-synthesis/pop_syn.ipynb b/docs/_source/tutorials-examples/population-synthesis/pop_syn.ipynb index fad9aab478..8e737b508f 100644 --- a/docs/_source/tutorials-examples/population-synthesis/pop_syn.ipynb +++ b/docs/_source/tutorials-examples/population-synthesis/pop_syn.ipynb @@ -7,7 +7,7 @@ "# Large-Scale Population Synthesis on HPC Facilities 🚀\n", "\n", "\n", - "This tutorial will cover how to setup and run a large, multi-metallicity population run on a HPC with slurm.\n", + "This tutorial will cover how to set up and run a large, multi-metallicity population run on an HPC with slurm.\n", "We will dive deeper into the `population_params.ini` file and how the `Population` class works for multi-metallicity populations." ] }, @@ -72,10 +72,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "POSYDON provides `posydon-popsyn setup` as a command line script that you can use to setup a (multi-metallicity) population run on a HPC facility.\n", - "This will split each metallicity into a separate slurm job-array and a dependent job which will automatically merge the output of the separate jobs.\n", - "This removes the need to manually setup each slurm job.\n", - "Below are two examples, but you might require to adjust the inputs for your email, cluster and available partitions.\n", + "POSYDON provides `posydon-popsyn setup` as a command line script that you can use to set up a (multi-metallicity) population run on an HPC facility.\n", + "This will split each metallicity into a separate slurm job array and a dependent job which will automatically merge the output of the separate jobs.\n", + "This removes the need to manually set up each slurm job.\n", + "Below are two examples, but you might need to adjust the inputs for your email, cluster and available partitions.\n", "\n", "
\n", "\n", @@ -123,6 +123,7 @@ "posydon-popsyn setup population_params.ini --job-array=10 \\\n", " --walltime=00:14:00 \\\n", " --partition=posydon-std \\\n", + " --account=b1119 \\\n", " --email=max.briel@unige.ch \\\n", " --mem_per_cpu=5G\n" ] @@ -136,19 +137,19 @@ "\n", "### Walltime and job_array number fine-tuning\n", "\n", - "The above examples will setup the population run with an array of 10 jobs for each metallicity. As such, each job will run 100 binaries of the 1000 binaries per metallicity.\n", + "The above examples will set up the population run with an array of 10 jobs for each metallicity. As such, each job will run 100 binaries of the 1000 binaries per metallicity.\n", "\n", - "Fine-tuning the `dump_rate`, `mem_per_cpu`, number of `job-array`'s, and `walltime` compared to the total number of binaries can be helpful in optimizing your cluster usage. See [the FAQ](../../troubleshooting-faqs/code-questions.rst) on more detailed guidance on the memory footprint of a population synthesis run in POSYDON.\n", + "Fine-tuning the `dump_rate`, `mem_per_cpu`, number of `job-array`'s, and `walltime` compared to the total number of binaries can be helpful in optimizing your cluster usage. See [the FAQ](../../troubleshooting-faqs/code-questions.rst) for more detailed guidance on the memory footprint of a population synthesis run in POSYDON.\n", "By default, 5GB of RAM is requested per job, which fits well with the default `dump_rate` of 2000.\n", "\n", - "As a rule of thumb for the `walltime`, a single binary takes about 1-2 seconds to run. In the example run, each job of 100 binaries should take around 2 to 3 minutes. However, some additional setup time for loading the grids is required! See [the FAQ](../../troubleshooting-faqs/code-questions.rst) for another example of ```walltime``` estimation." + "As a rule of thumb for the `walltime`, a single binary takes about 1-2 seconds to run. In the example run, each job of 100 binaries should take around 2 to 3 minutes. However, some additional set up time for loading the grids is required! See [the FAQ](../../troubleshooting-faqs/code-questions.rst) for another example of `walltime` estimation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "After you've ran the above setup, you should now have several files in your work directory.\n", + "After you've run the above setup, you should now have several files in your working directory.\n", "\n", "You can submit all job arrays and merged jobs using `slurm_submit.sh`\n" ] @@ -172,7 +173,6 @@ "source": [ "After your job has finished, you can use the following command to verify that your run was successful.\n", "Please replace `{run_folder}` with the folder, where you ran the population.\n", - "\n", "```\n", "posydon-popsyn check {run_folder}\n", "```\n", @@ -183,9 +183,9 @@ "This allows failed job in a job array to be resubmitted. You can provide the same commands as `posydon-popsyn setup`, if you want to change slurm parameters, such as walltime and mem-per-cpu.\n", "\n", "\n", - "## Populations inspection\n", + "## Population inspection\n", "\n", - "Once the runs and the mergers have finished. You should have 8 files named `MET_Zsun_population.h5`, where `MET` are the metallicities.\n", + "Once the runs and the concatenation have finished. You should have 8 files named `MET_Zsun_population.h5`, where `MET` are the metallicities.\n", "\n", "We will inspect two to check the number of binaries in the population." ] @@ -212,7 +212,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the next tutorial, we will look into selecting specific events and combining the different metallicity runs into a single population and selecting the binary black hole mergers in them! [BBH analysis](bbh_analysis.ipynb)" + "In the next tutorial ([BBH analysis](bbh_analysis.ipynb)), we will look into selecting specific events and combining the different metallicity runs into a single population and selecting the binary black hole mergers in them!" ] }, { diff --git a/docs/_source/tutorials-examples/population-synthesis/population_params_end.ini b/docs/_source/tutorials-examples/population-synthesis/population_params_end.ini index ace50a1ed2..f144fcc251 100644 --- a/docs/_source/tutorials-examples/population-synthesis/population_params_end.ini +++ b/docs/_source/tutorials-examples/population-synthesis/population_params_end.ini @@ -188,15 +188,15 @@ # 'optimistic', 'pessimistic' common_envelope_alpha_thermal=1.0 # float in (0, inf) used only for option for (4), (5) - core_definition_H_fraction=0.1 + core_definition_H_fraction=0.3 # 0.01, 0.1, 0.3 core_definition_He_fraction=0.1 # 0.1 CEE_tolerance_err = 0.001 # float (0, inf) - common_envelope_option_after_succ_CEE = 'core_not_replaced_noMT' - # 'core_not_replaced_noMT' 'core_replaced_noMT' - # 'core_not_replaced_stableMT' 'core_not_replaced_windloss' + common_envelope_option_after_succ_CEE = 'two_phases_stableMT' + # 'two_phases_stableMT' 'one_phase_variable_core_definition' + # 'two_phases_windloss' verbose = False # True False @@ -205,35 +205,52 @@ # builtin posydon step absolute_import = None # 'package' kwarg for importlib.import_module - mechanism = 'Patton&Sukhbold20-engine' - # 'direct', Fryer+12-rapid', 'Fryer+12-delayed', - # 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' - engine = 'N20' - # 'N20' for 'Sukhbold+16-engine', - # 'Patton&Sukhbold20-engine' or None for the others - PISN = "Marchant+19" - # None, "Marchant+19" - ECSN = "Podsiadlowski+04" + mechanism = 'Fryer+12-delayed' + # v2 interpolators support: 'Fryer+12-rapid', 'Fryer+12-delayed', + # 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' + # need profiles: 'direct' + engine = '' + # 'N20' or 'W20' for 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' + # '' for the others + PISN = "Hendriks+23" + # v2 interpolators support: "Hendriks+23" + # other options: None, "Marchant+19" + PISN_CO_shift = 0.0 + # Only when using Hendriks+23 + # float (-inf,inf) + # v2 interpolators support: 0.0 + PPI_extra_mass_loss = -20.0 + # Only when using Hendriks+23 + # float (-inf,inf) + # v2 interpolators support: 0.0 or -20.0 + ECSN = "Tauris+15" # "Tauris+15", "Podsiadlowski+04" conserve_hydrogen_envelope = False # True, False + conserve_hydrogen_PPI = False + # Only when using Hendriks+23 + # True, False max_neutrino_mass_loss = 0.5 # float (0,inf) + # v2 interpolators support: 0.5 max_NS_mass = 2.5 # float (0,inf) + # v2 interpolators support: 2.5 use_interp_values = True # True, False use_profiles = True # True, False use_core_masses = True # True, False + allow_spin_None = False + # True, False approx_at_he_depletion = False # True, False kick = True # True, False kick_normalisation = 'one_over_mass' - # "one_minus_fallback", "one_over_mass", - # "NS_one_minus_fallback_BH_one", "one", "zero" + # "one_minus_fallback", "one_over_mass", "NS_one_minus_fallback_BH_one", + # "one", "zero" sigma_kick_CCSN_NS = 265.0 # float (0,inf) sigma_kick_CCSN_BH = 265.0 @@ -281,6 +298,7 @@ # set maximum ram per cpu before batch saving (GB) dump_rate = 2000 # batch save after evolving N binaries + # this should be at least 500 for populations of 100,000 binaries or more temp_directory = 'batches' # folder for keeping batch files tqdm = False @@ -291,15 +309,20 @@ # use only for local MPI runs metallicity = [1.] #[2., 1., 0.45, 0.2, 0.1, 0.01, 0.001, 0.0001] # In units of solar metallicity - - # Random Number Generation + error_checking_verbose = False + # if True, write all POSYDON errors to stderr at runtime, default=False + warnings_verbose = False + # if True, write all POSYDON warnings to stderr at runtime, default=False + history_verbose = False + # if True, record extra functional steps in the output DataFrames + # (These steps represent internal workings of POSYDON rather than physical phases of evolution) entropy = None # `None` uses system entropy (recommended) number_of_binaries = 10 # int binary_fraction_scheme = 'const' #'const' 'Moe_17' - binary_fraction_const = 1 + binary_fraction_const = 1.0 # float 0< fraction <=1 star_formation = 'burst' # 'constant' 'burst' 'custom_linear' 'custom_log10' 'custom_linear_histogram' 'custom_log10_histogram' @@ -310,7 +333,7 @@ # path to file to read initial parameters from (if empty string get random samples) primary_mass_scheme = 'Kroupa2001' # 'Salpeter', 'Kroupa1993', 'Kroupa2001' - primary_mass_min = 7 + primary_mass_min = 7.0 # float (0,130) primary_mass_max = 150.0 # float (0,130) diff --git a/docs/_source/user-guides/database-query-system.rst b/docs/_source/user-guides/database-query-system.rst deleted file mode 100644 index 02428fd933..0000000000 --- a/docs/_source/user-guides/database-query-system.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. _database-and-query-system: - -Database and Query System -========================= - -POSYDON offers a robust database and query system, allowing users to access, analyze, and interact with the vast amount of simulation data it produces. This page provides an introduction to the structure of the database and instructions on how to utilize the query system. - -Database Overview ------------------ - -The POSYDON database has been structured to ensure efficient storage and quick retrieval of data. It comprises: - -- **Simulation Data**: Details of individual simulations, including initial conditions, evolution paths, and outcomes. - -- **Model Assumptions**: Various model assumptions under which the simulations were run. - -- **Metadata**: Additional information about the simulations, like the date of the run, software version, etc. - -Query System ------------- - -Our query system enables users to: - -1. **Search**: Filter and retrieve specific sets of data based on certain criteria. -2. **Analyze**: Compute statistics or perform operations on the retrieved data. -3. **Visualize**: Generate plots and diagrams to better understand the data. -4. **Export**: Extract the queried data for external analysis. - -Getting Started with Queries ------------------------------ - -1. **Accessing the Query Interface**: - - You can access the interface through [this link](link-to-query-interface) or via the POSYDON main software. - -2. **Basic Queries**: - - Start by selecting the category of data you're interested in (e.g., 'Simulation Data'). Define the criteria to filter the results. - -3. **Advanced Queries**: - - Combine multiple criteria, utilize logical operators (AND, OR), and specify the desired output format. - -Tutorials & Examples --------------------- - -To help you get started with the query system, we've prepared some tutorials: - -1. [Basic Querying](link-to-basic-query-tutorial) -2. [Advanced Query Techniques](link-to-advanced-query-tutorial) -3. [Data Analysis using the Query System](link-to-data-analysis-tutorial) - -Best Practices --------------- - -- Always double-check your query parameters to ensure accuracy. -- For large data retrievals, consider off-peak hours to minimize server load. - - -Support & Feedback ------------------- - -If you encounter any difficulties or have suggestions for improving the database and query system, please refer to the [contact-information.rst](contact-information.rst) page. - diff --git a/docs/_source/user-guides/user-roadmap.rst b/docs/_source/user-guides/user-roadmap.rst deleted file mode 100644 index 31a1c06797..0000000000 --- a/docs/_source/user-guides/user-roadmap.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. _roadmap: - -Roadmap to Becoming an Expert POSYDON User -========================================== - -Welcome to your journey towards mastering POSYDON! Below is your roadmap, carefully curated to guide you from the basics to advanced features of POSYDON. As you progress through the steps, you'll earn exciting POSYDON recognition badges. 🌟 - -Let's set forth on this voyage and unleash the true power of POSYDON! - -Step 1: Dive Into Binary-Star Population Synthesis 🌌 ------------------------------------------------------- - -Start your adventure by exploring the magic of binary-star population synthesis with POSYDON: - -- **Model Assumptions**: Learn to select and configure the right assumptions for your simulations. -- **Running Simulations**: Acquaint yourself with running simulations both on your laptop and on HPC facilities. -- **Exporting Outputs**: Master the techniques to neatly export and archive your simulation results. -- **Analyzing Outcomes**: Delve deep into the results to identify and analyze phenomena like merging double compact object populations. - -🔗 Dive into this tutorial: :ref:`Binary-Star Population Synthesis with POSYDON ` - -**Badge Earned**: 🌠 POSYDON Binary Population Master 🌠 - ---- - -Step 2: Behind the Scenes - Generating Datasets 📊 --------------------------------------------------- - -Unravel the mysteries behind the datasets driving your population synthesis models: - -- **The Origin of Datasets**: Explore the vast raw MESA datasets and understand their essence. -- **Data Downsampling**: Learn how we efficiently downsample these datasets without compromising on quality. -- **Processing Pipeline API**: Get hands-on with our robust API that controls every nuance of data processing. - -🔗 Dive into this tutorial: :ref:`Crafting the Core Datasets for POSYDON ` - -**Badge Earned**: 🔧 POSYDON MESA Engineer 🔧 - ---- - -Step 3: Architecting MESA Simulation Grids 🏗️ ------------------------------------------------ - -Elevate your expertise by diving deep into the creation of MESA simulation grids: - -- **MESA Configuration Repository**: Unearth the treasure of our dedicated repository, housing a plethora of MESA configurations. -- **Harnessing the API Interface**: Utilize the API to craft and customize MESA grids with precision. - -🔗 Dive into this tutorial: :ref:`Architecting MESA Simulation Grids with POSYDON ` - -**Badge Earned**: 🛠️ POSYDON MESA Grid Architect 🛠️ - ---- - -🔱 Final Achievement: POSYDON Triton! 🔱 ----------------------------------------- - -Congratulations! By mastering all the steps, you've now earned the ultimate badge: the **POSYDON Trident** 🔱. Wear it with pride and harness the boundless powers of POSYDON! - -For more in-depth topics and advanced tutorials, head over to our [Advanced Tutorials Section](link-to-advanced-tutorials). And always remember, the ocean of knowledge is vast, and there's always more to explore! diff --git a/docs/_source/user-guides/web-application.rst b/docs/_source/user-guides/web-application.rst deleted file mode 100644 index 1226ad1b06..0000000000 --- a/docs/_source/user-guides/web-application.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. _web-application: - -Web Application: POSYDON Interactive Interface -=============================================== - -POSYDON provides an interactive web application that enables users to explore and manipulate population synthesis models without directly diving into the code. Whether you're a seasoned astronomer or just curious, the POSYDON web application can be a powerful tool for visualization and analysis. - -Overview --------- - -The web application offers the following features: - -- **Population Synthesis**: Run your own population synthesis model through your broseware without the need to have access to an HPC facility or installing POSYDON on your laptop. - -- **Model Selection**: Choose from different model assumptions and instantly select the model you want to run. - -- **Export Options**: Once you have run the desired model, you can easily download the dataset output for further use. - -Getting Started ---------------- - -1. **Accessing the Web Application** - - Visit the official POSYDON web application link: [POSYDON WebApp](https://posydon.odahub.io). TODO: bring it back online and update to v2.0.0 - -2. **Open a account** - - To use the web application, you will need to create an account. This is a simple process that only requires a valid email address and why you want to use the Webapp. Once you have created an account, we will grant you access and you will be able to submit population synthesis simulations throught the web application. - -3. **Navigation** - - The user interface is designed to be intuitive. Start by selecting a model and then navigate through various pages to submit your simulation. Wait for the email notification that your simulation has finished and then download the results. - -Tutorials & Examples --------------------- - -For detailed walkthroughs of specific functionalities and use-cases: TODO: provide links to tutorials - -1. [Basic Navigation and Model Selection](link-to-tutorial-1) -2. [Advanced Data Analysis](link-to-tutorial-2) -3. [Exporting Visualizations](link-to-tutorial-3) - -Feedback & Support ------------------- - -We're always looking to improve the web application. If you encounter any issues or have suggestions for new features, please refer to the [contact-information.rst](contact-information.rst) page to get in touch with us. - diff --git a/grid_params/grid_params.ini b/grid_params/grid_params.ini index c06cd8edfe..a866997277 100644 --- a/grid_params/grid_params.ini +++ b/grid_params/grid_params.ini @@ -45,7 +45,7 @@ email={YOUR_EMAIL_ADDRESS} ; inlist types: binary_control,binary_job,star_job,star_control ; Please leave MESA defaults lines alone, please fill in the path to ; you local clone of POSYDON -posydon_github_root={PATH_TO_POSYDON_DIRECTORY} +posydon_github_root={$PATH_TO_POSYDON} ; There are a number of ways to build the physics of your MESA grid ; the first way is to point the sections below to your own MESA inlists, diff --git a/posydon/binary_evol/CE/step_CEE.py b/posydon/binary_evol/CE/step_CEE.py index 4d0d21888b..49b11d0dfc 100644 --- a/posydon/binary_evol/CE/step_CEE.py +++ b/posydon/binary_evol/CE/step_CEE.py @@ -52,15 +52,15 @@ "common_envelope_option_for_lambda": 'lambda_from_grid_final_values', "common_envelope_option_for_HG_star": "optimistic", "common_envelope_alpha_thermal": 1.0, - "core_definition_H_fraction": 0.1, # with 0.01 no CE BBHs + "core_definition_H_fraction": 0.3, # with 0.01 no CE BBHs "core_definition_He_fraction": 0.1, "CEE_tolerance_err": 0.001, "verbose": False, - "common_envelope_option_after_succ_CEE": 'core_not_replaced_noMT', + "common_envelope_option_after_succ_CEE": 'two_phases_stableMT', "mass_loss_during_CEE_merged": False # If False, then no mass loss from this step for a merged star # If True, then we remove mass according to the alpha-lambda prescription # assuming a final separation where the inner core RLOF starts. - # "core_replaced_noMT" for core_definition_H_fraction=0.01 + # "one_phase_variable_core_definition" for core_definition_H_fraction=0.01 } @@ -304,7 +304,7 @@ def calculate_lambda_CE(self, donor, verbose=False): if donor_type == 'He_core': mc1_i = donor.he_core_mass rc1_i = donor.he_core_radius - elif donor_type == "CO_core": + elif donor_type == 'CO_core': mc1_i = donor.co_core_mass rc1_i = donor.co_core_radius else: @@ -362,7 +362,7 @@ def calculate_lambda_CE(self, donor, verbose=False): if donor_type == 'He_core': mc1_i = donor.he_core_mass rc1_i = donor.he_core_radius - elif donor_type == "CO_core": + elif donor_type == 'CO_core': mc1_i = donor.co_core_mass rc1_i = donor.co_core_radius else: @@ -397,6 +397,417 @@ def calculate_lambda_CE(self, donor, verbose=False): return lambda_CE, mc1_i, rc1_i, donor_type + def CEE_adjust_post_CE_core_masses(self, donor, mc1_i, rc1_i, donor_type, + comp_star, mc2_i, rc2_i, comp_type, double_CE, verbose=False): + """Calculate the post-common-envelope core masses and radii. + + It determines the post-CE parameters based on the core properties. + Note that these parameters may be updated in a subsequent step + depending on assumptions about whether and how the final layers + of the CE are removed from the cores. + + Parameters + ---------- + donor : SingleStar object + The donor star + mc1_i : float + Core mass of the donor after the fast CE phase (in Msun) + rc1_i : float + Core radius of the donor after the fast CE phase (in Rsun) + donor_type : string + String dictating whether the donor has a 'He_core' or 'CO_core' + comp_star : SingleStar object + The companion star + mc2_i : float + Core mass of the companion after the fast CE phase (in Msun) + rc2_i : float + Core radius of the companion after the fast CE phase (in Rsun) + comp_type : string + String dictating whether the companion has a 'He_core', 'CO_core', + or 'not_giant_companion' (e.g. a compact object or MS star). + double_CE : bool + In case we have a double CE situation. + verbose : bool + In case we want information about the CEE. + + Returns + ------- + m1c_f: float + Donor mass after the complete CE (in Msun) + r1c_f: float + Donor radius after the complete CE (in Rsun) + m2c_f: float + Companion mass after the complete CE (in Msun) + r2c_f: float + Companion radius after the complete CE (in Rsun) + """ + if donor_type == 'He_core': + mc1_f = donor.he_core_mass + rc1_f = donor.he_core_radius + elif donor_type == 'CO_core': + mc1_f = donor.co_core_mass + rc1_f = donor.co_core_radius + else: + raise ValueError( + "donor_type in common envelope is not recognized. " + "donor_type = {}, don't know how to proceed". + format(donor_type)) + if mc1_f > mc1_i: + mc1_f = mc1_i + Pwarn("The final donor core mass (even after stable, postCEE MT)" + " is higher than the postCEE core mass. Now equalizing to " + "postCEE mass", "ApproximationWarning") + if not double_CE: + mc2_f = mc2_i + rc2_f = rc2_i + else: + if comp_type == 'He_core': + mc2_f = comp_star.he_core_mass + rc2_f = donor.he_core_radius + elif comp_type == 'CO_core': + mc2_f = comp_star.co_core_mass + rc2_f = donor.co_core_radius + elif comp_type == 'not_giant_companion': + mc2_f = comp_star.mass + rc2_f = 10.**(comp_star.log_R) + else: + raise ValueError( + "comp_type in common envelope is not recognized. " + "comp_type = {}, don't know how to proceed". + format(comp_type)) + if mc2_f > mc2_i: + mc2_f = mc2_i + Pwarn("The accretor's final core mass (even after " + "non-conservative stable, postCEE MT) is " + "higher that postCEE core mass. " + "Now equalizing to postCEE mass", "ApproximationWarning") + if verbose: + print("m1 core mass (in Msun) change after fast CE phase from: ", + mc1_i, " to: ", mc1_f) + print("r1 core radius (in Rsun) change after fast CE phase " + "from: ", rc1_i, " to: ", rc1_f) + print("m2 core mass (in Msun) change after fast CE phase from: ", + mc2_i, " to: ", mc2_f) + print("r2 core radius (in Rsun) change after fast CE phase " + "from: ", rc2_i, " to: ", rc2_f) + + return mc1_f, rc1_f, mc2_f, rc2_f + + def CEE_one_phase_variable_core_definition(self, donor, mc1_i, rc1_i, + comp_star, mc2_i, rc2_i, + separation_postCEE, + verbose=False): + """Calculate the post-common-envelope parameters upon exiting a CEE. + + This prescription assumes the he_core_mass/radius (or + co_core_mass/radius for CEE of stripped_He*) are not further stripped + beyond the core defined given by the core_definition_H/He_fraction. + Hence, there are no changes on the orbit after successful ejection. + Instead it is assumed that the defined core will become the remaining + stripped object with core abundances at its surface. + + Parameters + ---------- + donor : SingleStar object + The donor star + mc1_i : float + Core mass of the donor after the fast CE phase (in Msun) + rc1_i : float + Core radius of the donor after the fast CE phase (in Rsun) + comp_star : SingleStar object + The companion star + mc2_i : float + Core mass of the companion after the fast CE phase (in Msun) + rc2_i : float + Core radius of the companion after the fast CE phase (in Rsun) + separation_postCEE : float + Binary's separation after the fast CE phase (in cm) + verbose : bool + In case we want information about the CEE. + + Returns + ------- + m1c_f: float + Donor mass after the complete CE (in Msun) + r1c_f: float + Donor radius after the complete CE (in Rsun) + m2c_f: float + Companion mass after the complete CE (in Msun) + r2c_f: float + Companion radius after the complete CE (in Rsun) + separation_f : float + Binary's final separation upon exiting the CE (in Rsun) + orbital_period_f : float + Binary's final orbital period upon exiting the CE (in days) + merger : bool + Whether the binary merged in the CE + """ + mc1_f = mc1_i + rc1_f = rc1_i + mc2_f = mc2_i + rc2_f = rc2_i + if verbose: + print("m1 core mass pre CEE (He,CO) / to after CEE : ", + donor.he_core_mass, donor.co_core_mass, mc1_f) + print("m1 core radius pre CEE (He,CO) / to after CEE : ", + donor.he_core_radius, donor.co_core_radius, rc1_f) + print("m2 core mass pre CEE (He,CO) / to after CEE : ", + comp_star.he_core_mass, comp_star.co_core_mass, mc2_f) + print("m2 core radius pre CEE (He,CO) / to after CEE : ", + comp_star.he_core_radius, comp_star.co_core_radius, rc2_f) + + # Check to see if the system has merged + merger = cf.check_for_RLO(mc1_i, rc1_f, mc2_i, rc2_f, + separation_postCEE/const.Rsun, self.CEE_tolerance_err) + + if verbose: + if merger: + print("system merges within the CEE") + else: + print("system survives CEE, the whole CE is ejected") + + # since the binary components' masses do not change, the output + # separation is just the input separation in different units + separation_f = separation_postCEE / const.Rsun + + # Calculate the orbital period accordingly + orbital_period_f = cf.orbital_period_from_separation(separation_f, + mc1_i, mc2_i) + + return (mc1_f, rc1_f, mc2_f, rc2_f, separation_f, orbital_period_f, + merger) + + def CEE_two_phases_stableMT(self, donor, mc1_i, rc1_i, donor_type, + comp_star, mc2_i, rc2_i, comp_type, double_CE, + separation_postCEE, verbose=False): + """Calculate the post-common-envelope parameters upon exiting a CEE. + + This prescription assumes the he_core_mass/radius (or + co_core_mass/radius for CEE of stripped_He*) becomes the value + determined by MESA pre CEE. It stripes the envelope between the core + defined by the core_definition_H/He_fraction and the MESA core by + assuming an instantaneous stable MT phase (non-conservative, with mass + lost from the accretor) after the successful ejection of the outer + envelope part lost during the fast CE phase. + + Parameters + ---------- + donor : SingleStar object + The donor star + mc1_i : float + Core mass of the donor after the fast CE phase (in Msun) + rc1_i : float + Core radius of the donor after the fast CE phase (in Rsun) + comp_star : SingleStar object + The companion star + mc2_i : float + Core mass of the companion after the fast CE phase (in Msun) + rc2_i : float + Core radius of the companion after the fast CE phase (in Rsun) + separation_postCEE : float + Binary's separation after the fast CE phase (in cm) + verbose : bool + In case we want information about the CEE. + + Returns + ------- + m1c_f: float + Donor mass after the complete CE (in Msun) + r1c_f: float + Donor radius after the complete CE (in Rsun) + m2c_f: float + Companion mass after the complete CE (in Msun) + r2c_f: float + Companion radius after the complete CE (in Rsun) + separation_f : float + Binary's final separation upon exiting the CE (in Rsun) + orbital_period_f : float + Binary's final orbital period upon exiting the CE (in days) + merger : bool + Whether the binary merged in the CE + """ + if double_CE: + Pwarn("A double CE cannot have a stable mass transfer afterwards " + "as both cores are donors, switch to losing the mass as " + "wind.", "ReplaceValueWarning") + return self.CEE_two_phases_windloss(donor, mc1_i, rc1_i, + donor_type, comp_star, mc2_i, + rc2_i, comp_type, double_CE, + separation_postCEE, verbose) + # First find the post-CE parameters for each star + mc1_f, rc1_f, mc2_f, rc2_f = self.CEE_adjust_post_CE_core_masses( + donor, mc1_i, rc1_i, donor_type, comp_star, mc2_i, rc2_i, + comp_type, double_CE, verbose=verbose) + + # calculate the orbital period after CEE (before adjustment) + orbital_period_postCEE = cf.orbital_period_from_separation( + separation_postCEE / const.Rsun, mc1_i, mc2_i) + + # First, check if merger happens before stable MT phase. Note this uses + # the final core radii as we are beyond the point of radius contraction + # (the final radii are a better estimate for the radius after + # contraction) then the post-CEE value. But the masses are taken + # post-CEE. Hence, this might not detect all mergers, while not + # classifying any surviving binary as merger. + merger = cf.check_for_RLO(mc1_i, rc1_f, mc2_i, rc2_f, + separation_postCEE / const.Rsun, self.CEE_tolerance_err) + + if merger: + if verbose: + print("system merges within the CEE, prior to stable MT") + return mc1_i, rc1_f, mc2_i, rc2_f, \ + separation_postCEE / const.Rsun, orbital_period_postCEE, merger + else: + if verbose: + print("system survives initial CEE detachment") + print("donor core mass / core radius:", mc1_f, rc1_i) + print("companion core mass / core radius:", mc2_f, rc2_i) + + + # An assumed stable mass transfer case after postCEE with + # fully non-conservative MT and mass lost from the vicinity + # of the accretor: + orbital_period_f = cf.period_change_stable_MT( + orbital_period_postCEE, Mdon_i=mc1_i, Mdon_f=mc1_f, + Macc_i=mc2_i, alpha=0.0, beta=1.0) + + if verbose: + print("during the assumed stable MT phase after postCE") + print("the orbit changed from postCEE : ", orbital_period_postCEE) + print("to : ", orbital_period_f) + + # Calculate the post-CEE separation + separation_f = cf.orbital_separation_from_period(orbital_period_f, + mc1_f, mc2_f) + + # Check once more to see if the system has merged during stable MT + merger = cf.check_for_RLO(mc1_f, rc1_f, mc2_f, rc2_f, separation_f, + self.CEE_tolerance_err) + + if verbose: + if merger: + print("system merges within the CEE") + else: + print("system survives CEE, the whole CE is ejected and the " + "orbit is adopted according to the stable MT") + + return (mc1_f, rc1_f, mc2_f, rc2_f, separation_f, orbital_period_f, + merger) + + def CEE_two_phases_windloss(self, donor, mc1_i, rc1_i, donor_type, + comp_star, mc2_i, rc2_i, comp_type, + double_CE, separation_postCEE, verbose=False): + """Calculate the post-common-envelope parameters upon exiting a CEE. + + This prescription assumes the he_core_mass/radius (or + co_core_mass/radius for CEE of stripped_He*) becomes the value + determined by MESA pre CEE. It stripes the envelope between the core + defined by the core_definition_H/He_fraction and the MESA core by + assuming an instantaneous wind loss phase from the donor (or both + components in case of a double CE) after the successful ejection of the + outer envelope part lost during the fast CE phase. + + Parameters + ---------- + donor : SingleStar object + The donor star + mc1_i : float + Core mass of the donor after the fast CE phase (in Msun) + rc1_i : float + Core radius of the donor after the fast CE phase (in Rsun) + comp_star : SingleStar object + The companion star + mc2_i : float + Core mass of the companion after the fast CE phase (in Msun) + rc2_i : float + Core radius of the companion after the fast CE phase (in Rsun) + separation_postCEE : float + Binary's separation after the fast CE phase (in cm) + verbose : bool + In case we want information about the CEE. + + Returns + ------- + m1c_f: float + Donor mass after the complete CE (in Msun) + r1c_f: float + Donor radius after the complete CE (in Rsun) + m2c_f: float + Companion mass after the complete CE (in Msun) + r2c_f: float + Companion radius after the complete CE (in Rsun) + separation_f : float + Binary's final separation upon exiting the CE (in Rsun) + orbital_period_f : float + Binary's final orbital period upon exiting the CE (in days) + merger : bool + Whether the binary merged in the CE + """ + # First find the post-CE parameters for each star + mc1_f, rc1_f, mc2_f, rc2_f = self.CEE_adjust_post_CE_core_masses( + donor, mc1_i, rc1_i, donor_type, comp_star, mc2_i, rc2_i, + comp_type, double_CE, verbose=verbose) + + # calculate the orbital period after CEE (before adjustment) + orbital_period_postCEE = cf.orbital_period_from_separation( + separation_postCEE / const.Rsun, mc1_i, mc2_i) + + # First, check if merger happens before wind loss phase. Note this uses + # the final core radii as we are beyond the point of radius contraction + # (the final radii are a better estimate for the radius after + # contraction) then the post-CEE value. But the masses are taken + # post-CEE. Hence, this might not detect all mergers, while not + # classifying any surviving binary as merger. + merger = cf.check_for_RLO(mc1_i, rc1_f, mc2_i, rc2_f, + separation_postCEE / const.Rsun, self.CEE_tolerance_err) + + if merger: + if verbose: + print("system merges within the CEE, prior to wind loss") + return mc1_i, rc1_f, mc2_i, rc2_f, \ + separation_postCEE / const.Rsun, orbital_period_postCEE, merger + else: + if verbose: + print("system survives initial CEE detachment") + print("donor core mass / core radius:", mc1_f, rc1_i) + print("companion core mass / core radius:", mc2_f, rc2_i) + + + # An assumed wind loss after postCEE with mass lost + # from the vicinity of the donor + orbital_period_f = cf.period_change_stable_MT( + orbital_period_postCEE, Mdon_i=mc1_i, Mdon_f=mc1_f, + Macc_i=mc2_i, alpha=1.0, beta=0.0) + + if double_CE: + # a wind mass loss from the 2nd star assumed to be + # happening at the same time + orbital_period_f = cf.period_change_stable_MT( + orbital_period_f, Mdon_i=mc2_i, Mdon_f=mc2_f, + Macc_i=mc1_f, alpha=1.0, beta=0.0) + if verbose: + print("during the assumed windloss phase after postCE") + print("the orbit changed from postCEE : ", orbital_period_postCEE) + print("to : ", orbital_period_f) + + # Calculate the post-CEE separation + separation_f = cf.orbital_separation_from_period(orbital_period_f, + mc1_f, mc2_f) + + # Check once more to see if the system has merged during stable MT + merger = cf.check_for_RLO(mc1_f, rc1_f, mc2_f, rc2_f, separation_f, + self.CEE_tolerance_err) + + if verbose: + if merger: + print("system merges within the CEE") + else: + print("system survives CEE, the whole CE is ejected and the " + "orbit is adopted according to the wind mass loss") + + return (mc1_f, rc1_f, mc2_f, rc2_f, separation_f, orbital_period_f, + merger) + def CEE_simple_alpha_prescription( self, binary, donor, comp_star, lambda1_CE, mc1_i, rc1_i, donor_type, lambda2_CE, mc2_i, rc2_i, comp_type, double_CE=False, @@ -445,17 +856,13 @@ def CEE_simple_alpha_prescription( common_envelope_option_after_succ_CEE: str Options are: - 1) "core_replaced_noMT" + 1) "one_phase_variable_core_definition" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) are replaced according to the new core boundary used for CEE (based on core_definition_H/He_fraction) but no other change in period after succesful ejection at alpha-lambda prescription. - 2) "core_not_replaced_noMT" - he_core_mass/radius (or co_core_mass/radius for CEE of - stripped_He*) staying as preCEE and no other change in period - after succesful ejection at alpha-lambda prescription. - 3) "core_not_replaced_stableMT" + 2) "two_phases_stableMT" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) staying as preCEE and after succesful ejection at alpha-lambda prescription, we assume an instantaneous stableMT @@ -464,7 +871,7 @@ def CEE_simple_alpha_prescription( double_CE), taking away the extra "core" mass as defined by the core boundary used for CEE (based on core_definition_H/He_fraction). - 4) "core_not_replaced_windloss" + 3) "two_phases_windloss" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) staying as preCEE and after succesful ejection at alpha-lambda prescription, we assume a instantaneous @@ -528,7 +935,7 @@ def CEE_simple_alpha_prescription( * mc2_i * const.Msun / eorb_postCEE) # Check to make sure final orbital separation is positive - if not (separation_postCEE > -self.CEE_tolerance_err): + if separation_postCEE < -self.CEE_tolerance_err: raise ValueError("CEE problem, negative postCEE separation") if verbose: @@ -540,364 +947,481 @@ def CEE_simple_alpha_prescription( print("separation_i in Rsun", separation_i/const.Rsun) print("separation_postCEE in Rsun", separation_postCEE/const.Rsun) - # now we check if the roche Lobe of any of the cores that spiralled-in - # will be filled if reached this final separation - RL1 = cf.roche_lobe_radius(mc1_i, mc2_i, separation_postCEE/const.Rsun) - RL2 = cf.roche_lobe_radius(mc2_i, mc1_i, separation_postCEE/const.Rsun) + + # Calculate the post-CE binary properties + if (common_envelope_option_after_succ_CEE + == "one_phase_variable_core_definition"): + (mc1_f, rc1_f, mc2_f, rc2_f, separation_f, orbital_period_f, + merger) = self.CEE_one_phase_variable_core_definition(donor, + mc1_i, rc1_i, comp_star, mc2_i, rc2_i, + separation_postCEE, verbose=verbose) + elif (common_envelope_option_after_succ_CEE + == "two_phases_stableMT"): + (mc1_f, rc1_f, mc2_f, rc2_f, separation_f, orbital_period_f, + merger) = self.CEE_two_phases_stableMT(donor, mc1_i, rc1_i, + donor_type, comp_star, + mc2_i, rc2_i, comp_type, + double_CE, + separation_postCEE, + verbose=verbose) + elif (common_envelope_option_after_succ_CEE + == "two_phases_windloss"): + (mc1_f, rc1_f, mc2_f, rc2_f, separation_f, orbital_period_f, + merger) = self.CEE_two_phases_windloss(donor, mc1_i, rc1_i, + donor_type, comp_star, + mc2_i, rc2_i, comp_type, + double_CE, + separation_postCEE, + verbose=verbose) + else: + raise ValueError("Not accepted option in " + "common_envelope_option_after_succ_CEE = " + f"{common_envelope_option_after_succ_CEE}, do " + "not know how to proceed") + + # Adjust stellar and binary parameters depending on whether the system + # mergers in the CE or not + if merger: + # Calculate the amount of mass lost during the merger + if mass_loss_during_CEE_merged: + Mejected_donor, Mejected_comp = \ + self.CEE_adjust_mass_loss_during_CEE_merged(donor, m1_i, + mc1_i, rc1_i, comp_star, m2_i, mc2_i, rc2_i, + separation_i, alpha_CE, radius1, radius2, double_CE, + verbose=verbose) + else: + Mejected_donor = 0.0 + Mejected_comp = 0.0 + + # Adjust the binary and single star properties due to merger + self.CEE_adjust_binary_upon_merger(binary, donor, comp_star, m1_i, + m2_i, donor_type, comp_type, + Mejected_donor, Mejected_comp, + verbose) + else: + # Adjust the binary and single star properties due to ejection + self.CEE_adjust_binary_upon_ejection(binary, donor, mc1_f, rc1_f, + donor_type, comp_star, mc2_f, rc2_f, comp_type, double_CE, + separation_f, orbital_period_f, + common_envelope_option_after_succ_CEE, + core_definition_H_fraction, + core_definition_He_fraction, + verbose) + + return + + + + def CEE_adjust_binary_upon_ejection(self, binary, donor, mc1_f, rc1_f, + donor_type, comp_star, mc2_f, rc2_f, comp_type, double_CE, + separation_f, orbital_period_f, + common_envelope_option_after_succ_CEE, + core_definition_H_fraction, + core_definition_He_fraction, + verbose=False): + """Update the binary and component stars upon exiting a CEE. + + The binary's parameters (orbital period, separation, state, etc.) are + updated along with the donor's (and in the case of a double CE the + companion's) parameters as well. Note that certain parameters are set + to np.nan if they are undetermined after the CE. + + Parameters + ---------- + binary: BinaryStar object + The binary system + donor : SingleStar object + The donor star + mc1_f : float + Final core mass of the donor (in Msun) + rc1_f : float + Final core radius of the donor (in Rsun) + donor_type : string + Descriptor for the stellar type of the donor's core + comp_star : SingleStar object + The companion star + mc2_f : float + Final core mass of the companion (in Msun) + rc2_f : float + Final core radius of the companion (in Rsun) + comp_type : string + Descriptor for the stellar type of the companion or it's core + double_CE : bool + Whether the CEE is a double CE or not + separation_f : float + Final binary separation upon exiting the CEE (in Rsun) + orbital_period_f : float + Final orbital period upon exiting the CEE (in days) + common_envelope_option_after_succ_CEE : string + Which type of post-common envelope evolution is used to remove + the final layers around the core + core_definition_H_fraction : float + The fractional abundance of H defining the He core (0.3, 0.1, or + 0.01) + core_definition_He_fraction : float + The fractional abundance of He defining the CO core (typically 0.1) + verbose : bool + In case we want information about the CEE. + """ + # Adjust binary properties + binary.separation = separation_f + binary.orbital_period = orbital_period_f + binary.eccentricity = 0.0 + binary.state = 'detached' + binary.event = None + + # If the binary is sufficiently evolved (core helium exhaustion), then + # don't sent it to the detached step after successful ejection, but + # send the binary directly to the core collapse step to calculate the + # explosion + if donor.state == 'stripped_He_Central_He_depleted': + if donor == binary.star_1: + binary.event = 'CC1' + elif donor == binary.star_2: + binary.event = 'CC2' if verbose: - print("donor radius / core radius / RL1:", radius1, rc1_i, RL1) - print("companion radius / core radius / RL2:", radius2, rc2_i, RL2) - - # Check if binary merges or survives - if ((rc1_i - RL1) < self.CEE_tolerance_err - and (rc2_i - RL2) < self.CEE_tolerance_err): - # system survives CEE, the whole CE is ejected - # and the new orbital separation for the cores is returned - if verbose: - print("system survives CEE, the whole CE is ejected and the " - "new orbital separation for the cores is returned") + print("CEE succesfully ejected") + print("new orbital period = ", binary.orbital_period) + print("new orbital separation = ", binary.separation) + print("binary event : ", binary.event) + print("double CEE : ", double_CE) - # possible change of core masses - if common_envelope_option_after_succ_CEE in ["core_replaced_noMT"]: - mc1_f = mc1_i - rc1_f = rc1_i - mc2_f = mc2_i - rc2_f = rc2_i - if verbose: - print("m1 core mass pre CEE (He,CO) / to after CEE : ", - donor.he_core_mass, donor.co_core_mass, mc1_f) - print("m1 core radius pre CEE (He,CO) / to after CEE : ", - donor.he_core_radius, donor.co_core_radius, rc1_f) - print("m2 core mass pre CEE (He,CO) / to after CEE : ", - comp_star.he_core_mass, comp_star.co_core_mass, mc2_f - ) - print("m2 core radius pre CEE (He,CO) / to after CEE : ", - comp_star.he_core_radius, - comp_star.co_core_radius, rc2_f) - elif common_envelope_option_after_succ_CEE in [ - "core_not_replaced_noMT", - "core_not_replaced_stableMT", - "core_not_replaced_windloss"]: - if donor_type == 'He_core': - mc1_f = donor.he_core_mass - rc1_f = donor.he_core_radius - elif donor_type == "CO_core": - mc1_f = donor.co_core_mass - rc1_f = donor.co_core_radius - if mc1_f > mc1_i: - mc1_f = mc1_i - Pwarn( - "The final donor core mass (even after stable, postCEE MT)" - " is higher than the postCEE core mass. Now equalizing to " - "postCEE mass", "ApproximationWarning") - if not double_CE: - mc2_f = mc2_i - rc2_f = rc2_i + # Set binary values that are not set in CE step to np.nan + for key in BINARYPROPERTIES: + + # the binary attributes that are changed in the CE step + if key in ["separation", "orbital_period", + "eccentricity", "state", "event"]: + continue + + # the binary attributes that keep the same value from the + # previous step + if key in ["time", "V_sys", "mass_transfer_case", + "nearest_neighbour_distance"]: + continue + + # the rest become np.nan + setattr(binary, key, np.nan) + + # Create list of stars that need updated parameters. If normal CE, + # then only donor has updated properties + stars = [donor] + star_types = [donor_type] + core_masses = [mc1_f] + core_radii = [rc1_f] + + # If a double CE, then the companion's properties are updated, too + if double_CE: + stars.append(comp_star) + star_types.append(comp_type) + core_masses.append(mc2_f) + core_radii.append(rc2_f) + + # Adjust stellar properties + for star, star_type, core_mass, core_radius in zip( + stars, star_types, core_masses, core_radii): + star.mass = core_mass + star.log_R = np.log10(core_radius) + attributes_changing = [ + "mass", + "log_R", + "state" + ] + + if star_type == 'He_core': + if common_envelope_option_after_succ_CEE in [ + "one_phase_variable_core_definition"]: + star.surface_h1 = core_definition_H_fraction else: - if comp_type == 'He_core': - mc2_f = comp_star.he_core_mass - rc2_f = donor.he_core_radius - elif comp_type == "CO_core": - mc2_f = comp_star.co_core_mass - rc2_f = donor.co_core_radius - elif comp_type == "not_giant_companion": - mc2_f = comp_star.mass - rc2_f = 10.**(comp_star.log_R) - if mc2_f > mc2_i: - mc2_f = mc2_i - Pwarn("The accretor's final core mass (even after " - "non-conservative stable, postCEE MT) is " - "higher that postCEE core mass. " - "Now equalizing to postCEE mass", "ApproximationWarning") - if verbose: - print("difference between m1 core mass defined by CEE step" - " / to the final one as pre CEE : ", mc1_f, mc1_i) - print("difference between r1 core mass defined by CEE step" - " / to the final one as pre CEE : ", rc1_f, rc1_i) - print("difference between m2 core mass defined by CEE step" - " / to the final one as pre CEE : ", mc2_f, mc2_i) - print("difference between r2 core mass defined by CEE step" - " / to the final one as pre CEE : ", rc2_f, rc2_i) - else: - raise ValueError( - "Not accepted option in common_envelope_option_after_succ_" - "CEE = {}, dont know how to proceed". - format(common_envelope_option_after_succ_CEE)) - - # possible change of orbital period after CEE - orbital_period_postCEE = cf.orbital_period_from_separation( - separation_postCEE / const.Rsun, mc1_i, mc2_i) - if common_envelope_option_after_succ_CEE in [ - "core_replaced_noMT", "core_not_replaced_noMT"]: - orbital_period_f = orbital_period_postCEE - elif common_envelope_option_after_succ_CEE in [ - "core_not_replaced_stableMT"]: - # An assumed stable mass transfer case after postCEE with - # fully non-conservative MT and mass lost from the vicinity - # of the accretor: - orbital_period_f = cf.period_change_stable_MT( - orbital_period_postCEE, Mdon_i=mc1_i, Mdon_f=mc1_f, - Macc_i=mc2_i, alpha=0.0, beta=1.0) - if double_CE: - # a reverse stable MT assumed to be happening - # at the same time - orbital_period_f = cf.period_change_stable_MT( - orbital_period_f, Mdon_i=mc2_i, Mdon_f=mc2_f, - Macc_i=mc1_i, alpha=0.0, beta=1.0) - if verbose: - print("during the assumed stable MT phase after postCE") - print("with 'common_envelope_option_after_succ_CEE' :", - common_envelope_option_after_succ_CEE) - print("the orbit cahnged from postCEE : ", - orbital_period_postCEE) - print("to : ", orbital_period_f) - elif common_envelope_option_after_succ_CEE in [ - "core_not_replaced_windloss"]: - # An assumed wind loss after postCEE with mass lost - # from the vicinity of the donor - orbital_period_f = cf.period_change_stable_MT( - orbital_period_postCEE, Mdon_i=mc1_i, Mdon_f=mc1_f, - Macc_i=mc2_i, alpha=1.0, beta=0.0) - if double_CE: - # a wind mass loss from the 2nd star assumed to be - # happening at the same time - orbital_period_f = cf.period_change_stable_MT( - orbital_period_f, Mdon_i=mc2_i, Mdon_f=mc2_f, - Macc_i=mc1_i, alpha=1.0, beta=0.0) - if verbose: - print("during the assumed windloss phase after postCE") - print("with 'common_envelope_option_after_succ_CEE' :", - common_envelope_option_after_succ_CEE) - print("the orbit cahnged from postCEE : ", - orbital_period_postCEE) - print("to : ", orbital_period_f) - separation_f = cf.orbital_separation_from_period(orbital_period_f, - mc1_f, mc2_f) - - if state1_i == 'stripped_He_Central_He_depleted': - if donor == binary.star_1: - binary.event = 'CC1' - elif donor == binary.star_2: - binary.event = 'CC2' + star.surface_h1 = 0.01 + star.he_core_mass = core_mass + star.he_core_radius = core_radius + if star.metallicity is None: + star.surface_he4 = 1 - star.surface_h1 - 0.0142 + else: + star.surface_he4 = 1.0-star.surface_h1-star.metallicity + star.log_LH = -1e99 + attributes_changing.extend([ + "surface_h1", + "surface_he4", + "log_LH", + "log_LHe", + 'he_core_mass', + 'he_core_radius' + ]) + elif star_type == 'CO_core': + star.he_core_mass = core_mass + star.he_core_radius = core_radius + star.co_core_mass = core_mass + star.co_core_radius = core_radius + star.surface_h1 = 0.0 + if common_envelope_option_after_succ_CEE in [ + "one_phase_variable_core_definition"]: + star.surface_he4 = core_definition_He_fraction + else: + star.surface_he4 = 0.1 + star.log_LH = -1e99 + star.log_LHe = -1e99 + attributes_changing.extend([ + "surface_h1", + "surface_he4", + "log_LH", + "log_LHe", + 'he_core_mass', + 'he_core_radius', + 'co_core_mass', + 'co_core_radius' + + ]) + elif star_type == "not_giant_companion": + continue else: - binary.event = None - - # Adjust binary properties - binary.separation = separation_f - binary.orbital_period = orbital_period_f - binary.eccentricity = 0.0 - binary.state = 'detached' - # binary.event = None - - for key in BINARYPROPERTIES: - # the binary attributes that are changed in the CE step - if key not in ["separation", "orbital_period", - "eccentricity", "state", "event"]: - # the binary attributes that keep the same value from the - # previous step - if key not in ["time", "V_sys", "mass_transfer_case", - "nearest_neighbour_distance"]: - setattr(binary, key, np.nan) # the rest become np.nan - - stars = [donor] - star_types = [donor_type] - core_masses = [mc1_f] - core_radii = [rc1_f] + raise ValueError("Unrecognized star type:", star_type) + # Update state of star + state_old = star.state + star.state = check_state_of_star(star) if verbose: - print("CEE succesfully ejected") - print("new orbital period = ", binary.orbital_period) - print("binary event : ", binary.event) - print("double CEE : ", double_CE) + print("star state of before/ after CE : ", + state_old, star.state) - if double_CE: - stars.append(comp_star) - star_types.append(comp_type) - core_masses.append(mc2_f) - core_radii.append(rc2_f) - - # Adjust donor star properties - for star, star_type, core_mass, core_radius in zip( - stars, star_types, core_masses, core_radii): - star.mass = core_mass - star.log_R = np.log10(core_radius) - attributes_changing = [ - "mass", - "log_R", - "state" - ] - - if star_type == 'He_core': - if common_envelope_option_after_succ_CEE in [ - "core_replaced_noMT"]: - star.surface_h1 = core_definition_H_fraction - else: - star.surface_h1 = 0.01 - star.he_core_mass = core_mass - star.he_core_radius = core_radius - if star.metallicity is None: - star.surface_he4 = 1 - star.surface_h1 - 0.0142 - else: - star.surface_he4 = 1.0-star.surface_h1-star.metallicity - star.log_LH = -1e99 - attributes_changing.extend([ - "surface_h1", - "surface_he4", - "log_LH", - "log_LHe", - 'he_core_mass', - 'he_core_radius' - ]) - #TODO: update 'total_mass_h1' and 'total_mass_he4' - elif star_type == 'CO_core': - star.he_core_mass = core_mass - star.he_core_radius = core_radius - star.co_core_mass = core_mass - star.co_core_radius = core_radius - star.surface_h1 = 0.0 - if common_envelope_option_after_succ_CEE in [ - "core_replaced_noMT"]: - star.surface_he4 = core_definition_He_fraction - else: - star.surface_he4 = 0.1 - star.log_LH = -1e99 - star.log_LHe = -1e99 - attributes_changing.extend([ - "surface_h1", - "surface_he4", - "log_LH", - "log_LHe", - 'he_core_mass', - 'he_core_radius', - 'co_core_mass', - 'co_core_radius' - - ]) - #TODO: update 'total_mass_h1' and 'total_mass_he4' - elif star_type == "not_giant_companion": - continue - else: - raise ValueError("Unrecognized star type:", star_type) - - state_old = star.state - star.state = check_state_of_star(star) - if verbose: - print("star state of before/ after CE : ", - state_old, star.state) - - for key in STARPROPERTIES: - if key not in attributes_changing: - # the singlestar attributes that are changed - # in the CE step - - # the singlestar attributes that keep the same value - # from previous step if not in attributes_changing - if key not in [ - "surface_h1", "surface_he4", "log_LH", - "log_LHe", "he_core_mass", "he_core_radius", - "co_core_mass", "co_core_radius", "center_h1", - "center_he4", "center_c12", "center_o16", - "center_n14", "metallicity", "log_LZ", - "log_Lnuc", "c12_c12", "center_gamma", - "avg_c_in_c_core"]: - setattr(star, key, np.nan) # the rest become NaN's + # Set star values that are unchanged in CE step to np.nan + for key in STARPROPERTIES: - else: - # system merges - binary.state = 'merged' - if binary.event in ["oCE1", "oDoubleCE1"]: - binary.event = "oMerging1" - if binary.event in ["oCE2", "oDoubleCE2"]: - binary.event = "oMerging2" + # the singlestar attributes that are changed + # in the CE step + if key in attributes_changing: + continue - if verbose: - print("system merges due to one of the two star's core filling" - "its RL") - print("Rdonor core vs RLdonor core = ", rc1_i, RL1) - print("Rcompanion vs RLcompanion= ", rc2_i, RL2) + # the singlestar attributes that keep the same value + # from previous step if not in attributes_changing + if key in [ + "surface_h1", "surface_he4", "log_LH", + "log_LHe", "he_core_mass", "he_core_radius", + "co_core_mass", "co_core_radius", "center_h1", + "center_he4", "center_c12", "center_o16", + "center_n14", "metallicity", "log_LZ", + "log_Lnuc", "c12_c12", "center_gamma", + "avg_c_in_c_core"]: + continue - Mejected_donor = 0.0 - Mejected_comp = 0.0 + # the rest become NaN's + setattr(star, key, np.nan) + return - if mass_loss_during_CEE_merged: - # we calculate the ejected mass from part of the commone envelope, using - # a_f = separation_postCEE so that one of the cores (or MS star) is filling its inner Roche lobe, - # and assuming that lambda(Menvelope) ~ lamda(Mejected) although - # Mejected < Menvelope (e.g. see Fig1 of Dewi+Tauris2000) - - if not double_CE and donor.profile is None: - Mejected_donor = 0.0 - Mejected_comp = 0.0 - Pwarn("mass_loss_during_CEE_merged == True, but no profile found " - "for the donor star. Proceeding with no partial mass ejection.", "ApproximationWarning") - elif double_CE and (donor.profile is None or comp_star.profile is None): - Mejected_comp = 0.0 - Mejected_comp = 0.0 - Pwarn("mass_loss_during_CEE_merged == True, but not profile found " - "for the donor or companion star in double_CE. Proceeding with no partial mass ejection.", - "ApproximationWarning") + def CEE_adjust_mass_loss_during_CEE_merged(self, donor, m1_i, mc1_i, rc1_i, + comp_star, m2_i, mc2_i, rc2_i, + separation_i, alpha_CE, radius1, + radius2, + double_CE, verbose=False): + """ Calculate the amount of mass lost during a stellar merger in a CEE - else: + From the stellar profiles the mass ejected until merger is calculated. + Note that this function only returns non-zero mass-loss values if a + profile is available. - separation_for_inner_RLO1 = rc1_i / cf.roche_lobe_radius(mc1_i, mc2_i, a_orb=1) - separation_for_inner_RLO2 = rc2_i / cf.roche_lobe_radius(mc2_i, mc1_i, a_orb=1) + Parameters + ---------- + donor : SingleStar object + The donor star + m1_i : float + Initial mass of the donor (in Msun) + mc1_i : float + Initial core mass of the donor (in Msun) + rc1_i : float + Initial core radius of the donor (in Rsun) + comp_star : SingleStar object + The companion star + m2_i : float + Initial mass of the companion (in Msun) + mc2_i : float + Initial core mass of the companion (in Msun) + rc2_i : float + Initial core radius of the companion (in Rsun) + separation_i : float + Initial separation of the binary (in cm) + alpha_CE : float + Common envelope efficiency parameter (unitless) + radius1 : float + Initial radius of the donor (in Rsun) + radius2 : float + Initial radius of the companion (in Rsun) + double_CE : bool + Whether the CEE is a double CE or not + verbose : bool + In case we want information about the CEE. - separation_before_merger = max( separation_for_inner_RLO1, separation_for_inner_RLO2 ) * const.Rsun + Returns + ------- + Mejected_donor : float + Mass ejected from the donor (in Msun) + Mejected_comp : float + Mass ejected from the companion (in Msun) + """ + # we calculate the ejected mass from part of the common envelope, using + # a_f = separation_postCEE so that one of the cores (or MS star) is + # filling its inner Roche lobe - if verbose: - print("separation_before_merger (for the calculation of Mejected for the merger): ",separation_before_merger/ const.Rsun , "in Rsun") - print("which is the max of RLO1 or RLO2 of the inner cores: ", separation_for_inner_RLO1 , separation_for_inner_RLO2, "in Rsun") + # First, make sure we have information to calculate ejected mass + if not double_CE and donor.profile is None: + Mejected_donor = 0.0 + Mejected_comp = 0.0 + Pwarn("mass_loss_during_CEE_merged == True, but no profile found " + "for the donor star. Proceeding with no partial mass " + "ejection.", "ApproximationWarning") + if verbose: + print("Mejecta_donor = 0 in Msun compared to its initial " + "envelope =", m1_i - mc1_i) + print("Mejecta_comp = 0 in Msun compared to its initial " + "envelope =", m2_i - mc2_i) + return Mejected_donor, Mejected_comp + elif double_CE and (donor.profile is None or comp_star.profile is None): + Mejected_donor = 0.0 + Mejected_comp = 0.0 + Pwarn("mass_loss_during_CEE_merged == True, but no profile found " + "for the donor or companion star in double_CE. Proceeding " + "with no partial mass ejection.", "ApproximationWarning") + if verbose: + print("Mejecta_donor = 0 in Msun compared to its initial " + "envelope =", m1_i - mc1_i) + print("Mejecta_comp = 0 in Msun compared to its initial " + "envelope =", m2_i - mc2_i) + return Mejected_donor, Mejected_comp + else: + pass - E_orb_used_up_to_inner_RLOF = alpha_CE * ( - const.standard_cgrav * m1_i * const.Msun * m2_i * const.Msun/(2. * separation_i) + \ - const.standard_cgrav * mc1_i * const.Msun * mc2_i * const.Msun/(2. * separation_before_merger) ) + separation_for_inner_RLO1 = rc1_i / cf.roche_lobe_radius(mc1_i, mc2_i, a_orb=1) + separation_for_inner_RLO2 = rc2_i / cf.roche_lobe_radius(mc2_i, mc1_i, a_orb=1) - # We assume "lambda_from_profile_gravitational_plus_internal_minus_recombination" - if not double_CE: - Mejected_donor = calculate_Mejected_for_integrated_binding_energy(donor.profile, E_orb_used_up_to_inner_RLOF, mc1_i, rc1_i, m1_i, radius1) + separation_before_merger = max(separation_for_inner_RLO1, + separation_for_inner_RLO2) * const.Rsun - else: # in double_CE + if verbose: + print("separation_before_merger (for the calculation of Mejected " + "for the merger): ", separation_before_merger/ const.Rsun, + "in Rsun") + print("which is the max of RLO1 or RLO2 of the inner cores: ", + separation_for_inner_RLO1 , separation_for_inner_RLO2, + "in Rsun") + + # Calculate the initial and final orbital energies + E_orb_initial = - const.standard_cgrav * m1_i * const.Msun * m2_i * \ + const.Msun/(2. * separation_i) + E_orb_final = - const.standard_cgrav * mc1_i * const.Msun * mc2_i * \ + const.Msun/(2. * separation_before_merger) + E_orb_released_to_inner_RLOF = alpha_CE * (E_orb_initial - E_orb_final) + + # We assume "lambda_from_profile_gravitational_plus_internal_minus_recombination" + if not double_CE: + Mejected_donor = \ + calculate_Mejected_for_integrated_binding_energy(donor.profile, + E_orb_released_to_inner_RLOF, mc1_i, rc1_i, m1_i, radius1) + + else: # in double_CE + + # Assuming that the ratio of orbital energy used for the partial + # ejection of each (common) envelope is the same as the ratio of + # the initial envelope masses: + + M_envelope_donor = m1_i - mc1_i + M_envelope_companion = m2_i - mc2_i + weight = M_envelope_donor / (M_envelope_donor + M_envelope_companion) + + Eorb_for_partial_ej_donor = E_orb_released_to_inner_RLOF * weight + Mejected_donor = calculate_Mejected_for_integrated_binding_energy( + donor.profile, Eorb_for_partial_ej_donor, mc1_i, + rc1_i, m1_i, radius1) + + Eorb_for_partial_ej_companion = E_orb_released_to_inner_RLOF * \ + (1.0 - weight) + Mejected_comp = calculate_Mejected_for_integrated_binding_energy( + comp_star.profile, Eorb_for_partial_ej_companion, + mc2_i, rc2_i, m2_i, radius2) - # Assuming that the ratio of orbital energy used for the partial ejection of each (common) envelope - # is the same as the ratio of the initial envelope masses: + if verbose: + print("Mejecta_donor = ", Mejected_donor, + "in Msun compared to its initial envelope =", + M_envelope_donor) + print("Mejecta_comp = ", Mejected_comp, + "in Msun compared to its initial envelope =", + M_envelope_companion) + + # Make sure that we are not removing more mass than the envelope has available + # If there is more removed keep 0.01 Msun + if Mejected_donor > M_envelope_donor: + Mejected_donor = M_envelope_donor - 0.01 + Pwarn("M_ejected of the donor is found to be more than its initial " + "envelope. Reducing its mass loss to have a remaining " + "envelope of 0.01 Msun", "ApproximationWarning") + + if Mejected_comp > M_envelope_companion: + Mejected_comp = M_envelope_companion - 0.01 + Pwarn("M_ejected of the companion is found to be more than its " + "initial envelope. Reducing its mass loss to have a " + "remaining envelope of 0.01 Msun", "ApproximationWarning") - WF = (m1_i - mc1_i)/ (m2_i - mc2_i + m1_i - mc1_i) # weight factor for 1: - # = Mdonor,envelope / Mcomp,envelope - Eorb_for_partial_ej_1 = E_orb_used_up_to_inner_RLOF * WF - Mejected_donor = calculate_Mejected_for_integrated_binding_energy(donor.profile, Eorb_for_partial_ej_1, mc1_i, rc1_i, m1_i, radius1) + if verbose: + print("In calculation for mass loss during merging_CEE") + print("Initial donor mass = ", m1_i, + "Mass lost by donor = ", Mejected_donor) + print("Initial companion mass = ", m2_i, + "Mass lost by companion = ", Mejected_comp) - Eorb_for_partial_ej_2 = E_orb_used_up_to_inner_RLOF * (1. - WF) - Mejected_comp = calculate_Mejected_for_integrated_binding_energy(comp_star.profile, Eorb_for_partial_ej_2, mc2_i, rc2_i, m2_i, radius2) + return Mejected_donor, Mejected_comp - if verbose: - print("Mejecta_donor = ", Mejected_donor, "in Msun compared to its initial envelope =", m1_i - mc1_i) - print("Mejecta_comp = ", Mejected_comp, "in Msun compared to its initial envelope =", m2_i - mc2_i) - ''' - if not ( (Mejected_donor <= m1_i - mc1_i) and (Mejected_comp <= m2_i - mc2_i) ): - raise Exception("Mejected_donor in double CEE with mass_loss_during_CEE_merged is found more than Menvelope") - if not (Mejected_donor >= 0.): - raise Exception("The root of the equation in double CEE with mass_loss_during_CEE_merged is found negative") - ''' + def CEE_adjust_binary_upon_merger(self, binary, donor, comp_star, m1_i, + m2_i, donor_type, comp_type, + Mejected_donor, Mejected_comp, + verbose=False): + """Update the binary and component stars upon merging within a CEE. - if (Mejected_donor > m1_i - mc1_i) or (Mejected_comp > m2_i - mc2_i): - Mejected_donor = (m1_i - mc1_i) -0.01 # at least this value of envelope is left. - Mejected_comp = (m2_i - mc2_i) -0.01 - Pwarn("M_ejected of at least one star in double CEE is found to be more than the initial envelope. " - "Reducing both to their initial_envelope - 0.01 Msun", "ApproximationWarning") + The binary's state and event are updated along with the donor and + companion star masses and radii corresponding to a merger event. - donor.mass = m1_i - Mejected_donor - donor.log_R = np.nan - comp_star.mass = m2_i - Mejected_comp - comp_star.log_R = np.nan - if donor_type == 'CO_core': - donor.he_core_mass = m1_i - Mejected_donor - donor.he_core_radius = np.nan - comp_star.he_core_mass = m2_i - Mejected_comp - comp_star.he_core_radius = np.nan + Parameters + ---------- + binary: BinaryStar object + The binary system + donor : SingleStar object + The donor star + comp_star : SingleStar object + The companion star + m1_i : float + Mass of the donor upon entering a CE (in Msun) + m2_i : float + Mass of the companion upon entering a CE (in Msun) + donor_type : string + Descriptor for the stellar type of the donor's core + comp_type : string + Descriptor for the stellar type of the companion or it's core + Mejected_donor : float + How much mass is ejected from the donor upon merger (in Msun) + Mejected_comp : float + How much mass is ejected from the companion upon merger (in Msun) + verbose : bool + In case we want information about the CEE. + """ + # system merges + binary.state = 'merged' + if binary.event in ["oCE1", "oDoubleCE1"]: + binary.event = "oMerging1" + if binary.event in ["oCE2", "oDoubleCE2"]: + binary.event = "oMerging2" - if verbose: - print("The mass loss during merging_CEE is: ", mass_loss_during_CEE_merged) - print("so m1_i , m1_f = ", m1_i, donor.mass) - print("so m2_i , m2_f = ", m2_i, comp_star.mass) + if verbose: + print("system merges due to one of the two star's core filling" + "its RL") + + donor.mass = m1_i - Mejected_donor + donor.log_R = np.nan + comp_star.mass = m2_i - Mejected_comp + comp_star.log_R = np.nan + if donor_type == 'CO_core': + donor.he_core_mass = m1_i - Mejected_donor + donor.he_core_radius = np.nan + if comp_type == 'CO_core': + comp_star.he_core_mass = m2_i - Mejected_comp + comp_star.he_core_radius = np.nan return diff --git a/posydon/binary_evol/MESA/step_mesa.py b/posydon/binary_evol/MESA/step_mesa.py index ca1040533b..a3460ab8dd 100644 --- a/posydon/binary_evol/MESA/step_mesa.py +++ b/posydon/binary_evol/MESA/step_mesa.py @@ -29,7 +29,7 @@ set_binary_to_failed,) from posydon.config import PATH_TO_POSYDON_DATA from posydon.utils.data_download import data_download -from posydon.grids.MODELS import MODELS +from posydon.grids.SN_MODELS import SN_MODELS from posydon.utils.posydonerror import FlowError, GridError from posydon.utils.posydonwarning import Pwarn @@ -814,23 +814,27 @@ def update_properties_NN(self, star_1_CO=False, star_2_CO=False, # update nearest neighbor core collapse quantites if interpolation_class != 'unstable_MT': - for MODEL_NAME in MODELS.keys(): + for SN_MODEL_NAME in SN_MODELS.keys(): for i, star in enumerate(stars): - if (not stars_CO[i] and cb.final_values[f'S{i+1}_{MODEL_NAME}_CO_type'] != 'None'): + col_name = f'S{i+1}_{SN_MODEL_NAME}_CO_type' + if ((not stars_CO[i]) + and (cb.final_values[col_name] != 'None')): values = {} for key in ['state', 'SN_type', 'f_fb', 'mass', 'spin', - 'm_disk_accreted', 'm_disk_radiated', 'M4', 'mu4', - 'h1_mass_ej', 'he4_mass_ej']: + 'm_disk_accreted', 'm_disk_radiated', 'M4', + 'mu4', 'h1_mass_ej', 'he4_mass_ej']: if key == "state": - state = cb.final_values[f'S{i+1}_{MODEL_NAME}_CO_type'] + state = cb.final_values[col_name] values[key] = state elif key == "SN_type": - values[key] = cb.final_values[f'S{i+1}_{MODEL_NAME}_{key}'] + col_name = f'S{i+1}_{SN_MODEL_NAME}_{key}' + values[key] = cb.final_values[col_name] else: - values[key] = cb.final_values[f'S{i+1}_{MODEL_NAME}_{key}'] - setattr(star, MODEL_NAME, values) + col_name = f'S{i+1}_{SN_MODEL_NAME}_{key}' + values[key] = cb.final_values[col_name] + setattr(star, SN_MODEL_NAME, values) else: - setattr(star, MODEL_NAME, None) + setattr(star, SN_MODEL_NAME, None) def initial_final_interpolation(self, star_1_CO=False, star_2_CO=False): """Update the binary through initial-final interpolation.""" @@ -979,28 +983,31 @@ def initial_final_interpolation(self, star_1_CO=False, star_2_CO=False): # update interpolated core collapse quantites if interpolation_class != 'unstable_MT': - for MODEL_NAME in MODELS.keys(): + for SN_MODEL_NAME in SN_MODELS.keys(): for i, star in enumerate(stars): - if (not stars_CO[i] and self.classes[f'S{i+1}_{MODEL_NAME}_CO_type'] != 'None'): + col_name = f'S{i+1}_{SN_MODEL_NAME}_CO_type' + if (not stars_CO[i] and self.classes[col_name] != 'None'): values = {} for key in ['state', 'SN_type', 'f_fb', 'mass', 'spin', 'm_disk_accreted', 'm_disk_radiated', 'M4', 'mu4', 'h1_mass_ej', 'he4_mass_ej']: if key == "state": - state = self.classes[f'S{i+1}_{MODEL_NAME}_CO_type'] + state = self.classes[col_name] values[key] = state elif key == "SN_type": - values[key] = self.classes[f'S{i+1}_{MODEL_NAME}_{key}'] - elif f'S{i+1}_{MODEL_NAME}_{key}' in fv: - values[key] = fv[f'S{i+1}_{MODEL_NAME}_{key}'] + col_name = f'S{i+1}_{SN_MODEL_NAME}_{key}' + values[key] = self.classes[col_name] + elif f'S{i+1}_{SN_MODEL_NAME}_{key}' in fv: + col_name = f'S{i+1}_{SN_MODEL_NAME}_{key}' + values[key] = fv[col_name] else: - Pwarn(f"S{i+1}_{MODEL_NAME}_{key} not found in fv", - "UnsupportedModelWarning") + Pwarn(f"S{i+1}_{SN_MODEL_NAME}_{key} not " + "found in fv", "UnsupportedModelWarning") values = None break - setattr(star, MODEL_NAME, values) + setattr(star, SN_MODEL_NAME, values) else: - setattr(star, MODEL_NAME, None) + setattr(star, SN_MODEL_NAME, None) # STOPPING METHODS diff --git a/posydon/binary_evol/SN/profile_collapse.py b/posydon/binary_evol/SN/profile_collapse.py index c2e900773d..fd173d0cca 100644 --- a/posydon/binary_evol/SN/profile_collapse.py +++ b/posydon/binary_evol/SN/profile_collapse.py @@ -161,20 +161,6 @@ def get_initial_BH_properties(star, mass_collapsing, mass_central_BH, Maxium Helium in SN ejecta. """ - if not (hasattr(star, 'profile') and isinstance(star.profile, np.ndarray) - and ('mass' in star.profile.dtype.names) - and ('radius' in star.profile.dtype.names) - and ('logRho' in star.profile.dtype.names) - and ('omega' in star.profile.dtype.names) -# and ('he3' in star.profile.dtype.names) - and ('he4' in star.profile.dtype.names)): - Pwarn("The stellar profile does not contain enough information to " - "collapse the star and determine the remnant properties.", - 'InappropriateValueWarning') - return [np.nan, np.nan, np.nan, np.array([]), np.array([]), - np.array([]), np.array([]), np.array([]), np.array([]), - np.array([]), np.array([]), np.nan] - if neutrino_mass_loss < 0.: raise ValueError( 'Something went wrong, neutrino_mass_loss must be positive!') @@ -187,6 +173,20 @@ def get_initial_BH_properties(star, mass_collapsing, mass_central_BH, 'while neutrino_mass_loss = {:2.2f} Msun'.format( neutrino_mass_loss), 'was passed!') + if not (hasattr(star, 'profile') and isinstance(star.profile, np.ndarray) + and ('mass' in star.profile.dtype.names) + and ('radius' in star.profile.dtype.names) + and ('logRho' in star.profile.dtype.names) + and ('omega' in star.profile.dtype.names)): + Pwarn("The stellar profile does not contain enough information to " + "collapse the star and determine the remnant properties.", + 'InappropriateValueWarning') + # estimate still initial BH mass + mass_initial_BH = mass_central_BH - neutrino_mass_loss + return [mass_initial_BH, np.nan, np.nan, np.array([]), np.array([]), + np.array([]), np.array([]), np.array([]), np.array([]), + np.array([]), np.array([]), np.nan] + # load units and convert to CGS units G = const.standard_cgrav c = const.clight @@ -196,16 +196,17 @@ def get_initial_BH_properties(star, mass_collapsing, mass_central_BH, neutrino_mass_loss *= Mo # read star quantities - enclosed_mass_all = star.profile['mass'][::-1]*Mo # cell outer total mass - radius_all = star.profile['radius'][::-1] * Ro # cell outer radius - density_all = 10**(star.profile['logRho'][::-1]) # cell density - angular_frequency_all = star.profile['omega'][::-1] # cell angular freq. + enclosed_mass_all = star.profile['mass'][::-1] * Mo # cell outer total mass + radius_all = star.profile['radius'][::-1] * Ro # cell outer radius + density_all = 10**(star.profile['logRho'][::-1]) # cell density + angular_frequency_all = star.profile['omega'][::-1] # cell angular freq. if 'he4' in star.profile.dtype.names: - # he3_all = star.profile['he3'][::-1] # he4 mass fraction - he4_all = star.profile['he4'][::-1] # he4 mass fraction - he3_all = np.zeros(len(enclosed_mass_all)) # ENHANCEMENT support he3 + he4_all = star.profile['he4'][::-1] # he4 mass fraction else: he4_all = np.zeros(len(enclosed_mass_all)) + if 'he3' in star.profile.dtype.names: + he3_all = star.profile['he3'][::-1] # he3 mass fraction + else: he3_all = np.zeros(len(enclosed_mass_all)) # cut the ejected layers of the profile due to the SN event: diff --git a/posydon/binary_evol/SN/step_SN.py b/posydon/binary_evol/SN/step_SN.py index bc29ad36a8..81ee92e957 100644 --- a/posydon/binary_evol/SN/step_SN.py +++ b/posydon/binary_evol/SN/step_SN.py @@ -53,7 +53,7 @@ from posydon.binary_evol.flow_chart import (STAR_STATES_CO, STAR_STATES_CC, STAR_STATES_C_DEPLETION) -from posydon.grids.MODELS import get_MODEL_NAME +from posydon.grids.SN_MODELS import get_SN_MODEL_NAME, DEFAULT_SN_MODEL from posydon.utils.posydonerror import ModelError from posydon.utils.posydonwarning import Pwarn from posydon.utils.common_functions import set_binary_to_failed @@ -73,23 +73,7 @@ path_to_Couch_datasets = os.path.join(PATH_TO_POSYDON_DATA, "Couch+2020/") -MODEL = { - # core collapse physics - "mechanism": 'Patton&Sukhbold20-engine', - "engine": 'N20', - "PISN": "Marchant+19", - "PISN_CO_shift": 0.0, - "PPI_extra_mass_loss": 0.0, - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : False, - "conserve_hydrogen_PPI" : False, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": True, - "use_profiles": True, - "use_core_masses": True, - "allow_spin_None" : False, - "approx_at_he_depletion": False, +SN_MODEL = { # kick physics "kick": True, "kick_normalisation": 'one_over_mass', @@ -99,6 +83,8 @@ # other "verbose": False, } +# add core collapse physics +SN_MODEL.update(DEFAULT_SN_MODEL) class StepSN(object): @@ -258,14 +244,14 @@ def __init__(self, **kwargs): # read kwargs to initialize the class if kwargs: for key in kwargs: - if key not in MODEL: + if key not in SN_MODEL: raise ValueError(key + " is not a valid parameter name!") - for varname in MODEL: - default_value = MODEL[varname] + for varname in SN_MODEL: + default_value = SN_MODEL[varname] setattr(self, varname, kwargs.get(varname, default_value)) else: - for varname in MODEL: - default_value = MODEL[varname] + for varname in SN_MODEL: + default_value = SN_MODEL[varname] setattr(self, varname, default_value) if self.max_neutrino_mass_loss is None: @@ -518,25 +504,26 @@ def collapse_star(self, star): # if no profile is avaiable but interpolation quantities are, # use those, else continue with or without profile. if self.use_interp_values: - # find MODEL_NAME corresponding to class variable - MODEL_NAME_SEL = get_MODEL_NAME(vars(self), verbose=self.verbose) + # find SN_MODEL_NAME corresponding to class variable + SN_MODEL_NAME_SEL = get_SN_MODEL_NAME(vars(self), + verbose=self.verbose) - # check if selected MODEL is supported - if MODEL_NAME_SEL is None: + # check if selected model is supported + if SN_MODEL_NAME_SEL is None: raise ValueError('Your model assumptions are not' 'supported!') - elif getattr(star, MODEL_NAME_SEL) is None: + elif getattr(star, SN_MODEL_NAME_SEL) is None: # NOTE: this option is needed to do the collapse # for stars evolved with the step_detached or # step_disrupted. # allow to continue with the collapse with profile # or core masses - Pwarn(f'{MODEL_NAME_SEL}: The collapsed star was not ' + Pwarn(f'{SN_MODEL_NAME_SEL}: The collapsed star was not ' 'interpolated! If use_profiles or use_core_masses ' 'is set to True, continue with the collapse.', "InterpolationWarning") else: - MODEL_properties = getattr(star, MODEL_NAME_SEL) + SN_MODEL_properties = getattr(star, SN_MODEL_NAME_SEL) SN_type = self.check_SN_type(m_core=star.co_core_mass, m_He_core=star.he_core_mass, @@ -550,9 +537,9 @@ def collapse_star(self, star): else: alternative = "" - if MODEL_properties['SN_type'] == "ECSN": + if SN_MODEL_properties['SN_type'] == "ECSN": # overwrite ECSN in SN MODEL - MODEL_properties['SN_type'] = SN_type + SN_MODEL_properties['SN_type'] = SN_type Pwarn(f"ECSN in SN_MODEL replaced by {SN_type}", "ReplaceValueWarning") @@ -560,24 +547,24 @@ def collapse_star(self, star): # do not use interpolated values for ECSN range instead # behave like use_core_masses=True pass - ## star's SN_type mismatches one from the MODEL - elif SN_type != MODEL_properties['SN_type']: + ## star's SN_type mismatches one from the model + elif SN_type != SN_MODEL_properties['SN_type']: Pwarn(f"The SN_type does not match the star: {SN_type}" - f"!={MODEL_properties['SN_type']}."+alternative, - "ApproximationWarning") - ## Check if SN_type mismatches the CO_type in MODEL - elif not check_SN_CO_match(MODEL_properties['SN_type'], - MODEL_properties['state']): - Pwarn(f"{MODEL_NAME_SEL}: The SN_type does not match " - "the predicted CO."+alternative, + f"!={SN_MODEL_properties['SN_type']}." + +alternative, "ApproximationWarning") + ## Check if SN_type mismatches the CO_type in the model + elif not check_SN_CO_match(SN_MODEL_properties['SN_type'], + SN_MODEL_properties['state']): + Pwarn(f"{SN_MODEL_NAME_SEL}: The SN_type does not " + "match the predicted CO."+alternative, "ApproximationWarning") ## Check if there is no interpolated remnant mass - elif pd.isna(MODEL_properties['mass']): + elif pd.isna(SN_MODEL_properties['mass']): Pwarn(f"There is no interpolated remnant mass." +alternative, "ApproximationWarning") ## Otherwise interpolated values can be used for this SN else: - for key, value in MODEL_properties.items(): + for key, value in SN_MODEL_properties.items(): setattr(star, key, value) if star.state == 'WD': diff --git a/posydon/binary_evol/binarystar.py b/posydon/binary_evol/binarystar.py index 631231852e..e67e020574 100644 --- a/posydon/binary_evol/binarystar.py +++ b/posydon/binary_evol/binarystar.py @@ -23,6 +23,7 @@ "Philipp Moura Srivastava ", "Devina Misra ", "Scott Coughlin ", + "Seth Gossage " ] @@ -35,7 +36,8 @@ from posydon.utils.common_functions import ( check_state_of_star, orbital_period_from_separation, orbital_separation_from_period, get_binary_state_and_event_and_mt_case) -from posydon.popsyn.io import (clean_binary_history_df, clean_binary_oneline_df) +from posydon.popsyn.io import (clean_binary_history_df, clean_binary_oneline_df, + BINARYPROPERTIES_DTYPES, OBJECT_FIXED_SUB_DTYPES) from posydon.binary_evol.flow_chart import UNDEFINED_STATES from posydon.utils.posydonerror import FlowError @@ -112,7 +114,7 @@ def signal_handler(signum, frame): """React to a maximum time signal.""" - raise RuntimeError("Binary Step Exceeded Alloted Time: {}". + raise RuntimeError("Binary Step Exceeded Allotted Time: {}". format(MAXIMUM_STEP_TIME)) @@ -147,13 +149,15 @@ def __init__(self, star_1=None, star_2=None, index=None, properties=None, # Set the initial binary properties for item in BINARYPROPERTIES: if item == 'V_sys': - setattr(self, item, binary_kwargs.pop(item, [0,0,0])) + setattr(self, item, binary_kwargs.pop(item, np.array([0.0, + 0.0, + 0.0]))) elif item == 'mass_transfer_case': setattr(self, item, binary_kwargs.pop(item, 'None')) elif item == 'nearest_neighbour_distance': - setattr(self, item, binary_kwargs.pop(item, ['None', - 'None', - 'None'])) + setattr(self, item, binary_kwargs.pop(item, [0.0, + 0.0, + 0.0])) else: setattr(self, item, binary_kwargs.pop(item, None)) setattr(self, item + '_history', [getattr(self, item)]) @@ -363,6 +367,9 @@ def to_df(self, **kwargs): extra_binary_cols_dict = kwargs.get('extra_columns', {}) extra_columns = list(extra_binary_cols_dict.keys()) + # dictionary mapping binary properties (plus extras) to their dtypes + properties_dtypes = {**BINARYPROPERTIES_DTYPES, **extra_binary_cols_dict} + all_keys = (["binary_index"] + [key+'_history' for key in BINARYPROPERTIES] + extra_columns) @@ -389,9 +396,41 @@ def to_df(self, **kwargs): # If a binary fails, usually history cols have diff lengths. # This should append NAN to create even columns. all_equal_length_cols = len(set(col_lengths)) == 1 + if not all_equal_length_cols: - for col in data_to_save: - col.extend([np.nan] * abs(max_col_length - len(col))) + for i, col in enumerate(data_to_save): + + dkey = all_keys[i] + dtype = properties_dtypes.get(dkey, '') + + # check type of data and determine what to fill data column with + if dtype == 'string': + filler_value = '' + elif dtype == 'float64': + filler_value = np.nan + # array-like data + elif dtype == 'object': + # get the max length that data elements have + ele_lengths = [len(x) for x in col] + max_ele_length = np.max(ele_lengths) + sub_dtype = OBJECT_FIXED_SUB_DTYPES.get(dkey, '') + + # array of strings + if sub_dtype == 'string': + filler_value = np.array([''] * max_ele_length) + # array of float64's + elif sub_dtype == 'float64': + filler_value = np.array([np.nan] * max_ele_length) + # default to array of NoneTypes + else: + filler_value = np.array([None] * max_ele_length) + + # defaulting to NoneType value (gets converted to np.nan below) + else: + filler_value = None + + # extend data column with determined filler values + col.extend([filler_value] * abs(max_col_length - len(col))) where_none = np.array([[True if var is None else False for var in column] diff --git a/posydon/binary_evol/singlestar.py b/posydon/binary_evol/singlestar.py index 17207c6a60..e0ce2270cd 100644 --- a/posydon/binary_evol/singlestar.py +++ b/posydon/binary_evol/singlestar.py @@ -21,7 +21,7 @@ import numpy as np import pandas as pd from posydon.utils.common_functions import check_state_of_star -from posydon.grids.MODELS import MODELS +from posydon.grids.SN_MODELS import SN_MODELS @@ -180,9 +180,9 @@ def __init__(self, **kwargs): setattr(self, quantity, None) # core collapse quantities - for MODEL_NAME in MODELS.keys(): - if not hasattr(self, MODEL_NAME): - setattr(self, MODEL_NAME, None) + for SN_MODEL_NAME in SN_MODELS.keys(): + if not hasattr(self, SN_MODEL_NAME): + setattr(self, SN_MODEL_NAME, None) def append_state(self): """Append the new version of the star to the end of the star state.""" diff --git a/posydon/grids/MODELS.py b/posydon/grids/MODELS.py deleted file mode 100644 index 8efd62efe0..0000000000 --- a/posydon/grids/MODELS.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -This file contains all core-collapse models -we use in our post processing and, as a -consequence, what the user will be able to -select in the initial/final interpolation. -Each model is a dictionary containing the -properties of the model used by step_SN. -""" - -__authors__ = [ - "Simone Bavera ", - "Max Briel ", -] - -from posydon.utils.limits_thresholds import (STATE_NS_STARMASS_UPPER_LIMIT, - NEUTRINO_MASS_LOSS_UPPER_LIMIT) - -MODELS = { - "MODEL01": { - "mechanism": "direct", - "engine": "", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : False, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL02": { - "mechanism": "Fryer+12-rapid", - "engine": "", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : False, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL03": { - "mechanism": "Fryer+12-delayed", - "engine": "", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : False, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL04": { - "mechanism": "Sukhbold+16-engine", - "engine": "N20", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : False, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL05": { - "mechanism": "Patton&Sukhbold20-engine", - "engine": "N20", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : False, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL06": { - "mechanism": "direct", - "engine": "", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : True, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL07": { - "mechanism": "Fryer+12-rapid", - "engine": "", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : True, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL08": { - "mechanism": "Fryer+12-delayed", - "engine": "", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : True, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL09": { - "mechanism": "Sukhbold+16-engine", - "engine": "N20", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : True, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - "MODEL10": { - "mechanism": "Patton&Sukhbold20-engine", - "engine": "N20", - "PISN": "Marchant+19", - "ECSN": "Podsiadlowski+04", - "conserve_hydrogen_envelope" : True, - "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - "use_interp_values": False, - "use_profiles": True, - "use_core_masses": False, - "approx_at_he_depletion": False, - }, - # "MODEL11": { - # "mechanism": "Patton&Sukhbold20-engine", - # "engine": "N20", - # "PISN": "Hendriks+23", - # "PISN_CO_shift": 0.0, - # "PPI_extra_mass_loss": 0.0, - # "ECSN": "Podsiadlowski+04", - # "conserve_hydrogen_envelope" : True, - # "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - # "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - # "use_interp_values": False, - # "use_profiles": True, - # "use_core_masses": False, - # "approx_at_he_depletion": False, - # }, - # "MODEL12": { - # "mechanism": "Patton&Sukhbold20-engine", - # "engine": "N20", - # "PISN": "Hendriks+23", - # "PISN_CO_shift": 0.0, - # "PPI_extra_mass_loss": 0.0, - # "ECSN": "Podsiadlowski+04", - # "conserve_hydrogen_envelope" : False, - # "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, - # "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, - # "use_interp_values": False, - # "use_profiles": True, - # "use_core_masses": False, - # "approx_at_he_depletion": False, - # }, -} - -def get_MODEL_NAME(input_MODEL, verbose=False): - '''Find MODEL_NAME that matches input_MODEL. - - Parameters - ---------- - input_MODEL : dict - Dictionary with the properties of the model. - verbose : bool (default: False) - Enables additional output. - - Returns - ------- - MODEL_NAME_SEL : str - Name of the model in MODELS.py that matches input_MODEL. - ''' - MODEL_NAME_SEL = None - for MODEL_NAME, MODEL in MODELS.items(): - tmp = MODEL_NAME - for key, val in MODEL.items(): - if "use_" in key or key=="ECSN": - # escape values, which are allowed to differ - continue - if input_MODEL[key] != val: - if verbose: - print(tmp, 'mismatch:', key, - input_MODEL[key], val) - tmp = None - break - if tmp is not None: - if verbose: - print('matched to model:', tmp) - MODEL_NAME_SEL = tmp - - return MODEL_NAME_SEL \ No newline at end of file diff --git a/posydon/grids/SN_MODELS.py b/posydon/grids/SN_MODELS.py new file mode 100644 index 0000000000..dac4a63b06 --- /dev/null +++ b/posydon/grids/SN_MODELS.py @@ -0,0 +1,514 @@ +""" +This file contains all supernova models we use in our post processing and, as a +consequence, what the user will be able to select in the initial/final +interpolation. There is a default supernova model, which is used to fill not +specified parameters in the set of models. Each model is a dictionary +containing the properties of the model used by step_SN. The functions +`get_SN_MODEL` provides the supernova model with all parameters and +`get_SN_MODEL_NAME` findes the matching model to a given set of supernova +parameters of the step_SN. +""" + +__authors__ = [ + "Simone Bavera ", + "Matthias Kruckow ", + "Max Briel ", +] + +from posydon.utils.limits_thresholds import (STATE_NS_STARMASS_UPPER_LIMIT, + NEUTRINO_MASS_LOSS_UPPER_LIMIT) + +DEFAULT_SN_MODEL = { + "mechanism": "Fryer+12-delayed", + "engine": "", + "PISN": "Hendriks+23", + "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": -20.0, + "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : False, + "conserve_hydrogen_PPI" : False, + "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, + "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": True, + "use_profiles": True, + "use_core_masses": True, + "allow_spin_None" : False, + "approx_at_he_depletion": False, + } + +# pre-defined supernova models values not changed to the default model are put +# as comments +SN_MODELS = { + "SN_MODEL_v2_01": { +# "mechanism": "Fryer+12-delayed", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_02": { +# "mechanism": "Fryer+12-delayed", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_03": { +# "mechanism": "Fryer+12-delayed", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_04": { +# "mechanism": "Fryer+12-delayed", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_05": { + "mechanism": "Fryer+12-rapid", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_06": { + "mechanism": "Fryer+12-rapid", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_07": { + "mechanism": "Fryer+12-rapid", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_08": { + "mechanism": "Fryer+12-rapid", +# "engine": "", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_09": { + "mechanism": "Sukhbold+16-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_10": { + "mechanism": "Sukhbold+16-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_11": { + "mechanism": "Sukhbold+16-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_12": { + "mechanism": "Sukhbold+16-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_13": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_14": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_15": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_16": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "N20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_17": { + "mechanism": "Sukhbold+16-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_18": { + "mechanism": "Sukhbold+16-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_19": { + "mechanism": "Sukhbold+16-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_20": { + "mechanism": "Sukhbold+16-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_21": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_22": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, +# "PPI_extra_mass_loss": -20.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_23": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", +# "conserve_hydrogen_envelope" : False, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, + "SN_MODEL_v2_24": { + "mechanism": "Patton&Sukhbold20-engine", + "engine": "W20", +# "PISN": "Hendriks+23", +# "PISN_CO_shift": 0.0, + "PPI_extra_mass_loss": 0.0, +# "ECSN": "Tauris+15", + "conserve_hydrogen_envelope" : True, +# "conserve_hydrogen_PPI" : False, +# "max_neutrino_mass_loss": NEUTRINO_MASS_LOSS_UPPER_LIMIT, +# "max_NS_mass": STATE_NS_STARMASS_UPPER_LIMIT, + "use_interp_values": False, +# "use_profiles": True, + "use_core_masses": False, +# "allow_spin_None" : False, +# "approx_at_he_depletion": False, + }, +} + +def get_SN_MODEL(name): + """Get predefined supernova model with all properties. + + Parameters + ---------- + name : str + Name of a pre-defined supernova model. + + Returns + ------- + SN_MODEL : dict + Dictionary with the properties of the supernova model. If the given + name does not exist in the pre-defined models, the default supernova + model is returned. + + """ + if name in SN_MODELS: + SN_MODEL = DEFAULT_SN_MODEL.copy() + SN_MODEL.update(SN_MODELS[name]) + return SN_MODEL + else: + return DEFAULT_SN_MODEL.copy() + +def get_SN_MODEL_NAME(input_SN_MODEL, verbose=False): + """Find SN_MODEL_NAME that matches input_SN_MODEL. + + Parameters + ---------- + input_SN_MODEL : dict + Dictionary with the properties of the supernova model. + verbose : bool (default: False) + Enables additional output. + + Returns + ------- + SN_MODEL_NAME_SEL : str + Name of the model in SN_MODELS that matches input_SN_MODEL. + + """ + SN_MODEL_NAME_SEL = None + for SN_MODEL_NAME, SN_MODEL in SN_MODELS.items(): + tmp = SN_MODEL_NAME + for key in DEFAULT_SN_MODEL.keys(): + val = SN_MODEL.get(key, DEFAULT_SN_MODEL[key]) + if "use_" in key or key=="ECSN": + # escape values, which are allowed to differ + continue + if key not in input_SN_MODEL: + if verbose: + print(tmp, 'missing key:', key, input_SN_MODEL) + tmp = None + break + elif input_SN_MODEL[key] != val: + if verbose: + print(tmp, 'mismatch:', key, input_SN_MODEL[key], val) + tmp = None + break + if tmp is not None: + if verbose: + print('matched to supernova model:', tmp) + SN_MODEL_NAME_SEL = tmp + + return SN_MODEL_NAME_SEL diff --git a/posydon/grids/io.py b/posydon/grids/io.py index ad3441dfcc..686d799ef0 100644 --- a/posydon/grids/io.py +++ b/posydon/grids/io.py @@ -478,6 +478,8 @@ def initial_values_from_dirname(mesa_dir): else: # binary-star grid if "v1/" in str(mesa_dir): # version 1 dirnames don't contain initial_z variable_names = ["m1", "m2", "initial_period_in_days"] + elif "initial_eccentricity" in dirname: + variable_names = ["m1", "m2", "initial_period_in_days", "initial_z", "initial_eccentricity"] else: variable_names = ["m1", "m2", "initial_period_in_days", "initial_z"] for variable_name in variable_names: diff --git a/posydon/grids/post_processing.py b/posydon/grids/post_processing.py index 1af31c8ce1..fc5ce44028 100644 --- a/posydon/grids/post_processing.py +++ b/posydon/grids/post_processing.py @@ -8,7 +8,7 @@ calculate_Patton20_values_at_He_depl, CEE_parameters_from_core_abundance_thresholds, check_state_of_star) -from posydon.grids.MODELS import MODELS +from posydon.grids.SN_MODELS import get_SN_MODEL, SN_MODELS from posydon.visualization.combine_TF import combine_TF12, TF1_POOL_STABLE from posydon.visualization.plot_defaults import DEFAULT_MARKERS_COLORS_LEGENDS import numpy as np @@ -35,7 +35,7 @@ 'h1_mass_ej', 'he4_mass_ej'] def assign_core_collapse_quantities_none(EXTRA_COLUMNS, star_i, - MODEL_NAME=None): + SN_MODEL_NAME=None): """Assign None values to all core collapse properties. Parameters @@ -45,37 +45,36 @@ def assign_core_collapse_quantities_none(EXTRA_COLUMNS, star_i, dictionary will get modified by this function. star_i : int (1 or 2) Index of the star. - MODEL_NAME : str or list of str, or None (default: None) - Model key like the ones given in MODELS.py. + SN_MODEL_NAME : str or list of str, or None (default: None) + SN_MODEL key like the ones given in SN_MODELS.py. """ if (not isinstance(star_i, int) or (star_i<1) or (star_i>2)): raise ValueError("'star_i' should be 1 or 2.") - if MODEL_NAME is None: - for NAME, MODEL in MODELS.items(): + if SN_MODEL_NAME is None: + for NAME in SN_MODELS.keys(): for quantity in CC_quantities: - EXTRA_COLUMNS[f'S{star_i}_{NAME}_{quantity}' - ].append(None) - elif isinstance(MODEL_NAME, str): + EXTRA_COLUMNS[f'S{star_i}_{NAME}_{quantity}'].append(None) + elif isinstance(SN_MODEL_NAME, str): for quantity in CC_quantities: - EXTRA_COLUMNS[f'S{star_i}_{MODEL_NAME}_{quantity}'].append(None) - elif isinstance(MODEL_NAME, list): - for NAME in MODEL_NAME: + EXTRA_COLUMNS[f'S{star_i}_{SN_MODEL_NAME}_{quantity}'].append(None) + elif isinstance(SN_MODEL_NAME, list): + for NAME in SN_MODEL_NAME: for quantity in CC_quantities: EXTRA_COLUMNS[f'S{star_i}_{NAME}_{quantity}'].append(None) else: - raise TypeError("'MODEL_NAME' should be a string, a list of strings, " - "or None.") + raise TypeError("'SN_MODEL_NAME' should be a string, a list of " + "strings, or None.") -def print_CC_quantities(star, MODEL_NAME=None): +def print_CC_quantities(star, SN_MODEL_NAME=None): """Print quantities at core collapse. Parameters ---------- star : SingleStar object The star whose quantities to print. - MODEL_NAME : str or None (default: None) - Name of the model. If None, print pre SN values. + SN_MODEL_NAME : str or None (default: None) + Name of the supernova model. If None, print pre SN values. """ format_string = "{:<50} {:<33} {:12} {:10} {:15} {:10} {:25} {:25} {:25} "\ @@ -84,7 +83,7 @@ def print_CC_quantities(star, MODEL_NAME=None): + "{:25} {:25} {:25} {:25} {:25}" format_val = "{:<50} {:<33} {:12} {:1.2f} {:13.2f} {:12.2f} {:20.2f} "\ + "{:20.2f} {:20.2f} {:20.2f} {:20.2f} {:20.2f}" - if MODEL_NAME is None: + if SN_MODEL_NAME is None: print('') print(format_string.format("mechanism", "state", "SN type", "f_fb", "mass [Msun]", "spin", @@ -110,7 +109,7 @@ def print_CC_quantities(star, MODEL_NAME=None): else: checked_quantities_for_None[quantity] = getattr(star, quantity) - print(format_val.format(MODEL_NAME, star.state, star.SN_type, + print(format_val.format(SN_MODEL_NAME, star.state, star.SN_type, star.f_fb, star.mass, checked_quantities_for_None["spin"], star.m_disk_accreted, star.m_disk_radiated, @@ -121,20 +120,16 @@ def print_CC_quantities(star, MODEL_NAME=None): ) except Exception as e: Pwarn("Failed to print star values!\nWarning in " - "{}: {}".format(MODEL_NAME, e), "InappropriateValueWarning") + "{}: {}".format(SN_MODEL_NAME, e), + "InappropriateValueWarning") -def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, +def post_process_grid(grid, index=None, star_2_CO=True, SN_MODELS=SN_MODELS, single_star=False, verbose=False): """Compute post processed quantity of any grid. This function post processes any supported grid and computes: - Core collapse quantities for 5 prescriptions given the fiducial POSYDON - assumption given in MODEL plus: - A: direct collapse - B: Fryer+12-rapid - C: Fryer+12-delayed - D: Sukhbold+16-engine, N20 - E: Patton&Sukhbold20-engine, N20 + assumption given in SN_MODEL: for each prescrition we store the compact object state (WD/NS/BH), SN type (WD, ECSN, CCSN, PISN, PPISN), fallback mass fraction f_gb, compact object mass and spin. @@ -150,7 +145,7 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, or a single index, e.g. 42. star_2_CO : bool (default: True) If 'False' star 2 is not a compact object. - MODELS : list of dict (default are the models defined in MODELS.py) + SN_MODELS : list of dict (default are the models defined in SN_MODELS.py) List of supported core collapse model assumptions. single_star : bool (default: False) If `True` the PSyGrid contains single stars. @@ -184,9 +179,9 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, for val in [1, 10, 30, 'pure_He_star_10']: EXTRA_COLUMNS[f'S{star}_{quantity}_{val}cent'] = [] # Core collapse qunatities: [state, SN_type, f_fb, mass, spin] - for MODEL_NAME, MODEL in MODELS.items(): + for SN_MODEL_NAME in SN_MODELS.keys(): for quantity in CC_quantities: - EXTRA_COLUMNS[f'S{star}_{MODEL_NAME}_{quantity}'] = [] + EXTRA_COLUMNS[f'S{star}_{SN_MODEL_NAME}_{quantity}'] = [] # loop over all gird, index or range if index is None: @@ -294,31 +289,31 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, star = binary.star_1 star_i = 1 assign_core_collapse_quantities_none(EXTRA_COLUMNS, 2, - list(MODELS.keys())) + list(SN_MODELS.keys())) elif (TF1 in TF1_POOL_STABLE and ('secondary' in TF1 or 'Secondary' in TF1)): star = binary.star_2 star_i = 2 assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - list(MODELS.keys())) + list(SN_MODELS.keys())) elif TF1 == 'gamma_center_limit': if (binary.star_1.center_gamma is not None and binary.star_1.center_gamma >= 10.): star = binary.star_1 star_i = 1 assign_core_collapse_quantities_none(EXTRA_COLUMNS, 2, - list(MODELS.keys())) + list(SN_MODELS.keys())) elif (binary.star_2.center_gamma is not None and binary.star_2.center_gamma >= 10.): star = binary.star_2 star_i = 2 assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - list(MODELS.keys())) + list(SN_MODELS.keys())) else: assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - list(MODELS.keys())) + list(SN_MODELS.keys())) assign_core_collapse_quantities_none(EXTRA_COLUMNS, 2, - list(MODELS.keys())) + list(SN_MODELS.keys())) Pwarn(f'{grid.MESA_dirs[i]} ended with ' 'TF1=gamma_center_limit however the star has ' 'center_gamma < 10. This star cannot go through ' @@ -327,9 +322,9 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, star_i = 0 else: assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - list(MODELS.keys())) + list(SN_MODELS.keys())) assign_core_collapse_quantities_none(EXTRA_COLUMNS, 2, - list(MODELS.keys())) + list(SN_MODELS.keys())) Pwarn(f'{grid.MESA_dirs[i]} ended with TF={TF1} and ' f'IC={interpolation_class}. This star cannot go ' 'through step_SN appending NONE compact object ' @@ -342,9 +337,11 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, if verbose: print_CC_quantities(star) - for MODEL_NAME, MODEL in MODELS.items(): - mechanism = MODEL['mechanism']+MODEL['engine'] - SN = StepSN(**MODEL, allow_spin_None=True) + for SN_MODEL_NAME in SN_MODELS.keys(): + SN_MODEL = get_SN_MODEL(SN_MODEL_NAME) + mechanism = SN_MODEL['mechanism']+SN_MODEL['engine'] + SN_MODEL['allow_spin_None'] = True + SN = StepSN(**SN_MODEL) star_copy = copy.copy(star) try: flush = False @@ -353,21 +350,21 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, if quantity in ['state', 'SN_type']: if not isinstance(getattr(star_copy, quantity), str): flush = True - Pwarn(f'{MODEL_NAME} {mechanism} {quantity} is not a string!', "InappropriateValueWarning") + Pwarn(f'{SN_MODEL_NAME} {mechanism} {quantity} is not a string!', "InappropriateValueWarning") elif quantity != 'CO_interpolation_class': if quantity in ['spin', 'M4', 'mu4', "h1_mass_ej", "he4_mass_ej"]: if ((not isinstance(getattr(star_copy, quantity), float)) and (getattr(star_copy, quantity) != None)): flush = True - Pwarn(f'{MODEL_NAME} {mechanism} {quantity} is not a float nor None!', "InappropriateValueWarning") + Pwarn(f'{SN_MODEL_NAME} {mechanism} {quantity} is not a float nor None!', "InappropriateValueWarning") elif not isinstance(getattr(star_copy, quantity), float): flush = True - Pwarn(f'{MODEL_NAME} {mechanism} {quantity} is not a float!', "InappropriateValueWarning") + Pwarn(f'{SN_MODEL_NAME} {mechanism} {quantity} is not a float!', "InappropriateValueWarning") except Exception as e: flush = True if verbose: print('') - print(f'Error during {MODEL_NAME} {mechanism} core collapse prescrition!') + print(f'Error during {SN_MODEL_NAME} {mechanism} core collapse prescrition!') print(e) print('TF1:', TF1) print('interpolation class:', interpolation_class) @@ -376,41 +373,43 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, if flush: assign_core_collapse_quantities_none(EXTRA_COLUMNS, star_i, - MODEL_NAME) + SN_MODEL_NAME) else: for quantity in CC_quantities: if quantity != 'CO_interpolation_class': - EXTRA_COLUMNS[f'S{star_i}_{MODEL_NAME}_{quantity}'].append( + EXTRA_COLUMNS[f'S{star_i}_{SN_MODEL_NAME}_{quantity}'].append( getattr(star_copy, quantity)) else: if getattr(star_copy, 'state') == 'BH' and 'case' in TF2 and '1' in TF2 and '2' in TF2: - EXTRA_COLUMNS[f'S{star_i}_{MODEL_NAME}_{quantity}'].append( + EXTRA_COLUMNS[f'S{star_i}_{SN_MODEL_NAME}_{quantity}'].append( getattr(star_copy, 'state')+'_reverse_MT') else: - EXTRA_COLUMNS[f'S{star_i}_{MODEL_NAME}_{quantity}'].append( + EXTRA_COLUMNS[f'S{star_i}_{SN_MODEL_NAME}_{quantity}'].append( getattr(star_copy, 'state')) if verbose: - print_CC_quantities(star_copy, f'{MODEL_NAME}_{mechanism}') + print_CC_quantities(star_copy, f'{SN_MODEL_NAME}_{mechanism}') else: # star not explodable assign_core_collapse_quantities_none(EXTRA_COLUMNS, star_i, - list(MODELS.keys())) + list(SN_MODELS.keys())) else: # inital_RLOF, unstable_MT not_converged assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - list(MODELS.keys())) + list(SN_MODELS.keys())) assign_core_collapse_quantities_none(EXTRA_COLUMNS, 2, - list(MODELS.keys())) + list(SN_MODELS.keys())) else: if star.state in STAR_STATES_CC: if verbose: print_CC_quantities(star) - for MODEL_NAME, MODEL in MODELS.items(): - mechanism = MODEL['mechanism']+MODEL['engine'] - SN = StepSN(**MODEL, allow_spin_None=True) + for SN_MODEL_NAME in SN_MODELS.keys(): + SN_MODEL = get_SN_MODEL(SN_MODEL_NAME) + mechanism = SN_MODEL['mechanism']+SN_MODEL['engine'] + SN_MODEL['allow_spin_None'] = True + SN = StepSN(**SN_MODEL) star_copy = copy.copy(star) try: flush = False @@ -419,41 +418,41 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, if quantity in ['state', 'SN_type']: if not isinstance(getattr(star_copy, quantity), str): flush = True - Pwarn(f'{MODEL_NAME} {mechanism} {quantity} is not a string!', "InappropriateValueWarning") + Pwarn(f'{SN_MODEL_NAME} {mechanism} {quantity} is not a string!', "InappropriateValueWarning") elif quantity != 'CO_interpolation_class': if quantity in ['spin', 'M4', 'mu4', "h1_mass_ej", "he4_mass_ej"]: if ((not isinstance(getattr(star_copy, quantity), float)) and (getattr(star_copy, quantity) != None)): flush = True - Pwarn(f'{MODEL_NAME} {mechanism} {quantity} is not a float nor None!', "InappropriateValueWarning") + Pwarn(f'{SN_MODEL_NAME} {mechanism} {quantity} is not a float nor None!', "InappropriateValueWarning") elif not isinstance(getattr(star_copy, quantity), float): flush = True - Pwarn(f'{MODEL_NAME} {mechanism} {quantity} is not a float!', "InappropriateValueWarning") + Pwarn(f'{SN_MODEL_NAME} {mechanism} {quantity} is not a float!', "InappropriateValueWarning") except Exception as e: flush = True if verbose: print('') - print(f'Error during {MODEL_NAME} {mechanism} core collapse prescrition!') + print(f'Error during {SN_MODEL_NAME} {mechanism} core collapse prescrition!') print(e) print('TF1:', TF1) print('run directory:', grid.MESA_dirs[i]) print('') if flush: assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - MODEL_NAME) + SN_MODEL_NAME) else: for quantity in CC_quantities: if quantity != 'CO_interpolation_class': - EXTRA_COLUMNS[f'S1_{MODEL_NAME}_{quantity}'].append( + EXTRA_COLUMNS[f'S1_{SN_MODEL_NAME}_{quantity}'].append( getattr(star_copy, quantity)) else: - EXTRA_COLUMNS[f'S1_{MODEL_NAME}_{quantity}'].append( + EXTRA_COLUMNS[f'S1_{SN_MODEL_NAME}_{quantity}'].append( getattr(star_copy, 'state')) if verbose: - print_CC_quantities(star_copy, f'{MODEL_NAME}_{mechanism}') + print_CC_quantities(star_copy, f'{SN_MODEL_NAME}_{mechanism}') else: assign_core_collapse_quantities_none(EXTRA_COLUMNS, 1, - list(MODELS.keys())) + list(SN_MODELS.keys())) # add MT history column by combining TF1 and TF2 if not single_star: @@ -469,14 +468,14 @@ def post_process_grid(grid, index=None, star_2_CO=True, MODELS=MODELS, '%s has not the correct dimension! Error occoured after ' 'collapsing binary index=%s' % (key, i)) - # to avoid confusion rename core-collaspe compact object state "MODEL_NAME_state" - # to "MODEL_NAME_CO_type" - for MODEL_NAME in MODELS.keys(): - EXTRA_COLUMNS[f'S1_{MODEL_NAME}_CO_type'] = EXTRA_COLUMNS.pop( - f'S1_{MODEL_NAME}_state') - if f'S2_{MODEL_NAME}_state' in EXTRA_COLUMNS: - EXTRA_COLUMNS[f'S2_{MODEL_NAME}_CO_type'] = EXTRA_COLUMNS.pop( - f'S2_{MODEL_NAME}_state') + # to avoid confusion rename core-collaspe compact object state + # "SN_MODEL_NAME_state" to "SN_MODEL_NAME_CO_type" + for SN_MODEL_NAME in SN_MODELS.keys(): + EXTRA_COLUMNS[f'S1_{SN_MODEL_NAME}_CO_type'] = EXTRA_COLUMNS.pop( + f'S1_{SN_MODEL_NAME}_state') + if f'S2_{SN_MODEL_NAME}_state' in EXTRA_COLUMNS: + EXTRA_COLUMNS[f'S2_{SN_MODEL_NAME}_CO_type'] = EXTRA_COLUMNS.pop( + f'S2_{SN_MODEL_NAME}_state') return MESA_dirs, EXTRA_COLUMNS diff --git a/posydon/interpolation/interpolation.py b/posydon/interpolation/interpolation.py index 2bb91c945a..8d90d332ae 100644 --- a/posydon/interpolation/interpolation.py +++ b/posydon/interpolation/interpolation.py @@ -12,7 +12,7 @@ from sklearn.neighbors import NearestNeighbors from .data_scaling import DataScaler from posydon.grids.psygrid import PSyGrid -from posydon.grids.MODELS import MODELS +from posydon.grids.SN_MODELS import SN_MODELS from posydon.utils.interpolators import interp1d @@ -473,11 +473,11 @@ def __init__(self, path, verbose=False): # core collapse keys keys = [] - for MODEL_NAME in MODELS.keys(): + for SN_MODEL_NAME in SN_MODELS.keys(): for key in ['CO_type', 'SN_type', 'f_fb', 'mass', 'spin', 'm_disk_accreted', 'm_disk_radiated','M4', 'mu4', 'h1_mass_ej', 'he4_mass_ej']: - keys.append('S1_' + MODEL_NAME + '_' + key ) + keys.append('S1_' + SN_MODEL_NAME + '_' + key ) self.final_keys += tuple(keys) self.profile_keys = ( diff --git a/posydon/popsyn/binarypopulation.py b/posydon/popsyn/binarypopulation.py index a40feb02b5..d293a44922 100644 --- a/posydon/popsyn/binarypopulation.py +++ b/posydon/popsyn/binarypopulation.py @@ -27,7 +27,7 @@ __credits__ = ["Nam Tran "] - +import signal import pandas as pd import numpy as np import traceback @@ -341,7 +341,11 @@ def _safe_evolve(self, **kwargs): e.add_note(binary.initial_condition_message()) traceback.print_exception(e) - + + # If a binary has failed, the signal alarm for a step duration + # will not be disabled, so we need to disable it here. + signal.alarm(0) + # record if there were warnings caught during the binary # evolution, this is needed to update the WARNINGS column in # the oneline dataframe; this will only be updated if POSYDON diff --git a/posydon/popsyn/io.py b/posydon/popsyn/io.py index 5610079d2d..f7d851ebb3 100644 --- a/posydon/popsyn/io.py +++ b/posydon/popsyn/io.py @@ -31,7 +31,7 @@ 'separation': 'float64', # binary orbital separation (solar radii) 'orbital_period': 'float64', # binary orbital period (days) 'eccentricity': 'float64', # binary eccentricity - 'V_sys': 'object', # list of the 3 systemic velocity coordinates + 'V_sys': 'object', # array of the 3 systemic velocity coordinates # (R_{star} - R_{Roche_lobe}) / R_{Roche_lobe}... 'rl_relative_overflow_1': 'float64', # ...for star 1 'rl_relative_overflow_2': 'float64', # ...for star 2 @@ -46,14 +46,19 @@ 't_sync_rad_2': 'float64', 't_sync_conv_2': 'float64', 'nearest_neighbour_distance': 'object', # the distance of system from its nearest - # neighbour of MESA binary system in case - # of interpolation during the the end of - # the previous step including MESA psygrid. - # The distance is normalized in the - # parameter space and limits at which it - # was calculated. See `mesa_step` for more. + # neighbour of MESA binary system in case + # of interpolation during the the end of + # the previous step including MESA psygrid. + # The distance is normalized in the + # parameter space and limits at which it + # was calculated. See `mesa_step` for more. } +# specifies the dtypes of elements within an object (e.g., of elements in an array) +OBJECT_FIXED_SUB_DTYPES = { + 'V_sys': 'float64', + 'nearest_neighbour_distance': 'float64' +} STARPROPERTIES_DTYPES = { 'state': 'string', # the evolutionary state of the star. For more info see diff --git a/posydon/popsyn/population_params_default.ini b/posydon/popsyn/population_params_default.ini index c314bcfed8..14185b9749 100644 --- a/posydon/popsyn/population_params_default.ini +++ b/posydon/popsyn/population_params_default.ini @@ -188,15 +188,15 @@ # 'optimistic', 'pessimistic' common_envelope_alpha_thermal=1.0 # float in (0, inf) used only for option for (4), (5) - core_definition_H_fraction=0.1 + core_definition_H_fraction=0.3 # 0.01, 0.1, 0.3 core_definition_He_fraction=0.1 # 0.1 CEE_tolerance_err = 0.001 # float (0, inf) - common_envelope_option_after_succ_CEE = 'core_not_replaced_noMT' - # 'core_not_replaced_noMT' 'core_replaced_noMT' - # 'core_not_replaced_stableMT' 'core_not_replaced_windloss' + common_envelope_option_after_succ_CEE = 'two_phases_stableMT' + # 'two_phases_stableMT' 'one_phase_variable_core_definition' + # 'two_phases_windloss' verbose = False # True False @@ -205,44 +205,52 @@ # builtin posydon step absolute_import = None # 'package' kwarg for importlib.import_module - mechanism = 'Patton&Sukhbold20-engine' - # 'direct', Fryer+12-rapid', 'Fryer+12-delayed', - # 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' - engine = 'N20' - # 'N20' for 'Sukhbold+16-engine', - # 'Patton&Sukhbold20-engine' or '' for the others - PISN = "Marchant+19" - # None, "Marchant+19", "Hendriks+23" + mechanism = 'Fryer+12-delayed' + # v2 interpolators support: 'Fryer+12-rapid', 'Fryer+12-delayed', + # 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' + # need profiles: 'direct' + engine = '' + # 'N20' or 'W20' for 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' + # '' for the others + PISN = "Hendriks+23" + # v2 interpolators support: "Hendriks+23" + # other options: None, "Marchant+19" PISN_CO_shift = 0.0 - # float (-inf,inf) # Only when using Hendriks+23 - PPI_extra_mass_loss = 0.0 - # float (0,inf) + # float (-inf,inf) + # v2 interpolators support: 0.0 + PPI_extra_mass_loss = -20.0 # Only when using Hendriks+23 - ECSN = "Podsiadlowski+04" + # float (-inf,inf) + # v2 interpolators support: 0.0 or -20.0 + ECSN = "Tauris+15" # "Tauris+15", "Podsiadlowski+04" conserve_hydrogen_envelope = False # True, False conserve_hydrogen_PPI = False - # True, False # Only when using Hendriks+23 + # True, False max_neutrino_mass_loss = 0.5 # float (0,inf) + # v2 interpolators support: 0.5 max_NS_mass = 2.5 # float (0,inf) + # v2 interpolators support: 2.5 use_interp_values = True # True, False use_profiles = True # True, False use_core_masses = True # True, False + allow_spin_None = False + # True, False approx_at_he_depletion = False # True, False kick = True # True, False kick_normalisation = 'one_over_mass' - # "one_minus_fallback", "one_over_mass", - # "NS_one_minus_fallback_BH_one", "one", "zero" + # "one_minus_fallback", "one_over_mass", "NS_one_minus_fallback_BH_one", + # "one", "zero" sigma_kick_CCSN_NS = 265.0 # float (0,inf) sigma_kick_CCSN_BH = 265.0 diff --git a/posydon/popsyn/rate_calculation.py b/posydon/popsyn/rate_calculation.py index 3657403ffb..70f4deb531 100644 --- a/posydon/popsyn/rate_calculation.py +++ b/posydon/popsyn/rate_calculation.py @@ -14,12 +14,13 @@ from astropy import units as u -DEFAULT_MODEL = { +DEFAULT_SFH_MODEL = { "delta_t": 100, # Myr "SFR": "IllustrisTNG", "sigma_SFR": None, - "Z_max": 1.0, - "select_one_met": False, + "Z_max": None, # Zsun + "Z_min": None, # Zsun + "normalise": True, # normalise the SFR to 1 "dlogZ": None, # e.g, [np.log10(0.0142/2),np.log10(0.0142*2)] "Zsun": Zsun, } @@ -150,7 +151,6 @@ def get_redshift_from_cosmic_time(t_cosm): return trained_tz_interp(t_cosm) - def get_redshift_bin_edges(delta_t): """Compute the redshift bin edges. diff --git a/posydon/popsyn/star_formation_history.py b/posydon/popsyn/star_formation_history.py index c314a66663..196c79a2b5 100644 --- a/posydon/popsyn/star_formation_history.py +++ b/posydon/popsyn/star_formation_history.py @@ -12,6 +12,7 @@ import os import numpy as np import scipy as sp +import pandas as pd from scipy import stats from posydon.config import PATH_TO_POSYDON_DATA from posydon.utils.constants import age_of_universe @@ -22,7 +23,8 @@ ) from posydon.utils.constants import Zsun from posydon.utils.interpolators import interp1d -from astropy.cosmology import Planck15 as cosmology +from abc import ABC, abstractmethod +from posydon.utils.posydonwarning import Pwarn SFH_SCENARIOS = [ @@ -35,7 +37,1010 @@ ] -def get_formation_times(N_binaries, star_formation="constant", **kwargs): +class SFHBase(ABC): + """Abstract class for star formation history models""" + def __init__(self, SFH_MODEL): + """Initialise the SFH model + + Adds the model parameters as attributes. + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + self.Z_max = None + self.Z_min = None + self.normalise = False + + self.SFH_MODEL = SFH_MODEL + # Automatically attach all model parameters as attributes + for key, value in SFH_MODEL.items(): + setattr(self, key, value) + + # check if Z_max is not larger than 1 + if self.Z_max is not None: + if self.Z_max > 1: + raise ValueError("Z_max must be in absolute units! " + "It cannot be larger than 1!") + elif self.Z_max < 0: + raise ValueError("Z_max must be in absolute units! " + "It cannot be negative!") + if self.Z_min is not None: + if self.Z_min < 0: + raise ValueError("Z_min must be in absolute units! " + "It cannot be negative!") + elif self.Z_min > 1: + raise ValueError("Z_min must be in absolute units! " + "It cannot be larger than 1!") + if self.Z_min is not None and self.Z_max is not None: + if self.Z_min >= self.Z_max: + raise ValueError("Z_min must be smaller than Z_max!") + + + @abstractmethod + def CSFRD(self, z): # pragma: no cover + """Compute the cosmic star formation rate density. + + This is an abstract method that must be implemented by subclasses. + The implementation should calculate and return the cosmic star formation + rate density at the given redshift(s). + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + + Returns + ------- + float or array-like + The cosmic star formation rate density at the given redshift(s). + + Raises + ------- + NotImplementedError: If the subclass does not implement this method. + """ + pass + + @abstractmethod + def mean_metallicity(self, z): # pragma: no cover + """Return the mean metallicity at redshift z. + + This is an abstract method that must be implemented by subclasses. + The implementation should calculate and return the mean metallicity + at the given redshift(s). + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + + Returns + ------- + float or array-like + The mean metallicity at the given redshift(s). + + Raises + ------- + NotImplementedError: If the subclass does not implement this method. + """ + pass + + @abstractmethod + def fSFR(self, z, metallicity_bins): # pragma: no cover + """Compute the star formation rate fraction (fSFR) at a given redshift + using the specified metallicity bins. + + This is an abstract method that must be implemented by subclasses. + The implementation should calculate and return the fractional SFR per + metallicity bins at the provided redshift (z). + + Parameters + --------- + z : float or array-like + The redshift(s) at which to compute the star formation rate. + metallicity_bins : list or array-like + The metallicity bin boundaries or labels used in the computation to + account for different metallicity contributions. + + Returns + ------- + float or array-like + The calculated star formation rate at the given redshift(s) and + metallicity bins in Msun/yr. + + Raises: + NotImplementedError: If the subclass does not implement this method. + """ + pass + + def _distribute_cdf(self, cdf_func, metallicity_bins): + '''Distribute the SFR over the metallicity bins using the CDF + + Parameters + ---------- + cdf_func : function + The cumulative density function to use. + metallicity_bins : array + Metallicity bins edges in absolute metallicity. + + Returns + ------- + ndarray + Fraction of the SFR in the given metallicity bin at the given redshift. + ''' + # verify if the metallicity bins are sorted + if not np.all(np.diff(metallicity_bins) > 0): + raise ValueError("Metallicity bins must be sorted " + "in ascending order.") + + fSFR = (np.array(cdf_func(metallicity_bins[1:])) + - np.array(cdf_func(metallicity_bins[:-1]))) + + first_bin_index = 0 + last_bin_index = len(fSFR) - 1 + + # include material outside the metallicity bounds if requested + if self.Z_max is not None: + if self.Z_max >= metallicity_bins[-1]: + fSFR[-1] = cdf_func(self.Z_max) - cdf_func(metallicity_bins[-2]) + else: + Pwarn('Z_max is smaller than the highest metallicity bin.', + 'SFHModelWarning') + # find the index of the last bin that is smaller than Z_max + last_bin_index = np.searchsorted(metallicity_bins, self.Z_max) - 1 + fSFR[last_bin_index] = cdf_func(self.Z_max) - cdf_func(metallicity_bins[last_bin_index]) + fSFR[last_bin_index + 1:] = 0.0 + + if self.Z_min is not None: + if self.Z_min <= metallicity_bins[0]: + fSFR[0] = cdf_func(metallicity_bins[1]) - cdf_func(self.Z_min) + else: + Pwarn('Z_min is larger than the lowest metallicity bin.', + 'SFHModelWarning') + # find the index of the first bin that is larger than Z_min + first_bin_index = np.searchsorted(metallicity_bins, self.Z_min) -1 + fSFR[:first_bin_index] = 0.0 + fSFR[first_bin_index] = cdf_func(metallicity_bins[first_bin_index+1]) - cdf_func(self.Z_min) + + # Check if in the same bin + if self.Z_max is not None and self.Z_min is not None: + if first_bin_index == last_bin_index: + fSFR[first_bin_index] = cdf_func(self.Z_max) - cdf_func(self.Z_min) + fSFR[first_bin_index + 1:] = 0.0 + fSFR[:first_bin_index] = 0.0 + + if self.normalise: + fSFR /= np.sum(fSFR) + + return fSFR + + + def __call__(self, z, met_bins): + """Return the star formation history at a given redshift and metallicity + bins + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + met_bins : array + Metallicity bins edges in absolute metallicity. + + Returns + ------- + ndarray + Star formation history per metallicity bin at the given redshift(s). + """ + return self.CSFRD(z)[:, np.newaxis] * self.fSFR(z, met_bins) + +class MadauBase(SFHBase): + """ + Base class for Madau-style star-formation history implementations. + This class implements common methods for CSFRD, mean metallicity, + and fractional SFR based on the chosen Madau parameterisation. + The specific parameters for CSFRD must be provided by subclasses. + """ + def __init__(self, SFH_MODEL): + """Initialise the MadauBase class + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. MadauBase requires the following parameters: + - sigma : float or str + The standard deviation of the log-normal metallicity distribution. + Options are: + - Bavera+20 + - Neijssel+19 + - float + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + - CSFRD_params: dict + Parameters for the cosmic star formation rate density (CSFRD) + - a, b, c, d : float + Follows the Madau & Dickinson (2014) CSFRD formula (Eq. 15) in [1]_ + + References + ---------- + .. [1] Madau, P., & Dickinson, M. (2014). Cosmic star formation history. + Annual Review of Astronomy and Astrophysics, 52, 415-486. + https://ui.adsabs.harvard.edu/abs/2014ARA%26A..52..415M/abstract + + """ + if "sigma" not in SFH_MODEL: + raise ValueError("sigma not given!") + self.CSFRD_params = None + super().__init__(SFH_MODEL) + + def CSFRD(self, z): + """The cosmic star formation rate density at a given redshift. + + Follows the Madau & Dickinson (2014) cosmic star formation rate + density formula. + + Parameters + ---------- + z : float or np.array + Cosmological redshift. + + Returns + ------- + float or array + The cosmic star formation rate density at the given redshift. + """ + p = self.CSFRD_params + return p["a"] * (1.0 + z) ** p["b"] / (1.0 + ((1.0 + z) / p["c"]) ** p["d"]) + + def std_log_metallicity_dist(self): + """Return the standard deviation of the log-normal metallicity + distribution + + Either recognised the strings "Bavera+20" (sigma=0.5) + or "Neijssel+19" (sigma=0.39) or a float value. + + Returns + ------- + float + The standard deviation of the log-normal metallicity distribution. + """ + sigma = self.sigma + if isinstance(sigma, str): + if sigma == "Bavera+20": + return 0.5 + elif sigma == "Neijssel+19": + return 0.39 + else: + raise ValueError("Unknown sigma choice!") + elif isinstance(sigma, (float, int)): + return np.float64(sigma) + else: + raise ValueError(f"Invalid sigma value {sigma}!") + + def mean_metallicity(self, z): + """The mean metallicity at a given redshift + + Follows Madau & Fragos (2017) mean metallicity evolution + + Parameters + ---------- + z : float or np.array + Cosmological redshift. + + Returns + ------- + float or array + The mean metallicity at the given redshift. + """ + return 10 ** (0.153 - 0.074 * z ** 1.34) * Zsun + + def fSFR(self, z, metallicity_bins): + """Fraction of the SFR at a given redshift z in a given metallicity + bin as described in Bavera et al. (2020). + + Parameters + ---------- + z : np.array + Cosmological redshift. + metallicity_bins : array + Metallicity bins edges in absolute metallicity. + + Returns + ------- + ndarray + Fraction of the SFR in the given metallicity bin at the given + redshift. + """ + sigma = self.std_log_metallicity_dist() + # Compute mu; if z is an array, mu will be an array. + mu = np.log10(self.mean_metallicity(z)) - sigma ** 2 * np.log(10) / 2.0 + # Ensure mu is an array for consistency + mu_array = np.atleast_1d(mu) + + fSFR = np.zeros((len(z), len(metallicity_bins) - 1)) + for i, mean in enumerate(mu_array): + cdf_func = lambda x: stats.norm.cdf(np.log10(x), mean, sigma) + fSFR[i, :] = self._distribute_cdf(cdf_func, metallicity_bins) + + return fSFR + +class MadauDickinson14(MadauBase): + """Madau & Dickinson (2014) [1]_ star formation history model using the + mean metallicity evolution of Madau & Fragos (2017) [2]_. + + References + ---------- + .. [1] Madau, P., & Dickinson, M. (2014). ARA&A, 52, 415-486. + https://ui.adsabs.harvard.edu/abs/2014ARA%26A..52..415M + .. [2] Madau, P., & Fragos, T. (2017). ApJ, 840(1), 39. + https://ui.adsabs.harvard.edu/abs/2017ApJ...840...39M + """ + + def __init__(self, SFH_MODEL): + """Initialise the Madau & Dickinson (2014) [1]_ SFH model with the + metallicity evolution of Madau & Fragos (2017) [2]_. + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. Madau+14 requires the following parameters: + - sigma : float or str + The standard deviation of the log-normal metallicity + distribution. + Options are: + - Bavera+20 + - Neijssel+19 + - float + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + - CSFRD_params: dict + Parameters for the cosmic star formation rate density (CSFRD) + - a, b, c, d : float + Follows the Madau & Dickinson (2014) CSFRD formula (Eq. 15) in [1]_ + + References + ---------- + .. [1] Madau, P., & Dickinson, M. (2014). ARA&A, 52, 415-486. + https://ui.adsabs.harvard.edu/abs/2014ARA%26A..52..415M + .. [2] Madau, P., & Fragos, T. (2017). ApJ, 840(1), 39. + https://ui.adsabs.harvard.edu/abs/2017ApJ...840...39M + """ + super().__init__(SFH_MODEL) + # Parameters for Madau+Dickinson14 CSFRD + self.CSFRD_params = { + "a": 0.015, + "b": 2.7, + "c": 2.9, + "d": 5.6, + } + +class MadauFragos17(MadauBase): + """The Madau & Fragos (2017) star formation history model with the + metallicity evolution of Madau & Fragos (2017) [1]_. + + + References + ---------- + .. [1] Madau, P., & Fragos, T. (2017). ApJ, 840(1), 39. + https://ui.adsabs.harvard.edu/abs/2017ApJ...840...39M/abstract + """ + + def __init__(self, SFH_MODEL): + """Initialise the Madau+17 model + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. Madau+17 requires the following parameters: + - sigma : float or str + The standard deviation of the log-normal metallicity distribution. + Options are: + - Bavera+20 + - Neijssel+19 + - float + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + super().__init__(SFH_MODEL) + # Parameters for Madau+Fragos17 CSFRD + self.CSFRD_params = { + "a": 0.01, + "b": 2.6, + "c": 3.2, + "d": 6.2, + } + +class Neijssel19(MadauBase): + """The Neijssel et al. (2019) [1]_ star formation history model, which fits + the Madau & Dickinson (2014) [2]_ cosmic star formation rate density formula + with the BBH merger rate and uses a truncated log-normal distribution for + the mean metallicity distribution. + The mean metallicity evolution follows the Langer and Normal parameterisation + also fitted to the BBH merger rate. + + + References + ---------- + .. [1] Neijssel, C. J., et al. (2019). MNRAS, 490, 3740. + https://ui.adsabs.harvard.edu/abs/2019MNRAS.490.3740N + .. [2] Madau, P., & Fragos, T. (2017). ApJ, 840(1), 39. + https://ui.adsabs.harvard.edu/abs/2017ApJ...840...39M/ + """ + def __init__(self, SFH_MODEL): + """Initialise the Neijssel+19 model + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. Neijssel+19 requires the following parameters: + - sigma : float or str + The standard deviation of the log-normal metallicity distribution. + Options are: + - Bavera+20 + - Neijssel+19 + - float + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + super().__init__(SFH_MODEL) + # Parameters for Neijssel+19 CSFRD + self.CSFRD_params = { + "a": 0.01, + "b": 2.77, + "c": 2.9, + "d": 4.7, + } + + # overwrite mean_metallicity method of MadauBase + def mean_metallicity(self, z): + """Calculate the mean metallicity at a given redshift + + Overwrites the mean_metallicity method of MadauBase class. + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + + Returns + ------- + float or array-like + The mean metallicity at the given redshift(s). + """ + return 0.035 * 10 ** (-0.23 * z) + + def fSFR(self, z, metallicity_bins): + """Fraction of the SFR at a given redshift z in a given metallicity bin + as described in Neijssel et al. (2019). + + Overwrites the fSFR method of MadauBase class. + + Parameters + ---------- + z : np.array + Cosmological redshift. + metallicity_bins : array + Metallicity bins edges in absolute metallicity. + + Returns + ------- + ndarray + Fraction of the SFR in the given metallicity bins at the + given redshift. + """ + # assume a truncated ln-normal distribution of metallicities + sigma = self.std_log_metallicity_dist() + mu = np.log(self.mean_metallicity(z)) - sigma**2 / 2.0 + mu_array = np.atleast_1d(mu) + fSFR = np.zeros((len(z), len(metallicity_bins) - 1)) + for i, mean in enumerate(mu_array): + cdf_func = lambda x: stats.norm.cdf(np.log(x), mean, sigma) + fSFR[i,:] = self._distribute_cdf(cdf_func, metallicity_bins) + return fSFR + +class IllustrisTNG(SFHBase): + """The IllustrisTNG star formation history model. + + Uses the TNG100-1 model from the IllustrisTNG simulation. + + https://www.tng-project.org/ + """ + + def __init__(self, SFH_MODEL): + """Initialise the IllustrisTNG model + + Parameters + ---------- + SFH_MODEL : dict + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + super().__init__(SFH_MODEL) + # load the TNG data + illustris_data = self._get_illustrisTNG_data() + # the data is stored in reverse order high to low redshift + self.CSFRD_data = np.flip(illustris_data["SFR"]) + self.redshifts = np.flip(illustris_data["redshifts"]) + + self.Z = illustris_data["mets"] + self.M = np.flip(illustris_data["M"], axis=0) # Msun + + def _get_illustrisTNG_data(self, verbose=False): # pragma: no cover + """Load IllustrisTNG SFR dataset into the class. + + Parameters + ---------- + verbose : bool, optional + Print information about the data loading. + """ + if verbose: + print("Loading IllustrisTNG data...") + return np.load(os.path.join(PATH_TO_POSYDON_DATA, "SFR/IllustrisTNG.npz")) + + def CSFRD(self, z): + """The cosmic star formation rate density at a given redshift. + + Parameters + ---------- + z : float or np.array + Cosmological redshift. + + Returns + ------- + float or array + The cosmic star formation rate density at the given redshift(s). + """ + SFR_interp = interp1d(self.redshifts, self.CSFRD_data) + return SFR_interp(z) + + def mean_metallicity(self, z): + """Calculate the mean metallicity at a given redshift + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + + Returns + ------- + float or array-like + The mean metallicity at the given redshift(s). + """ + out = np.zeros_like(self.redshifts) + for i in range(len(out)): + if np.sum(self.M[i, :]) == 0: + out[i] = np.nan + else: + out[i] = np.average(self.Z, weights=self.M[i, :]) + Z_interp = interp1d(self.redshifts, out) + return Z_interp(z) + + def fSFR(self, z, metallicity_bins): + """Calculate the fractional SFR as a function of redshift and + metallicity bins. + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + metallicity_bins : array + Metallicity bins edges in absolute metallicity. + + Returns + ------- + ndarray + Fraction of the SFR in the given metallicity bin at the given redshift. + """ + redshift_indices = np.array([np.where(self.redshifts <= i)[0][-1] for i in z]) + Z_dist = self.M[redshift_indices] + fSFR = np.zeros((len(z), len(metallicity_bins) - 1)) + for i in range(len(z)): + if Z_dist[i].sum() == 0.0: + continue + else: + # At a specific redshift, the SFR is distributed over the metallicities + # according to the mass distribution + # Add a final point to the CDF and metallicities to ensure normalisation to 1 + Z_dist_cdf = np.cumsum(Z_dist[i]) / Z_dist[i].sum() + Z_dist_cdf = np.append(Z_dist_cdf, 1) + + Z_x_values = np.append(np.log10(self.Z), 0) + Z_dist_cdf_interp = interp1d(Z_x_values, Z_dist_cdf) + cdf_fun = lambda x: Z_dist_cdf_interp(np.log10(x)) + fSFR[i, :] = self._distribute_cdf(cdf_fun, metallicity_bins) + + return fSFR + +class Chruslinska21(SFHBase): + """The Chruślińska+21 star formation history model [1]_. + + + References + ---------- + .. [1] Chruślińska, M., et al. (2021). MNRAS, 508, 4994. + https://ui.adsabs.harvard.edu/abs/2021MNRAS.508.4994C + + Data source: + https://ftp.science.ru.nl/astro/mchruslinska/Chruslinska_et_al_2021/ + """ + def __init__(self, SFH_MODEL): + """Initialise the Chruslinska+21 model + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. Chruslinska+21 requires the + following parameters: + - sub_model : str + The sub-model to use. + This is the name of the file containing the data. + - Z_solar_scaling : str + The scaling of the solar metallicity. Options are: + - Asplund09 + - AndersGrevesse89 + - GrevesseSauval98 + - Villante14 + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + if "sub_model" not in SFH_MODEL: + raise ValueError("Sub-model not given!") + if "Z_solar_scaling" not in SFH_MODEL: + raise ValueError("Z_solar_scaling not given!") + super().__init__(SFH_MODEL) + self._load_chruslinska_data() + + def _load_chruslinska_data(self, verbose=False): + """Load the data from the Chruslinska+21 models. + Transforms the data to the format used in the classes. + + Parameters + ---------- + verbose : bool, optional + Print information about the data loading. + """ + # oxygen to hydrogen abundance ratio ( FOH == 12 + log(O/H) ) + # as used in the calculations - do not change + # This is the metallicity bin edges used in the Chruslinska+21 calculations + FOH_min = 5.3 + FOH_max = 9.7 + self.FOH_bins = np.linspace(FOH_min, FOH_max, 200) + self.dFOH = self.FOH_bins[1] - self.FOH_bins[0] + # Need to use the Z_solar_scaling parameter to + # convert the FOH bins to absolute metallicity. + # Use solar metallicity as the reference point + self.Z = self._FOH_to_Z(self.FOH_bins) + + self._data_folder = os.path.join(PATH_TO_POSYDON_DATA, "SFR/Chruslinska+21") + _, self.redshifts, delta_T = self._load_redshift_data(verbose) + M = self._load_raw_data() + self.SFR_data = np.array( [M[ii]/(1e6*delta_T[ii]) for ii in range(len(delta_T))])/self.dFOH + + def _FOH_to_Z(self, FOH): + """Convert the oxygen to hydrogen abundance ratio to absolute metallicity + + Parameters + ---------- + FOH : float or array-like + The oxygen to hydrogen abundance ratio. + + Returns + ------- + float or array-like + The absolute metallicity. + """ + # scalings from Chruslinksa+21 + if self.Z_solar_scaling == "Asplund09": + Zsun = 0.0134 + FOHsun = 8.69 + elif self.Z_solar_scaling == "AndersGrevesse89": + Zsun = 0.017 + FOHsun = 8.83 + elif self.Z_solar_scaling == "GrevesseSauval98": + Zsun = 0.0201 + FOHsun = 8.93 + elif self.Z_solar_scaling == "Villante14": + Zsun = 0.019 + FOHsun = 8.85 + else: + valid_options = ["Asplund09", "AndersGrevesse89", + "GrevesseSauval98", "Villante14"] + raise ValueError(f"Invalid Z_solar_scaling " + f"'{self.Z_solar_scaling}'. " + f"Valid options: {valid_options}") + logZ = np.log10(Zsun) + FOH - FOHsun + return 10**logZ + + def mean_metallicity(self, z): + """Calculate the mean metallicity at a given redshift + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + + Returns + ------- + float or array-like + The mean metallicity at the given redshift(s). + """ + mean_over_redshift = np.zeros_like(self.redshifts) + for i in range(len(mean_over_redshift)): + if np.sum(self.SFR_data[i]) == 0: + mean_over_redshift[i] = np.nan + else: + mean_over_redshift[i] = np.average(self.Z, weights=self.SFR_data[i,:]*self.dFOH) + + Z_interp = interp1d(self.redshifts, mean_over_redshift) + return Z_interp(z) + + def fSFR(self, z, metallicity_bins): + """Calculate the fractional SFR as a function of redshift and metallicity bins + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + metallicity_bins : array + Metallicity bins edges in absolute metallicity. + + Returns + ------- + ndarray + Fraction of the SFR in the given metallicity bin at the given redshift. + """ + # only use data within the metallicity bounds (no lower bound) + redshift_indices = np.array([np.where(self.redshifts <= i)[0][0] for i in z]) + Z_dist = self.SFR_data[redshift_indices] + fSFR = np.zeros((len(z), len(metallicity_bins) - 1)) + + for i in range(len(z)): + if Z_dist[i].sum() == 0.0: + continue + else: + Z_dist_cdf = np.cumsum(Z_dist[i]) / Z_dist[i].sum() + Z_dist_cdf = np.append(Z_dist_cdf, 1) + Z_x_values = np.append(np.log10(self.Z), 0) + Z_dist_cdf_interp = interp1d(Z_x_values, Z_dist_cdf) + cdf_fun = lambda x: Z_dist_cdf_interp(np.log10(x)) + fSFR[i, :] = self._distribute_cdf(cdf_fun, metallicity_bins) + return fSFR + + def _load_redshift_data(self, verbose=False): # pragma: no cover + """Load the redshift data from a Chruslinsk+21 model file. + + Returns + ------- + time : array + the center of the time bins + redshift : array + the redshifts corresponding to the time bins + delt : array + the width of the time bins + """ + if verbose: + print("Loading redshift data...") + + time, redshift, delt = np.loadtxt( + os.path.join(self._data_folder, "Time_redshift_deltaT.dat"), unpack=True) + return time, redshift, delt + + def _load_raw_data(self): # pragma: no cover + """Read the sub-model data from the file + + The data structure is as follows: + - mass per unit (comoving) volume formed in each z (row) - FOH (column) bin + + Returns + ------- + array + Mass formed per unit volume in each redshift and FOH bin + """ + input_file = os.path.join(self._data_folder, f"{self.sub_model}.dat") + data = np.loadtxt(input_file) + return data + + def CSFRD(self, z): + """Interpolate the cosmic star formation rate density at the given redshift(s) + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + + Returns + ------- + float or array-like + The cosmic star formation rate density at the given redshift(s). + """ + SFR_interp = interp1d(self.redshifts, np.sum(self.SFR_data*self.dFOH, axis=1)) + return SFR_interp(z) + +class Fujimoto24(MadauBase): + """The Fujimoto et al. (2024) star formation history model. + mean metallicity evolution of Madau & Fragos (2017). + + Fujimoto et al. (2024), ApJ SS, 275, 2, 36, 59 + https://ui.adsabs.harvard.edu/abs/2024ApJS..275...36F/abstract + """ + def __init__(self, SFH_MODEL): + """Initialise the Fujimoto+24 model + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. Fujimoto+24 requires the following parameters: + - sigma : float or str + The standard deviation of the log-normal metallicity distribution. + Options are: + - Bavera+20 + - Neijssel+19 + - float + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + super().__init__(SFH_MODEL) + # Parameters for Fujimoto+24 CSFRD + self.CSFRD_params = { + "a": 0.010, + "b": 2.8, + "c": 3.3, + "d": 6.6, + } + +class Zavala21(MadauBase): + """The Zavala et al. (2021) star formation history model [1]_. + + The "min" and "max" models are based on the obscured and unobscured + star formation rate density models, respectively. + + References + ---------- + .. [1] Zavala, J., et al. (2021). ApJ, 909(2), 165. + https://ui.adsabs.harvard.edu/abs/2021ApJ...909..165Z/ + """ + def __init__(self, SFH_MODEL): + """Initialise the Zavala+21 model + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. Zavala+21 requires the following parameters: + - sub_model : str + The sub-model to use. Either "min" or "max". + Additional SFH model parameters: + - Z_max : float + The maximum metallicity in absolute units. + - Z_min : float + The minimum metallicity in absolute units. + - normalise : bool + Normalise the metallicity distribution to 1. + """ + if "sub_model" not in SFH_MODEL: + raise ValueError("Sub-model not given!") + + super().__init__(SFH_MODEL) + self._load_zavala_data() + + def _load_zavala_data(self): # pragma: no cover + """Load the data from the Zavala+21 models + Transforms the data to the format used in the classes. + + """ + data_file = os.path.join(PATH_TO_POSYDON_DATA, "SFR/Zavala+21.txt") + tmp_data = pd.read_csv(data_file, + names=["redshift", "SFRD_min", "SFRD_max"], + skiprows=1, + sep=r"\s+") + self.redshifts = tmp_data["redshift"].values + # The min / max values originally come from their obscured + # and unobscured SFRD model. + if self.sub_model == "min": + self.SFR_data = tmp_data["SFRD_min"].values + elif self.sub_model == "max": + self.SFR_data = tmp_data["SFRD_max"].values + else: + raise ValueError("Invalid sub-model!") + + # overwrite the CSFRD method of MadauBase + def CSFRD(self, z): + SFR_interp = interp1d(self.redshifts, self.SFR_data) + return SFR_interp(z) + +def get_SFH_model(SFH_MODEL): + """Return the appropriate SFH model based on the given parameters + + Parameters + ---------- + SFH_MODEL : dict + SFH model parameters. + + Returns + ------- + a SFHBase instance or subclass + The SFH model instance. + """ + if SFH_MODEL["SFR"] == "Madau+Fragos17": + return MadauFragos17(SFH_MODEL) + elif SFH_MODEL["SFR"] == "Madau+Dickinson14": + return MadauDickinson14(SFH_MODEL) + elif SFH_MODEL["SFR"] == "Fujimoto+24": + return Fujimoto24(SFH_MODEL) + elif SFH_MODEL["SFR"] == "Neijssel+19": + return Neijssel19(SFH_MODEL) + elif SFH_MODEL["SFR"] == "IllustrisTNG": + return IllustrisTNG(SFH_MODEL) + elif SFH_MODEL["SFR"] == "Chruslinska+21": + return Chruslinska21(SFH_MODEL) + elif SFH_MODEL["SFR"] == "Zavala+21": + return Zavala21(SFH_MODEL) + else: + raise ValueError("Invalid SFR!") + +def SFR_per_met_at_z(z, met_bins, SFH_MODEL): + """Calculate the SFR per metallicity bin at a given redshift(s) + + Parameters + ---------- + z : float or array-like + Cosmological redshift. + met_bins : array + Metallicity bins edges in absolute metallicity. + SFH_MODEL : dict + SFH model parameters. + + Returns + ------- + SFH : 2D array + Star formation history per metallicity bin at the given redshift(s). + + """ + SFH = get_SFH_model(SFH_MODEL) + return SFH(z, met_bins) + +# TODO: No testing coverage for the following function, but should be added +def get_formation_times(N_binaries, star_formation="constant", **kwargs): # pragma: no cover """Get formation times of binaries in a population based on a SFH scenario. Parameters @@ -94,263 +1099,4 @@ def get_formation_times(N_binaries, star_formation="constant", **kwargs): "Unknown star formation scenario '{}' given. Valid options: {}".format( star_formation, ",".join(SFH_SCENARIOS) ) - ) - - -def get_illustrisTNG_data(verbose=False): - """Load IllustrisTNG SFR dataset.""" - if verbose: - print("Loading IllustrisTNG data...") - return np.load(os.path.join(PATH_TO_POSYDON_DATA, "SFR/IllustrisTNG.npz")) - - -def star_formation_rate(SFR, z): - """Star formation rate in M_sun yr^-1 Mpc^-3. - - Parameters - ---------- - SFR : string - Star formation rate assumption: - - Madau+Fragos17 see arXiv:1606.07887 - - Madau+Dickinson14 see arXiv:1403.0007 - - Neijssel+19 see arXiv:1906.08136 - - IllustrisTNG see see arXiv:1707.03395 - z : double - Cosmological redshift. - - Returns - ------- - double - The total mass of stars in M_sun formed per comoving volume Mpc^-3 - per year. - """ - if SFR == "Madau+Fragos17": - return ( - 0.01 * (1.0 + z) ** 2.6 / (1.0 + ((1.0 + z) / 3.2) ** 6.2) - ) # M_sun yr^-1 Mpc^-3 - elif SFR == "Madau+Dickinson14": - return ( - 0.015 * (1.0 + z) ** 2.7 / (1.0 + ((1.0 + z) / 2.9) ** 5.6) - ) # M_sun yr^-1 Mpc^-3 - elif SFR == "Neijssel+19": - return ( - 0.01 * (1.0 + z) ** 2.77 / (1.0 + ((1.0 + z) / 2.9) ** 4.7) - ) # M_sun yr^-1 Mpc^-3 - elif SFR == "IllustrisTNG": - illustris_data = get_illustrisTNG_data() - SFR = illustris_data["SFR"] # M_sun yr^-1 Mpc^-3 - redshifts = illustris_data["redshifts"] - SFR_interp = interp1d(redshifts, SFR) - return SFR_interp(z) - else: - raise ValueError("Invalid SFR!") - - -def mean_metallicity(SFR, z): - """Empiric mean metallicity function. - - Parameters - ---------- - SFR : string - Star formation rate assumption: - - Madau+Fragos17 see arXiv:1606.07887 - - Madau+Dickinson14 see arXiv:1403.0007 - - Neijssel+19 see arXiv:1906.08136 - z : double - Cosmological redshift. - - Returns - ------- - double - Mean metallicty of the universe at the given redhist. - - """ - - if SFR == "Madau+Fragos17" or SFR == "Madau+Dickinson14": - return 10 ** (0.153 - 0.074 * z**1.34) * Zsun - elif SFR == "Neijssel+19": - return 0.035 * 10 ** (-0.23 * z) - else: - raise ValueError("Invalid SFR!") - - -def std_log_metallicity_dist(sigma): - """Standard deviation of the log-metallicity distribution. - - Returns - ------- - double - Standard deviation of the adopted distribution. - - """ - if isinstance(sigma, str): - if sigma == "Bavera+20": - return 0.5 - elif sigma == "Neijssel+19": - return 0.39 - else: - raise ValueError("Uknown sigma choice!") - elif isinstance(sigma, float): - return sigma - else: - raise ValueError(f"Invalid sigma value {sigma}!") - - -def SFR_Z_fraction_at_given_redshift( - z, SFR, sigma, metallicity_bins, Z_max, select_one_met -): - """'Fraction of the SFR at a given redshift z in a given metallicity bin as in Eq. (B.8) of Bavera et al. (2020). - - Parameters - ---------- - z : np.array - Cosmological redshift. - SFR : string - Star formation rate assumption: - - Madau+Fragos17 see arXiv:1606.07887 - - Madau+Dickinson14 see arXiv:1403.0007 - - IllustrisTNG see see arXiv:1707.03395 - - Neijssel+19 see arXiv:1906.08136 - sigma : double / string - Standard deviation of the log-metallicity distribution. - If string, it can be 'Bavera+20' or 'Neijssel+19'. - metallicity_bins : array - Metallicity bins edges in absolute metallicity. - Z_max : double - Maximum metallicity in absolute metallicity. - select_one_met : bool - If True, the function returns the fraction of the SFR in the given metallicity bin. - If False, the function returns the fraction of the SFR in the given metallicity bin and the fraction of the SFR in the metallicity bin - - Returns - ------- - array - Fraction of the SFR in the given metallicity bin at the given redshift. - In absolute metallicity. - """ - - if SFR == "Madau+Fragos17" or SFR == "Madau+Dickinson14": - sigma = std_log_metallicity_dist(sigma) - mu = np.log10(mean_metallicity(SFR, z)) - sigma**2 * np.log(10) / 2.0 - # renormalisation constant. We can use mu[0], since we integrate over the whole metallicity range - norm = stats.norm.cdf(np.log10(Z_max), mu[0], sigma) - fSFR = np.empty((len(z), len(metallicity_bins) - 1)) - fSFR[:, :] = np.array( - [(stats.norm.cdf(np.log10(metallicity_bins[1:]), m, sigma) / norm - - stats.norm.cdf(np.log10(metallicity_bins[:-1]), m, sigma) / norm - ) for m in mu ] - ) - if not select_one_met: - fSFR[:, 0] = stats.norm.cdf(np.log10(metallicity_bins[1]), mu, sigma) / norm - fSFR[:,-1] = norm - stats.norm.cdf(np.log10(metallicity_bins[-1]), mu, sigma)/norm - - elif SFR == "Neijssel+19": - # assume a truncated ln-normal distribution of metallicities - sigma = std_log_metallicity_dist(sigma) - mu = np.log(mean_metallicity(SFR, z)) - sigma**2 / 2.0 - # renormalisation constant - norm = stats.norm.cdf(np.log(Z_max), mu[0], sigma) - fSFR = np.empty((len(z), len(metallicity_bins) - 1)) - fSFR[:, :] = np.array( - [ - ( - stats.norm.cdf(np.log(metallicity_bins[1:]), m, sigma) / norm - - stats.norm.cdf(np.log(metallicity_bins[:-1]), m, sigma) / norm - ) - for m in mu - ] - ) - if not select_one_met: - fSFR[:, 0] = stats.norm.cdf(np.log(metallicity_bins[1]), mu, sigma) / norm - fSFR[:,-1] = norm - stats.norm.cdf(np.log(metallicity_bins[-1]), mu, sigma)/norm - - elif SFR == "IllustrisTNG": - # numerically itegrate the IlluystrisTNG SFR(z,Z) - illustris_data = get_illustrisTNG_data() - redshifts = illustris_data["redshifts"] - Z = illustris_data["mets"] - M = illustris_data["M"] # Msun - # only use data within the metallicity bounds (no lower bound) - Z_max_mask = Z <= Z_max - redshift_indices = np.array([np.where(redshifts <= i)[0][0] for i in z]) - Z_dist = M[:, Z_max_mask][redshift_indices] - fSFR = np.zeros((len(z), len(metallicity_bins) - 1)) - - for i in range(len(z)): - if Z_dist[i].sum() == 0.0: - continue - else: - # We add a final point to the CDF and metallicities to ensure normalisation to 1 - Z_dist_cdf = np.cumsum(Z_dist[i]) / Z_dist[i].sum() - Z_dist_cdf = np.append(Z_dist_cdf, 1) - Z_x_values = np.append(np.log10(Z[Z_max_mask]), 0) - Z_dist_cdf_interp = interp1d(Z_x_values, Z_dist_cdf) - - fSFR[i, :] = (Z_dist_cdf_interp(np.log10(metallicity_bins[1:])) - - Z_dist_cdf_interp(np.log10(metallicity_bins[:-1]))) - - if not select_one_met: - # add the fraction of the SFR in the first and last bin - # or the only bin without selecting one metallicity - if len(metallicity_bins) == 2: - fSFR[i, 0] = 1 - else: - fSFR[i, 0] = Z_dist_cdf_interp(np.log10(metallicity_bins[1])) - fSFR[i, -1] = 1 - Z_dist_cdf_interp(np.log10(metallicity_bins[-1])) - else: - raise ValueError("Invalid SFR!") - - return fSFR - - -def integrated_SFRH_over_redshift(SFR, sigma, Z, Z_max): - """Integrated SFR history over z as in Eq. (B.10) of Bavera et al. (2020). - - Parameters - ---------- - SFR : string - Star formation rate assumption: - - Madau+Fragos17 see arXiv:1606.07887 - - Madau+Dickinson14 see arXiv:1403.0007 - - Neijssel+19 see arXiv:1906.08136 - Z : double - Metallicity. - - Returns - ------- - double - The total mass of stars formed per comoving volume at a given - metallicity Z. - - """ - - def E(z, Omega_m=cosmology.Om0): - Omega_L = 1.0 - Omega_m - return (Omega_m * (1.0 + z) ** 3 + Omega_L) ** (1.0 / 2.0) - - def f(z, Z): - if SFR == "Madau+Fragos17" or SFR == "Madau+Dickinson14": - sigma = std_log_metallicity_dist(sigma) - mu = np.log10(mean_metallicity(SFR, z)) - sigma**2 * np.log(10) / 2.0 - H_0 = cosmology.H0.to("1/yr").value # yr - # put a cutoff on metallicity at Z_max - norm = stats.norm.cdf(np.log10(Z_max), mu, sigma) - return ( - star_formation_rate(SFR, z) - * stats.norm.pdf(np.log10(Z), mu, sigma) - / norm - * (H_0 * (1.0 + z) * E(z)) ** (-1) - ) - elif SFR == "Neijssel+19": - sigma = std_log_metallicity_dist(sigma) - mu = np.log10(mean_metallicity(SFR, z)) - sigma**2 / 2.0 - H_0 = cosmology.H0.to("1/yr").value # yr - return ( - star_formation_rate(SFR, z) - * stats.norm.pdf(np.log(Z), mu, sigma) - * (H_0 * (1.0 + z) * E(z)) ** (-1) - ) - else: - raise ValueError("Invalid SFR!") - - return sp.integrate.quad(f, 1e-10, np.inf, args=(Z,))[0] # M_sun yr^-1 Mpc^-3 + ) \ No newline at end of file diff --git a/posydon/popsyn/synthetic_population.py b/posydon/popsyn/synthetic_population.py index bcc6fb1425..6d5e190ddb 100644 --- a/posydon/popsyn/synthetic_population.py +++ b/posydon/popsyn/synthetic_population.py @@ -52,15 +52,12 @@ get_comoving_distance_from_redshift, get_cosmic_time_from_redshift, redshift_from_cosmic_time_interpolator, - DEFAULT_MODEL, + DEFAULT_SFH_MODEL, get_redshift_bin_edges, get_redshift_bin_centers, ) -from posydon.popsyn.star_formation_history import ( - star_formation_rate, - SFR_Z_fraction_at_given_redshift, -) +from posydon.popsyn.star_formation_history import SFR_per_met_at_z from posydon.popsyn.binarypopulation import ( BinaryPopulation, @@ -2030,20 +2027,16 @@ def calculate_cosmic_weights(self, SFH_identifier, MODEL_in=None): Examples -------- >>> transient_population = TransientPopulation('filename.h5', 'transient_name') - >>> transient_population.calculate_cosmic_weights('IllustrisTNG', MODEL_in=DEFAULT_MODEL) + >>> transient_population.calculate_cosmic_weights('IllustrisTNG', MODEL_in=DEFAULT_SFH_MODEL) """ # Set model to DEFAULT or provided MODEL parameters # Allows for partial model specification if MODEL_in is None: - MODEL = DEFAULT_MODEL + MODEL = DEFAULT_SFH_MODEL else: - for key in MODEL_in: - if key not in DEFAULT_MODEL: - raise ValueError(key + " is not a valid parameter name!") - - # write the DEFAULT_MODEL with updates parameters to self.MODEL. - MODEL = DEFAULT_MODEL + # write the DEFAULT_SFH_MODEL with updated parameters to MODEL. + MODEL = DEFAULT_SFH_MODEL MODEL.update(MODEL_in) path_in_file = ( @@ -2083,23 +2076,12 @@ def calculate_cosmic_weights(self, SFH_identifier, MODEL_in=None): get_redshift_from_cosmic_time = redshift_from_cosmic_time_interpolator() indices = self.indices + met_edges = rates.edges_metallicity_bins # sample the SFH for only the events that are within the Hubble time # only need to sample the SFH at each metallicity and z_birth - # Not for every event! - SFR_at_z_birth = star_formation_rate(rates.MODEL["SFR"], z_birth) - # get metallicity bin edges - met_edges = rates.edges_metallicity_bins - - # get the fractional SFR at each metallicity and z_birth - fSFR = SFR_Z_fraction_at_given_redshift( - z_birth, - rates.MODEL["SFR"], - rates.MODEL["sigma_SFR"], - met_edges, - rates.MODEL["Z_max"], - rates.MODEL["select_one_met"], - ) - + # Not for every event! + SFR_per_met_at_z_birth = SFR_per_met_at_z(z_birth, met_edges, rates.MODEL) + # simulated mass per given metallicity corrected for the unmodeled # single and binary stellar mass M_model = rates.mass_per_metallicity.loc[rates.centers_metallicity_bins / Zsun][ @@ -2153,7 +2135,7 @@ def calculate_cosmic_weights(self, SFH_identifier, MODEL_in=None): ) weights = np.zeros((len(met_events), nr_of_birth_bins)) - for i, met in enumerate(rates.centers_metallicity_bins): + for j, met in enumerate(rates.centers_metallicity_bins): mask = met_events == met weights[mask, :] = ( 4.0 @@ -2161,8 +2143,8 @@ def calculate_cosmic_weights(self, SFH_identifier, MODEL_in=None): * c * D_c[mask] ** 2 * deltaT - * (fSFR[:, i] * SFR_at_z_birth) - / M_model[i] + * SFR_per_met_at_z_birth[:, j] + / M_model[j] ) # yr^-1 with pd.HDFStore(self.filename, mode="a") as store: diff --git a/posydon/unit_tests/grids/test_MODELS.py b/posydon/unit_tests/grids/test_MODELS.py index 0ba9cfafb5..d1fbb0ba22 100644 --- a/posydon/unit_tests/grids/test_MODELS.py +++ b/posydon/unit_tests/grids/test_MODELS.py @@ -1,4 +1,4 @@ -"""Unit tests of posydon/grids/MODELS.py +"""Unit tests of posydon/grids/SN_MODELS.py """ __authors__ = [ @@ -6,7 +6,7 @@ ] # import the module which will be tested -import posydon.grids.MODELS as totest +import posydon.grids.SN_MODELS as totest # import other needed code for the tests, which is not already imported in the # module you like to test @@ -18,11 +18,11 @@ class TestElements: # check for objects, which should be an element of the tested module def test_dir(self): - elements = {'MODELS', 'NEUTRINO_MASS_LOSS_UPPER_LIMIT',\ + elements = {'SN_MODELS', 'NEUTRINO_MASS_LOSS_UPPER_LIMIT',\ 'STATE_NS_STARMASS_UPPER_LIMIT', '__authors__',\ '__builtins__', '__cached__', '__doc__', '__file__',\ '__loader__', '__name__', '__package__', '__spec__',\ - 'get_MODEL_NAME'} + 'get_SN_MODEL_NAME', 'DEFAULT_SN_MODEL', 'get_SN_MODEL'} totest_elements = set(dir(totest)) missing_in_test = elements - totest_elements assert len(missing_in_test) == 0, "There are missing objects in "\ @@ -38,70 +38,119 @@ def test_dir(self): +"added on purpose and update this "\ +"unit test." - def test_instance_MODELS(self): - assert isinstance(totest.MODELS, dict), "MODELS is of type: "\ - +str(type(totest.MODELS)) + def test_instance_DEFAULT_SN_MODEL(self): + assert isinstance(totest.DEFAULT_SN_MODEL, dict), "DEFAULT_SN_MODEL "\ + + "is of type: " + str(type(totest.DEFAULT_SN_MODEL)) - def test_instance_get_MODEL_NAME(self): - assert isroutine(totest.get_MODEL_NAME) + def test_instance_SN_MODELS(self): + assert isinstance(totest.SN_MODELS, dict), "SN_MODELS is of type: "\ + + str(type(totest.SN_MODELS)) + + def test_instance_get_SN_MODEL(self): + assert isroutine(totest.get_SN_MODEL) + + def test_instance_get_SN_MODEL_NAME(self): + assert isroutine(totest.get_SN_MODEL_NAME) class TestValues: # check that the values fit - def test_value_MODELS(self): - assert len(totest.MODELS) > 0 - for m in totest.MODELS: - assert isinstance(totest.MODELS[m], dict) - assert 'mechanism' in totest.MODELS[m].keys() - assert 'engine' in totest.MODELS[m].keys() + def test_value_DEFAULT_SN_MODEL(self): + for k in ['mechanism', 'engine', 'PISN', 'PISN_CO_shift',\ + 'PPI_extra_mass_loss', 'ECSN', 'conserve_hydrogen_envelope',\ + 'conserve_hydrogen_PPI', 'max_neutrino_mass_loss',\ + 'max_NS_mass', 'use_interp_values', 'use_profiles',\ + 'use_core_masses', 'allow_spin_None',\ + 'approx_at_he_depletion']: + assert k in totest.DEFAULT_SN_MODEL.keys() + def test_value_SN_MODELS(self): + assert len(totest.SN_MODELS) > 0 + for m in totest.SN_MODELS: + assert isinstance(totest.SN_MODELS[m], dict) -class TestFunctions: - @fixture - def bad_MODEL(self): - return {"mechanism": "",\ - "engine": "",\ - "PISN": "",\ - "ECSN": "",\ - "conserve_hydrogen_envelope": False,\ - "max_neutrino_mass_loss":\ - totest.NEUTRINO_MASS_LOSS_UPPER_LIMIT,\ - "max_NS_mass": totest.STATE_NS_STARMASS_UPPER_LIMIT,\ - "use_interp_values": False,\ - "use_profiles": False,\ - "use_core_masses": False,\ - "approx_at_he_depletion": False} +class TestFunctions: # test functions - def test_get_MODEL_NAME(self, bad_MODEL, capsys): + def test_get_SN_MODEL(self): + # missing argument + with raises(TypeError, match="missing 1 required positional "\ + +"argument: 'name'"): + totest.get_SN_MODEL() + # examples: undefined + assert totest.get_SN_MODEL("Test") == totest.DEFAULT_SN_MODEL + # examples: pre defined supernova models + for n, m in totest.SN_MODELS.items(): + model = totest.get_SN_MODEL(n) + for k in totest.DEFAULT_SN_MODEL.keys(): + assert k in model + for k, v in m.items(): + assert model[k] == v + + def test_get_SN_MODEL_NAME(self, capsys): # missing argument with raises(TypeError, match="missing 1 required positional "\ - +"argument: 'input_MODEL'"): - totest.get_MODEL_NAME() + +"argument: 'input_SN_MODEL'"): + totest.get_SN_MODEL_NAME() # bad input - with raises(TypeError, match="'NoneType' object is not subscriptable"): - totest.get_MODEL_NAME(None) - # examples: failed - assert totest.get_MODEL_NAME(bad_MODEL) is None - # examples: pre-defined models - for n, m in totest.MODELS.items(): + with raises(TypeError, match="argument of type 'NoneType' is not "\ + +"iterable"): + totest.get_SN_MODEL_NAME(None) + # examples: missing key + bad_SN_MODEL = {} + for v in [True, False]: + assert totest.get_SN_MODEL_NAME(bad_SN_MODEL, verbose=v) is None + captured_output = capsys.readouterr().out + if v: + assert "missing key: mechanism" in captured_output + else: + assert captured_output == "" + # examples: wrong value + bad_SN_MODEL = {"mechanism": ""} + for v in [True, False]: + assert totest.get_SN_MODEL_NAME(bad_SN_MODEL, verbose=v) is None + captured_output = capsys.readouterr().out + if v: + assert "mismatch: mechanism" in captured_output + else: + assert captured_output == "" + # examples: pre-defined supernova models + for n in totest.SN_MODELS.keys(): + try: + m = totest.get_SN_MODEL(n) + except: # skip test as test on get_SN_MODEL should fail + assert n in totest.SN_MODELS + return for v in [True, False]: - assert totest.get_MODEL_NAME(m, verbose=v) == n + assert totest.get_SN_MODEL_NAME(m, verbose=v) == n captured_output = capsys.readouterr().out if v: assert "mismatch:" in captured_output - assert f"matched to model: {n}" in captured_output + assert f"matched to supernova model: {n}" in\ + captured_output else: assert captured_output == "" # examples: first model with allowed differences - n = list(totest.MODELS.keys())[0] + n = list(totest.SN_MODELS.keys())[0] + try: + m = totest.get_SN_MODEL(n) + except: # skip test as test on get_SN_MODEL should fail + assert n in totest.SN_MODELS + return for k in ["use_interp_values", "use_profiles", "use_core_masses"]: - m = totest.MODELS[n].copy() m[k] = not m[k] - assert totest.get_MODEL_NAME(m) == n - m = totest.MODELS[n].copy() + assert totest.get_SN_MODEL_NAME(m) == n + try: + m = totest.get_SN_MODEL(n) + except: # skip test as test on get_SN_MODEL should fail + assert n in totest.SN_MODELS + return m["use_test"] = "unit" - assert totest.get_MODEL_NAME(m) == n - m = totest.MODELS[n].copy() + assert totest.get_SN_MODEL_NAME(m) == n + try: + m = totest.get_SN_MODEL(n) + except: # skip test as test on get_SN_MODEL should fail + assert n in totest.SN_MODELS + return m["ECSN"] = "test" - assert totest.get_MODEL_NAME(m) == n + assert totest.get_SN_MODEL_NAME(m) == n diff --git a/posydon/unit_tests/grids/test_io.py b/posydon/unit_tests/grids/test_io.py index 0a5981b45a..678ab62678 100644 --- a/posydon/unit_tests/grids/test_io.py +++ b/posydon/unit_tests/grids/test_io.py @@ -221,7 +221,10 @@ def test_initial_values_from_dirname(self): assert totest.initial_values_from_dirname("./v2/"\ +"initial_z_1.0_m1_2.1_m2_2.2_initial_period_in_days_2.0") ==\ ('2.1', '2.2', '2.0', '1.0') - + # examples: binary v2 with eccentricity + assert totest.initial_values_from_dirname(\ + "initial_z_1.0_m1_2.1_m2_2.2_initial_period_in_days_2.0_initial_eccentricity_0.5") ==\ + ('2.1', '2.2', '2.0', '1.0', '0.5') class TestRunReader: @fixture diff --git a/posydon/unit_tests/grids/test_post_processing.py b/posydon/unit_tests/grids/test_post_processing.py index 7986b8e201..0781479b5f 100644 --- a/posydon/unit_tests/grids/test_post_processing.py +++ b/posydon/unit_tests/grids/test_post_processing.py @@ -17,7 +17,8 @@ import h5py import json import os -from posydon.utils.posydonwarning import InappropriateValueWarning +from posydon.utils.posydonwarning import InappropriateValueWarning,\ + POSYDONWarning from posydon.grids.psygrid import PSyGrid from posydon.config import PATH_TO_POSYDON, PATH_TO_POSYDON_DATA from posydon.unit_tests._helper_functions_for_tests.psygrid import get_PSyGrid @@ -29,7 +30,7 @@ def test_dir(self): elements = {'BinaryStar', 'CC_quantities',\ 'CEE_parameters_from_core_abundance_thresholds',\ 'Catch_POSYDON_Warnings',\ - 'DEFAULT_MARKERS_COLORS_LEGENDS', 'MODELS', 'Pwarn',\ + 'DEFAULT_MARKERS_COLORS_LEGENDS', 'SN_MODELS', 'Pwarn',\ 'STAR_STATES_CC', 'SingleStar', 'StepSN',\ 'TF1_POOL_STABLE', '__authors__', '__builtins__',\ '__cached__', '__credits__', '__doc__', '__file__',\ @@ -38,7 +39,8 @@ def test_dir(self): 'assign_core_collapse_quantities_none',\ 'calculate_Patton20_values_at_He_depl',\ 'check_state_of_star', 'combine_TF12', 'copy', 'np',\ - 'post_process_grid', 'print_CC_quantities', 'tqdm'} + 'post_process_grid', 'print_CC_quantities', 'tqdm',\ + 'get_SN_MODEL'} totest_elements = set(dir(totest)) missing_in_test = elements - totest_elements assert len(missing_in_test) == 0, "There are missing objects in "\ @@ -109,7 +111,7 @@ def star_history(self): (1.0e+3, 3.2, 0.0, 0.2, 0.4, 0.09, 0.3, 0.79, 0.2,\ 0.0, 0.0, 0.0, -10.0, -2.0, -2.0, 0.1, 2.0, 0.0),\ (1.0e+4, 2.3, 0.0, 0.0, 0.2, 0.1, 0.6, 0.79, 0.2,\ - 0.0, 0.0, 0.0, -10.0, -3.1, -3.0, 11.0, 3.0, 2.0)],\ + 0.0, 0.0, 0.0, -10.0, -3.1, -3.0, 11.0, 13.0, 12.0)],\ dtype=[('star_age', '", +] + +import numpy as np +import pandas as pd +import pytest +from posydon.utils.posydonwarning import SFHModelWarning +from posydon.popsyn.star_formation_history import SFHBase, MadauBase +from posydon.popsyn.star_formation_history import ( + MadauDickinson14, + MadauFragos17, + Neijssel19, + Fujimoto24, + IllustrisTNG, + Chruslinska21, + Zavala21, + get_SFH_model +) + + +class TestSFHBase: + + @pytest.fixture + def ConcreteSFH(self): + """Create a concrete subclass of SFHBase for testing.""" + class ConcreteSFH(SFHBase): + def CSFRD(self, z): + return z + + def mean_metallicity(self, z): + return z + + def fSFR(self, z, metallicity_bins): + return np.ones((len(z), len(metallicity_bins)-1)) + + return ConcreteSFH + + def test_init_attributes(self, ConcreteSFH): + """Test that the initialization sets attributes correctly.""" + model_dict = { + "test_param": 42, + "Z_max": 0.03, + "another_param": "test" + } + sfh = ConcreteSFH(model_dict) + + # Check that attributes are set correctly + for key, value in model_dict.items(): + assert getattr(sfh, key) == value + + # additional SFH_model set check + assert sfh.SFH_MODEL == model_dict + + + @pytest.mark.parametrize("model_dict, error_msg", [ + # Z_max + ({"Z_max": 1.5}, "Z_max must be in absolute units! It cannot be larger than 1!"), + ({"Z_max": -0.1}, "Z_max must be in absolute units! It cannot be negative!"), + # Z_min + ({"Z_min": -0.1}, "Z_min must be in absolute units! It cannot be negative!"), + ({"Z_min": 1.2}, "Z_min must be in absolute units! It cannot be larger than 1!"), + # Z_min > Z_max + ({"Z_max": 0.1, "Z_min": 0.2}, "Z_min must be smaller than Z_max!"), + ]) + def test_validation(self, ConcreteSFH, model_dict, error_msg): + with pytest.raises(ValueError) as excinfo: + sfh = ConcreteSFH(model_dict) + assert error_msg in str(excinfo.value) + + def test_abstract_methods(self): + """Test that abstract methods must be implemented.""" + # Create incomplete subclasses that don't implement all abstract methods + class IncompleteSFH1(SFHBase): + def CSFRD(self, z): + return z + + class IncompleteSFH2(SFHBase): + def CSFRD(self, z): + return z + def mean_metallicity(self, z): + return z + + model_dict = {"Z_max": 0.03} + with pytest.raises(TypeError) as excinfo: + IncompleteSFH1(model_dict) + assert ("Can't instantiate abstract class IncompleteSFH1 " + "with abstract methods fSFR, mean_metallicity") in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + IncompleteSFH2(model_dict) + assert ("Can't instantiate abstract class IncompleteSFH2 " + "with abstract method fSFR") in str(excinfo.value) + + @pytest.mark.parametrize( + "model_dict, normalise, met_edges, expected, warning", + [ + # Simple CDF with Z_max=1, Z_min=0.0 + ({"Z_max": 1, "Z_min": 0.0}, False, np.array([0.0, 0.01, 0.02, 0.03]), + np.array([0.01, 0.01, 0.98]), None), + # No Z_min/Z_max set + ({}, False, np.array([0.0, 0.01, 0.02, 0.03]), + 0.01 * np.ones(3), None), + # Model dict warning + ({"Z_max": 0.02, "Z_min": 0.0}, False, np.array([0.0, 0.01, 0.02, 0.03]), + None, 'Z_max is smaller than the highest metallicity bin.'), + # Different model dicts + ({"Z_max": 1, "Z_min": 0.015}, False, np.array([0.3, 0.6, 0.9]), + np.array([0.585, 0.4]), None), + # With normalization + ({"Z_max": 1, "Z_min": 0.015}, True, np.array([0.3, 0.6, 0.9]), + None, None), + # Restrict upper bound + ({"Z_max": 0.95, "Z_min": 0.2}, False, np.array([0.3, 0.6, 0.9]), + np.array([0.4, 0.35]), None), + # With normalization + ({"Z_max": 0.95, "Z_min": 0.2}, True, np.array([0.3, 0.6, 0.9]), + None, None), + # Minimum in lowest bin + ({"Z_min": 0.25}, False, np.array([0.2, 0.3, 0.6, 0.9]), + np.array([0.05, 0.3, 0.3]), + 'Z_min is larger than the lowest metallicity bin.'), + # Minimum higher than minimum bin + ({"Z_min": 0.35}, False, np.array([0.2, 0.3, 0.6, 0.9]), + np.array([0.0, 0.25, 0.3]), + 'Z_min is larger than the lowest metallicity bin.'), + # Minimum in lowest bin and maximum + ({"Z_min": 0.25, "Z_max": 0.8}, False, np.array([0.2, 0.3, 0.6, 0.9]), + np.array([0.05, 0.3, 0.2]), + 'Z_max is smaller than the highest metallicity bin.'), + # Minimum higher than minimum bin, narrow range + ({"Z_min": 0.35, "Z_max": 0.4}, False, np.array([0.2, 0.3, 0.6, 0.9]), + np.array([0.0, 0.05, 0.0]), + 'Z_max is smaller than the highest metallicity bin.'), + # Minimum higher than minimum bin, medium range + ({"Z_min": 0.35, "Z_max": 0.65}, False, np.array([0.2, 0.3, 0.6, 0.9]), + np.array([0.0, 0.25, 0.05]), + 'Z_max is smaller than the highest metallicity bin.'), + ]) + def test_distribute_cdf(self, ConcreteSFH, model_dict, normalise, met_edges, expected, warning): + """Test the _distribute_cdf method with various scenarios.""" + # Create a simple CDF function + cdf_func = lambda x: x + + # Create the SFH instance + sfh = ConcreteSFH(model_dict) + sfh.normalise = normalise + + # Test execution with or without warning check + if warning is not None: + with pytest.warns(SFHModelWarning, match=warning): + result = sfh._distribute_cdf(cdf_func, met_edges) + else: + result = sfh._distribute_cdf(cdf_func, met_edges) + + # Check results + if normalise: + # For normalise=True, check sum is 1.0 + np.testing.assert_allclose(np.sum(result), 1.0) + elif expected is not None: + # For specific expected values + np.testing.assert_allclose(result, expected) + + def test_distribute_cdf_invalid(self, ConcreteSFH): + """Test the _distribute_cdf method with invalid inputs.""" + # Create a simple CDF function + cdf_func = lambda x: x + + # Create the SFH instance + model_dict = {} + sfh = ConcreteSFH(model_dict) + + # Test with invalid metallicity edges + met_edges = np.array([0.04, 0.01, 0.02]) + with pytest.raises(ValueError) as excinfo: + sfh._distribute_cdf(cdf_func, met_edges) + assert "Metallicity bins must be sorted" in str(excinfo.value) + + def test_call_method(self): + """Test the __call__ method.""" + class ConcreteSFH(SFHBase): + def CSFRD(self, z): + return z*2.0 + + # just a placeholder. Doesn't contribute + def mean_metallicity(self, z): + return z*0.01 + + def fSFR(self, z, metallicity_bins): + # Return a simple array for testing + delta = np.diff(metallicity_bins) + # normalise + delta /= delta.sum() + + out = np.zeros((len(z), len(delta))) + out[:, :] = delta + return out + + model_dict = {"Z_max": 0.03} + sfh = ConcreteSFH(model_dict) + + z = np.array([0.5, 1.0]) + met_edges = np.array([0.0, 0.02, 0.06]) + + result = sfh(z, met_edges) + + # Expected: CSFRD(z)[:, np.newaxis] * fSFR(z, met_edges) + expected = np.array([ + [1.0 * 1/3, 1.0 * 2/3], + [2.0 * 1/3, 2.0 * 2/3] + ]) + + np.testing.assert_allclose(result, expected) + + +class TestMadauBase: + """Test class for MadauBase""" + + @pytest.fixture + def e_CSFRD_params(self): + return {"a": 0.01, + "b": 2.6, + "c": 3.2, + "d": 6.2, + } + + @pytest.fixture + def ConcreteMadau(self, e_CSFRD_params): + class ConcreteMadau(MadauBase): + """Concrete subclass of MadauBase for testing""" + def __init__(self, MODEL): + super().__init__(MODEL) + self.CSFRD_params = e_CSFRD_params + return ConcreteMadau + + def test_init_requires_sigma(self, ConcreteMadau): + """Test that MadauBase requires a sigma parameter""" + model_dict = {"Z_max": 0.03} + with pytest.raises(ValueError) as excinfo: + ConcreteMadau(model_dict) + assert "sigma not given!" in str(excinfo.value) + + def test_init_sets_csfrd_params_to_none(self, ConcreteMadau, e_CSFRD_params): + """Test that CSFRD_params is not set to None initially""" + model_dict = {"sigma": 0.5} + madau = ConcreteMadau(model_dict) + assert madau.CSFRD_params is not None + assert madau.CSFRD_params["a"] == e_CSFRD_params["a"] + assert madau.CSFRD_params["b"] == e_CSFRD_params["b"] + assert madau.CSFRD_params["c"] == e_CSFRD_params["c"] + assert madau.CSFRD_params["d"] == e_CSFRD_params["d"] + + def test_std_log_metallicity_dist(self, ConcreteMadau): + """Test the std_log_metallicity_dist method with different sigma values""" + # Test Bavera+20 + model_dict = {"sigma": "Bavera+20"} + madau = ConcreteMadau(model_dict) + assert madau.std_log_metallicity_dist() == 0.5 + + # Test Neijssel+19 + model_dict = {"sigma": "Neijssel+19"} + madau = ConcreteMadau(model_dict) + assert madau.std_log_metallicity_dist() == 0.39 + + # Test float value + model_dict = {"sigma": 0.45} + madau = ConcreteMadau(model_dict) + assert madau.std_log_metallicity_dist() == 0.45 + + # Test unknown string + model_dict = {"sigma": "unknown"} + madau = ConcreteMadau(model_dict) + with pytest.raises(ValueError) as excinfo: + madau.std_log_metallicity_dist() + assert "Unknown sigma choice!" in str(excinfo.value) + + # Test integer type + model_dict = {"sigma": 1} + madau = ConcreteMadau(model_dict) + assert madau.std_log_metallicity_dist() == 1.0 + + # Test invalid type + model_dict = {"sigma": [0.5, 0.6]} + madau = ConcreteMadau(model_dict) + with pytest.raises(ValueError) as excinfo: + madau.std_log_metallicity_dist() + assert "Invalid sigma value" in str(excinfo.value) + + def test_csfrd(self, ConcreteMadau, e_CSFRD_params): + """Test the CSFRD method""" + model_dict = {"sigma": 0.5} + madau = ConcreteMadau(model_dict) + + def tmp_CSFRD(z): + return (e_CSFRD_params["a"] * ((1 + z)**e_CSFRD_params["b"]) + / (1 + ((1 + z)/e_CSFRD_params["c"])**e_CSFRD_params["d"])) + + # Test with single value + z = 0.0 + result = madau.CSFRD(z) + # a * (1+z)^b / (1 + ((1+z)/c)^d) with z=0 + expected = tmp_CSFRD(z) + np.testing.assert_allclose(result, expected) + + # Test with array of values + z_array = np.array([0.0, 1.0, 2.0]) + result = madau.CSFRD(z_array) + expected = np.array([ + tmp_CSFRD(z) for z in z_array + ]) + np.testing.assert_allclose(result, expected) + + def test_mean_metallicity(self, ConcreteMadau): + """Test the mean_metallicity method""" + from posydon.utils.constants import Zsun + + model_dict = {"sigma": 0.5, "Z_max": 0.03} + madau = ConcreteMadau(model_dict) + + # Test with single value + z = 0.0 + result = madau.mean_metallicity(z) + expected = 10**(0.153) * Zsun + np.testing.assert_allclose(result, expected) + + # Test with array of values + z_array = np.array([0.0, 1.0, 2.0]) + result = madau.mean_metallicity(z_array) + expected = 10**(0.153 - 0.074 * z_array**1.34) * Zsun + np.testing.assert_allclose(result, expected) + + def test_fsfr(self, ConcreteMadau): + """Test the fSFR method""" + model_dict = {"sigma": 0.5, "Z_max": 1} + madau = ConcreteMadau(model_dict) + + # Test with redshift array and metallicity bins + z = np.array([0.0, 1.0]) + met_bins = np.array([0.001, 0.01, 0.02, 0.03]) + + result = madau.fSFR(z, met_bins) + + # Shape check - should be (len(z), len(met_bins)-1) + assert result.shape == (2, 3) + + # THIS IS A VALIDATION TEST + from scipy.stats import norm + mean0, mean1 = (np.log10(madau.mean_metallicity(z)) + - model_dict['sigma']**2 * np.log(10) / 2) + + expected1 = np.array( + # integral from 0.001 to 0.01; Z_min = lowest bin edge + [(norm.cdf(np.log10(0.01), mean0, model_dict['sigma']) + - norm.cdf(np.log10(0.001), mean0, model_dict['sigma'])), + # integral from 0.01 to 0.02 + (norm.cdf(np.log10(0.02), mean0, model_dict['sigma']) + - norm.cdf(np.log10(0.01), mean0, model_dict['sigma'])), + # integral from 0.02 to 1 + (norm.cdf(np.log10(1), mean0, model_dict['sigma']) + - norm.cdf(np.log10(0.02), mean0, model_dict['sigma']))],) + + expected2 = np.array( + # integral from 0.001 to 0.01;Z_min = lowest bin edge + [(norm.cdf(np.log10(0.01), mean1, model_dict['sigma']) + - norm.cdf(np.log10(0.001), mean1, model_dict['sigma'])), + # integral from 0.01 to 0.02 + (norm.cdf(np.log10(0.02), mean1, model_dict['sigma']) + - norm.cdf(np.log10(0.01), mean1, model_dict['sigma'])), + # integral from 0.02 to 1 + (norm.cdf(np.log10(1), mean1, model_dict['sigma']) + - norm.cdf(np.log10(0.02), mean1, model_dict['sigma']))], + ) + expected = np.array([expected1, expected2]) + np.testing.assert_allclose(result, expected) + + # Change Z_min to a very small number to include the rest of the lowest mets + model_dict = {"sigma": 0.5, "Z_max": 0.3, "Z_min": 1e-11} + madau = ConcreteMadau(model_dict) + result = madau.fSFR(z, met_bins) + + expected1 = np.array( + [norm.cdf(np.log10(0.01), mean0, model_dict['sigma']), + (norm.cdf(np.log10(0.02), mean0, model_dict['sigma']) + - norm.cdf(np.log10(0.01), mean0, model_dict['sigma'])), + (norm.cdf(np.log10(0.3), mean0, model_dict['sigma']) + - norm.cdf(np.log10(0.02), mean0, model_dict['sigma']))],) + expected2 = np.array( + [norm.cdf(np.log10(0.01), mean1, model_dict['sigma']), + (norm.cdf(np.log10(0.02), mean1, model_dict['sigma']) + - norm.cdf(np.log10(0.01), mean1, model_dict['sigma'])), + (norm.cdf(np.log10(0.3), mean1, model_dict['sigma']) + - norm.cdf(np.log10(0.02), mean1, model_dict['sigma']))],) + expected = np.array([expected1, expected2]) + np.testing.assert_allclose(result, expected) + + # Test with normalise + model_dict = {"sigma": 0.5, "Z_max": 0.3, "Z_min": 1e-11, "normalise": True} + madau = ConcreteMadau(model_dict) + result = madau.fSFR(z, met_bins) + expected = np.ones(len(z)) + np.testing.assert_allclose(np.sum(result, axis=1), expected) + + # Test with Z_min > met_bins[1] + model_dict = {"sigma": 0.5, + "Z_max": 0.3, + "Z_min": 0.02, + "normalise": True} + madau = ConcreteMadau(model_dict) + warning_str = "Z_min is larger than the lowest metallicity bin." + with pytest.warns(SFHModelWarning, match=warning_str): + result = madau.fSFR(z, met_bins) + #result = madau.fSFR(z, met_bins) + expected = np.ones(len(z)) + np.testing.assert_allclose(np.sum(result, axis=1), expected) + +class TestIllustrisTNG: + """Tests for the IllustrisTNG SFH model with mocked data loading.""" + + @pytest.fixture + def mock_illustris_data(self): + """Create mock data for the IllustrisTNG class.""" + # Create mock data structure similar to the npz file + num_redshifts = 10 + num_metallicities = 5 + + mock_data = { + "SFR": np.linspace(0.1, 1.0, num_redshifts)[::-1], # SFR decreases with redshift + "redshifts": np.linspace(0.0, 9.0, num_redshifts)[::-1], # Redshifts from 0 to 9 + "mets": np.logspace(-4, -1, num_metallicities), # Metallicities from 1e-4 to 1e-1 + "M": np.ones((num_redshifts, num_metallicities)) # Equal mass in all bins for simplicity + } + + # Add some variation to mass distribution for testing mean_metallicity + for i in range(num_redshifts): + # Linear decrease in higher metallicities as redshift increases + scale = 1.0 - i / num_redshifts + mock_data["M"][i] = np.linspace(1.0, scale, num_metallicities) + + mock_data["M"] = np.flip(mock_data["M"], axis=0) # Reverse the mass array + return mock_data + + @pytest.fixture + def illustris_model(self, monkeypatch, mock_illustris_data): + """Create an IllustrisTNG model instance with mocked data.""" + # Create a function that returns the mock data + def mock_get_illustrisTNG_data(self, verbose=False): + return mock_illustris_data + + # Patch the _get_illustrisTNG_data method + monkeypatch.setattr(IllustrisTNG, "_get_illustrisTNG_data", mock_get_illustrisTNG_data) + + # Create and return the model + model_dict = {"Z_max": 0.3} + return IllustrisTNG(model_dict) + + def test_init_parameters(self, illustris_model, mock_illustris_data): + """Test that initialization sets the parameters correctly.""" + # Check that data was loaded correctly + np.testing.assert_array_equal(illustris_model.CSFRD_data, np.flip(mock_illustris_data["SFR"])) + np.testing.assert_array_equal(illustris_model.redshifts, np.flip(mock_illustris_data["redshifts"])) + np.testing.assert_array_equal(illustris_model.Z, mock_illustris_data["mets"]) + np.testing.assert_array_equal(illustris_model.M, np.flip(mock_illustris_data["M"], axis=0)) + + # Check that model parameters were set correctly + assert illustris_model.Z_max == 0.3 + + def test_csfrd_calculation(self, illustris_model, mock_illustris_data): + """Test the CSFRD method.""" + # Test at specific redshifts including boundary values + z_values = np.array([0.0, 4.5, 9.0]) + result = illustris_model.CSFRD(z_values) + + # Expected values come from interpolating flipped SFR data + flipped_sfr = np.flip(mock_illustris_data["SFR"]) + flipped_redshifts = np.flip(mock_illustris_data["redshifts"]) + expected = np.interp(z_values, flipped_redshifts, flipped_sfr) + + np.testing.assert_allclose(result, expected) + + def test_mean_metallicity(self, illustris_model, mock_illustris_data): + """Test the mean_metallicity method.""" + # Test at specific redshifts + z_values = np.array([0.0, 4.5, 9.0]) + result = illustris_model.mean_metallicity(z_values) + + # Calculate expected values manually + flipped_redshifts = np.flip(mock_illustris_data["redshifts"]) + flipped_masses = np.flip(mock_illustris_data["M"], axis=0) + metallicities = mock_illustris_data["mets"] + + # Calculate expected mean metallicities at each test redshift + out = np.zeros_like(flipped_redshifts) + for i, z in enumerate(out): + weights = flipped_masses[i, :] + if np.sum(weights) == 0: + out[i] = np.nan + else: + out[i] = np.average(metallicities, weights=weights) + Z_interp = np.interp(z_values, flipped_redshifts, out) + np.testing.assert_allclose(result, Z_interp) + + # Test empty mass array + illustris_model.M[0] = np.zeros_like(flipped_masses[0]) + with pytest.raises(AssertionError): + result = illustris_model.mean_metallicity(z_values) + + def test_fsfr_calculation(self, illustris_model): + """Test the fSFR method.""" + # Test with redshift array and metallicity bins + z = np.array([0.0, 4.5]) + met_bins = np.array([0.001, 0.01, 0.05, 0.1]) + + result = illustris_model.fSFR(z, met_bins) + + # Shape check - should be (len(z), len(met_bins)-1) + assert result.shape == (2, 3) + + # Test with normalise=True + illustris_model.normalise = True + result = illustris_model.fSFR(z, met_bins) + for row in result: + if np.sum(row) > 0: + np.testing.assert_allclose(np.sum(row), 1.0) + + # Test for Z_dist[i].sum = 0 + # Force the first mass array to be all zeros + illustris_model.M[0] = np.zeros_like(illustris_model.M[0]) + result = illustris_model.fSFR(z, met_bins) + np.testing.assert_allclose(result[0], np.zeros_like(result[0])) + +class TestMadauDickinson14: + """Tests for the MadauDickinson14 SFH model""" + + def test_init_parameters(self): + """Test that initialization sets the correct CSFRD parameters""" + model_dict = {"sigma": 0.5, "Z_max": 0.03} + madau = MadauDickinson14(model_dict) + + # Check that CSFRD_params were set correctly + assert madau.CSFRD_params["a"] == 0.015 + assert madau.CSFRD_params["b"] == 2.7 + assert madau.CSFRD_params["c"] == 2.9 + assert madau.CSFRD_params["d"] == 5.6 + + # Check that it inherits correctly from MadauBase + assert isinstance(madau, MadauBase) + + def test_csfrd_calculation(self): + """Test that CSFRD calculations match expected values""" + model_dict = {"sigma": 0.5, "Z_max": 0.03} + madau = MadauDickinson14(model_dict) + + # Test at specific redshifts + z_values = np.array([0.0, 1.0, 2.0, 6.0]) + result = madau.CSFRD(z_values) + + # Calculate expected values manually + p = madau.CSFRD_params + expected = (p["a"] * (1.0 + z_values) ** p["b"] + / (1.0 + ((1.0 + z_values) / p["c"]) ** p["d"])) + + np.testing.assert_allclose(result, expected) + +class TestMadauFragos17: + """Tests for the MadauFragos17 SFH model""" + + def test_init_parameters(self): + """Test that initialization sets the correct CSFRD parameters""" + model_dict = {"sigma": 0.5, "Z_max": 0.03} + madau = MadauFragos17(model_dict) + + # Check that CSFRD_params were set correctly + assert madau.CSFRD_params["a"] == 0.01 + assert madau.CSFRD_params["b"] == 2.6 + assert madau.CSFRD_params["c"] == 3.2 + assert madau.CSFRD_params["d"] == 6.2 + + # Check that it inherits correctly from MadauBase + assert isinstance(madau, MadauBase) + +class TestNeijssel19: + """Tests for the Neijssel19 SFH model""" + + def test_init_parameters(self): + """Test that initialization sets the correct CSFRD parameters""" + model_dict = {"sigma": 0.5, "Z_max": 0.03} + neijssel = Neijssel19(model_dict) + + # Check that CSFRD_params were set correctly + assert neijssel.CSFRD_params["a"] == 0.01 + assert neijssel.CSFRD_params["b"] == 2.77 + assert neijssel.CSFRD_params["c"] == 2.9 + assert neijssel.CSFRD_params["d"] == 4.7 + + # Check that it inherits correctly from MadauBase + assert isinstance(neijssel, MadauBase) + + def test_mean_metallicity(self): + """Test the overridden mean_metallicity method""" + model_dict = {"sigma": 0.5, "Z_max": 0.03} + neijssel = Neijssel19(model_dict) + + # Test at specific redshifts + z_values = np.array([0.0, 1.0, 2.0, 6.0]) + result = neijssel.mean_metallicity(z_values) + + # Calculate expected values based on Neijssel19's formula + expected = 0.035 * 10 ** (-0.23 * z_values) + + np.testing.assert_allclose(result, expected) + + def test_fsfr_with_lognormal(self): + """Test the overridden fSFR method which uses a ln-normal distribution""" + model_dict = {"sigma": 0.5, "Z_max": 0.3} + neijssel = Neijssel19(model_dict) + + # Test with redshift array and metallicity bins + z = np.array([0.0, 1.0]) + met_bins = np.array([0.001, 0.01, 0.02, 0.03]) + + result = neijssel.fSFR(z, met_bins) + + # Shape check - should be (len(z), len(met_bins)-1) + assert result.shape == (2, 3) + + # Test normalization with normalise=True + model_dict = {"sigma": 0.5, "Z_max": 0.3, "normalise": True} + neijssel = Neijssel19(model_dict) + result = neijssel.fSFR(z, met_bins) + expected = np.ones(len(z)) + np.testing.assert_allclose(np.sum(result, axis=1), expected) + +class TestFujimoto24: + """Tests for the Fujimoto24 SFH model""" + + def test_init_parameters(self): + """Test that initialization sets the correct CSFRD parameters""" + model_dict = {"sigma": 0.5, "Z_max": 0.03} + fujimoto = Fujimoto24(model_dict) + + # Check that CSFRD_params were set correctly + assert fujimoto.CSFRD_params["a"] == 0.010 + assert fujimoto.CSFRD_params["b"] == 2.8 + assert fujimoto.CSFRD_params["c"] == 3.3 + assert fujimoto.CSFRD_params["d"] == 6.6 + + # Check that it inherits correctly from MadauBase + assert isinstance(fujimoto, MadauBase) + +class TestChruslinska21: + """Tests for the Chruslinska21 SFH model with mocked data loading.""" + + @pytest.fixture + def mock_chruslinska_data(self, monkeypatch): + """Create mock data for the Chruslinska21 class.""" + # Create mock data for FOH bins + FOH_bins = np.linspace(5.3, 9.7, 200) + dFOH = FOH_bins[1] - FOH_bins[0] + redshifts = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) + delta_T = np.array([1e9, 1e9, 1e9, 1e9, 1e9, 1e9]) # Time bin widths + + # Mock SFR data - decreasing with redshift, varying with metallicity + SFR_data = np.zeros((len(redshifts), len(FOH_bins))) + for i in range(len(redshifts)): + # Simple pattern: peak at middle metallicity, decreasing with redshift + peak_idx = len(FOH_bins) // 2 + SFR_data[i] = np.exp(-0.5 * ((np.arange(len(FOH_bins)) - peak_idx) / 20)**2) + SFR_data[i] *= np.exp(-redshifts[i] / 2) # Decrease with redshift + + # Create method that returns tuple of time, redshift, deltaT + def mock_load_redshift_data(self, verbose=False): + time = np.array([1e9, 2e9, 3e9, 4e9, 5e9, 6e9]) # Fake times + return time, redshifts, delta_T + + # Create method that returns the mock SFR data + def mock_load_raw_data(self): + return SFR_data * 1e6 * delta_T[:, np.newaxis] + + # Patch the methods + monkeypatch.setattr(Chruslinska21, "_load_redshift_data", mock_load_redshift_data) + monkeypatch.setattr(Chruslinska21, "_load_raw_data", mock_load_raw_data) + + return { + "FOH_bins": FOH_bins, + "dFOH": dFOH, + "redshifts": redshifts, + "SFR_data": SFR_data + } + + @pytest.fixture + def chruslinska_model(self, mock_chruslinska_data): + """Create a Chruslinska21 model instance with mocked data.""" + model_dict = { + "sub_model": "test_model", + "Z_solar_scaling": "Asplund09", + "Z_max": 0.03, + "select_one_met": False + } + return Chruslinska21(model_dict) + + def test_init_parameters(self): + """Test that initialization validates required parameters.""" + # Test missing sub_model + with pytest.raises(ValueError) as excinfo: + Chruslinska21({"Z_solar_scaling": "Asplund09", "Z_max": 0.03, "select_one_met": False}) + assert "Sub-model not given!" in str(excinfo.value) + + # Test missing Z_solar_scaling + with pytest.raises(ValueError) as excinfo: + Chruslinska21({"sub_model": "test", "Z_max": 0.03, "select_one_met": False}) + assert "Z_solar_scaling not given!" in str(excinfo.value) + + def test_foh_to_z_conversion(self, chruslinska_model): + """Test the _FOH_to_Z method for all scaling options.""" + # Test Asplund09 scaling + FOH_test = np.array([7.0, 8.0, 8.69, 9.0]) + result = chruslinska_model._FOH_to_Z(FOH_test) + + # Expected: 10^(log10(0.0134) + FOH - 8.69) + expected = 10**(np.log10(0.0134) + FOH_test - 8.69) + np.testing.assert_allclose(result, expected) + + # Test other scaling options + model_dict = { + "sub_model": "test_model", + "Z_solar_scaling": "AndersGrevesse89", + "Z_max": 0.03, + "select_one_met": False + } + model = Chruslinska21(model_dict) + result = model._FOH_to_Z(FOH_test) + expected = 10**(np.log10(0.017) + FOH_test - 8.83) + np.testing.assert_allclose(result, expected) + + # Test GrevesseSauval98 scaling + model_dict["Z_solar_scaling"] = "GrevesseSauval98" + model = Chruslinska21(model_dict) + result = model._FOH_to_Z(FOH_test) + expected = 10**(np.log10(0.0201) + FOH_test - 8.93) + np.testing.assert_allclose(result, expected) + + # Test Villante14 scaling + model_dict["Z_solar_scaling"] = "Villante14" + model = Chruslinska21(model_dict) + result = model._FOH_to_Z(FOH_test) + expected = 10**(np.log10(0.019) + FOH_test - 8.85) + np.testing.assert_allclose(result, expected) + + # Test invalid scaling + model_dict["Z_solar_scaling"] = "InvalidScaling" + with pytest.raises(ValueError) as excinfo: + model = Chruslinska21(model_dict) + expected_str = ("Invalid Z_solar_scaling 'InvalidScaling'. " + "Valid options: ['Asplund09', 'AndersGrevesse89', " + "'GrevesseSauval98', 'Villante14']") + assert expected_str in str(excinfo.value) + + def test_mean_metallicity(self, chruslinska_model, mock_chruslinska_data): + """Test the mean_metallicity method.""" + # Test at specific redshifts + z_values = np.array([0.0, 2.0, 4.0]) + result = chruslinska_model.mean_metallicity(z_values) + print(result) + # Should be an array of the same length as z_values + assert len(result) == len(z_values) + # Should be the same value at all redshifts + assert np.isclose(result[0], result[1]) + assert np.isclose(result[0], result[2]) + assert np.isclose(result[0], 0.0014903210118641882) + + # Test with SFR_data == 0 + chruslinska_model.SFR_data = np.zeros_like(chruslinska_model.SFR_data) + with pytest.raises(AssertionError): + result = chruslinska_model.mean_metallicity(z_values) + + def test_csfrd_calculation(self, chruslinska_model, mock_chruslinska_data): + """Test the CSFRD method.""" + # Test at specific redshifts + z_values = np.array([0.0, 2.0, 4.0]) + result = chruslinska_model.CSFRD(z_values) + + # Should be an array of the same length as z_values + assert len(result) == len(z_values) + + # Should decrease with increasing redshift in the test case + assert result[0] > result[1] > result[2] + + def test_fsfr_calculation(self, chruslinska_model): + """Test the fSFR method.""" + # Test with redshift array and metallicity bins + z = np.array([0.0, 2.0]) + met_bins = np.array([0.001, 0.01, 0.02, 0.03]) + + result = chruslinska_model.fSFR(z, met_bins) + # Shape check - should be (len(z), len(met_bins)-1) + assert result.shape == (2, 3) + + # Test with normalization + chruslinska_model.normalise = True + result = chruslinska_model.fSFR(z, met_bins) + for row in result: + if np.sum(row) > 0: + np.testing.assert_allclose(np.sum(row), 1.0) + + # Test with Z_dist[i].sum = 0 + # Force the first mass array to be all zeros + chruslinska_model.SFR_data[0] = np.zeros_like(chruslinska_model.SFR_data[0]) + result = chruslinska_model.fSFR(z, met_bins) + np.testing.assert_allclose(result[0], np.zeros_like(result[0])) + + + +class TestZavala21: + """Tests for the Zavala21 SFH model with mocked data loading.""" + + @pytest.fixture + def mock_zavala_data(self, monkeypatch): + """Create mock data for the Zavala21 class.""" + # Create mock data - simple decreasing function with redshift + redshifts = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + SFRD_min = 0.1 * np.exp(-redshifts / 3.0) # Simple declining function + SFRD_max = 0.2 * np.exp(-redshifts / 3.0) # Double the min values + + def mock_read_csv(self, **kwargs): + return pd.DataFrame(data={ + "redshift": redshifts, + "SFRD_min": SFRD_min, + "SFRD_max": SFRD_max + }) + + monkeypatch.setattr(pd, "read_csv", mock_read_csv) + + def test_init_parameters(self, mock_zavala_data): + """Test that initialization validates and sets parameters correctly.""" + # Test missing sub_model + with pytest.raises(ValueError) as excinfo: + Zavala21({"Z_max": 0.03, "sigma": 0.5}) + assert "Sub-model not given!" in str(excinfo.value) + + # Test valid initialization with min model + model_dict = {"sub_model": "min", "Z_max": 0.03, "sigma": 0.5} + zavala_min = Zavala21(model_dict) + assert zavala_min.sub_model == "min" + assert zavala_min.Z_max == 0.03 + assert zavala_min.sigma == 0.5 + + # Test valid initialization with max model + model_dict = {"sub_model": "max", "Z_max": 0.03, "sigma": 0.5} + zavala_max = Zavala21(model_dict) + assert zavala_max.sub_model == "max" + + # Test invalid sub_model + model_dict = {"sub_model": "invalid", "Z_max": 0.03, "sigma": 0.5} + with pytest.raises(ValueError) as excinfo: + zavala_invalid = Zavala21(model_dict) + assert "Invalid sub-model!" in str(excinfo.value) + + def test_csfrd_min_model(self, mock_zavala_data): + """Test the CSFRD method with min sub-model.""" + model_dict = {"sub_model": "min", "Z_max": 0.03, "sigma": 0.5} + zavala = Zavala21(model_dict) + + # Test at specific redshifts + z_values = np.array([0.0, 2.0, 4.0, 6.0]) + result = zavala.CSFRD(z_values) + + # Expected values come from interpolating the mock data + expected = 0.1 * np.exp(-z_values / 3.0) + np.testing.assert_allclose(result, expected) + + def test_csfrd_max_model(self, mock_zavala_data): + """Test the CSFRD method with max sub-model.""" + model_dict = {"sub_model": "max", "Z_max": 0.03, "sigma": 0.5} + zavala = Zavala21(model_dict) + + # Test at specific redshifts + z_values = np.array([0.0, 2.0, 4.0, 6.0]) + result = zavala.CSFRD(z_values) + + # Expected values come from interpolating the mock data + expected = 0.2 * np.exp(-z_values / 3.0) + np.testing.assert_allclose(result, expected) + + def test_fsfr_calculation(self, mock_zavala_data): + """Test the fSFR method which is inherited from MadauBase.""" + model_dict = {"sub_model": "min", "Z_max": 0.03, "sigma": 0.5} + zavala = Zavala21(model_dict) + + # Test with redshift array and metallicity bins + z = np.array([0.0, 2.0]) + met_bins = np.array([0.001, 0.01, 0.02, 0.03]) + + result = zavala.fSFR(z, met_bins) + + # Shape check - should be (len(z), len(met_bins)-1) + assert result.shape == (2, 3) + + # Test normalization + zavala.normalise = True + result = zavala.fSFR(z, met_bins) + for row in result: + np.testing.assert_allclose(np.sum(row), 1.0) + +class TestGetSFHModel: + """Tests for the get_SFH_model function.""" + + def test_returns_correct_instance(self): + """Test that get_SFH_model returns the correct instance for each model.""" + # Test for MadauDickinson14 + model_dict = {"SFR": "Madau+Dickinson14", "sigma": 0.5, "Z_max": 0.03} + model = get_SFH_model(model_dict) + assert isinstance(model, MadauDickinson14) + + # Test for MadauFragos17 + model_dict = {"SFR": "Madau+Fragos17", "sigma": 0.5, "Z_max": 0.03} + model = get_SFH_model(model_dict) + assert isinstance(model, MadauFragos17) + + # Test for Neijssel19 + model_dict = {"SFR": "Neijssel+19", "sigma": 0.5, "Z_max": 0.03} + model = get_SFH_model(model_dict) + assert isinstance(model, Neijssel19) + + # Test for Fujimoto24 + model_dict = {"SFR": "Fujimoto+24", "sigma": 0.5, "Z_max": 0.03} + model = get_SFH_model(model_dict) + assert isinstance(model, Fujimoto24) + + def test_illustris_tng_model(self, monkeypatch): + """Test that get_SFH_model returns IllustrisTNG instance.""" + # Mock the data loading method + def mock_get_data(self, verbose=False): + # Return minimal mock data structure + return { + "SFR": np.array([0.1, 0.2, 0.3]), + "redshifts": np.array([0.0, 1.0, 2.0]), + "mets": np.array([0.001, 0.01, 0.02]), + "M": np.ones((3, 3)) + } + + # Patch the data loading method + monkeypatch.setattr(IllustrisTNG, "_get_illustrisTNG_data", mock_get_data) + + # Test the model creation + model_dict = {"SFR": "IllustrisTNG", "Z_max": 0.03} + model = get_SFH_model(model_dict) + assert isinstance(model, IllustrisTNG) + + def test_chruslinska_model(self, monkeypatch): + """Test that get_SFH_model returns Chruslinska21 instance.""" + # Mock the methods needed for initialization + def mock_load_data(self): + # Minimal setup to make initialization work + self.FOH_bins = np.linspace(5.3, 9.7, 10) + self.dFOH = self.FOH_bins[1] - self.FOH_bins[0] + self.Z = np.array([0.001, 0.01, 0.02]) + self.redshifts = np.array([0.0, 1.0, 2.0]) + self.SFR_data = np.ones((3, 10)) + + def mock_load_redshift(self, verbose=False): + # Return mock time, redshift, deltaT + return (np.array([1e9, 2e9, 3e9]), + np.array([0.0, 1.0, 2.0]), + np.array([1e9, 1e9, 1e9])) + + def mock_load_raw(self): + # Return mock data matrix + return np.ones((3, 10)) * 1e6 + + # Patch the methods + monkeypatch.setattr(Chruslinska21, "_load_chruslinska_data", mock_load_data) + monkeypatch.setattr(Chruslinska21, "_load_redshift_data", mock_load_redshift) + monkeypatch.setattr(Chruslinska21, "_load_raw_data", mock_load_raw) + + # Test the model creation + model_dict = { + "SFR": "Chruslinska+21", + "sub_model": "test", + "Z_solar_scaling": "Asplund09", + "Z_max": 0.03 + } + model = get_SFH_model(model_dict) + assert isinstance(model, Chruslinska21) + + def test_zavala_model(self, monkeypatch): + """Test that get_SFH_model returns Zavala21 instance.""" + # Mock the data loading method + def mock_load_data(self): + # Set required attributes directly + self.redshifts = np.array([0.0, 1.0, 2.0]) + if self.sub_model == "min": + self.SFR_data = np.array([0.1, 0.08, 0.06]) + else: + self.SFR_data = np.array([0.2, 0.16, 0.12]) + + # Patch the data loading method + monkeypatch.setattr(Zavala21, "_load_zavala_data", mock_load_data) + + # Test for min model + model_dict = { + "SFR": "Zavala+21", + "sub_model": "min", + "sigma": 0.5, + "Z_max": 0.03 + } + model = get_SFH_model(model_dict) + assert isinstance(model, Zavala21) + assert model.sub_model == "min" + + # Test for max model + model_dict = { + "SFR": "Zavala+21", + "sub_model": "max", + "sigma": 0.5, + "Z_max": 0.03 + } + model = get_SFH_model(model_dict) + assert isinstance(model, Zavala21) + assert model.sub_model == "max" + + def test_invalid_model(self): + """Test that get_SFH_model raises an error for an invalid model.""" + + model_dict = {"SFR": "InvalidModel"} + with pytest.raises(ValueError) as excinfo: + model = get_SFH_model(model_dict) + assert "Invalid SFR!" in str(excinfo.value) + +class TestSFR_per_met_at_z: + """Tests for SFR_per_met_at_z function.""" + + def test_SFR_per_met_at_z(self, monkeypatch): + """Test that SFR_per_met_at_z correctly calls the model.""" + # Create a mock model result + expected_result = np.array([[0.1, 0.2], [0.3, 0.4]]) + + # Mock SFH model class + class MockSFH: + def __call__(self, z, met_bins): + return expected_result + + mock_model = MockSFH() + + # Mock the get_SFH_model function to return our mock model + def mock_get_sfh_model(MODEL): + assert MODEL["SFR"] == "TestModel" # Verify correct model is requested + assert MODEL["param"] == "value" # Verify parameters are passed + return mock_model + + # Patch the function + monkeypatch.setattr( + "posydon.popsyn.star_formation_history.get_SFH_model", + mock_get_sfh_model + ) + + # Test the function + from posydon.popsyn.star_formation_history import SFR_per_met_at_z + + z = np.array([0.0, 1.0]) + met_bins = np.array([0.001, 0.01, 0.02]) + model_dict = {"SFR": "TestModel", "param": "value"} + + result = SFR_per_met_at_z(z, met_bins, model_dict) + + # Verify the result + np.testing.assert_array_equal(result, expected_result) diff --git a/posydon/unit_tests/utils/test_common_functions.py b/posydon/unit_tests/utils/test_common_functions.py index e76c6c31f3..094d5099fb 100644 --- a/posydon/unit_tests/utils/test_common_functions.py +++ b/posydon/unit_tests/utils/test_common_functions.py @@ -107,9 +107,10 @@ def test_dir(self): 'period_change_stable_MT', 'period_evol_wind_loss',\ 'profile_recomb_energy', 'quad',\ 'read_histogram_from_file', 'rejection_sampler',\ - 'roche_lobe_radius', 'rotate', 'rzams',\ - 'separation_evol_wind_loss', 'set_binary_to_failed',\ - 'spin_stable_mass_transfer', 'stefan_boltzmann_law'} + 'roche_lobe_radius', 'check_for_RLO', 'rotate',\ + 'rzams', 'separation_evol_wind_loss',\ + 'set_binary_to_failed', 'spin_stable_mass_transfer',\ + 'stefan_boltzmann_law'} totest_elements = set(dir(totest)) missing_in_test = elements - totest_elements assert len(missing_in_test) == 0, "There are missing objects in "\ @@ -191,6 +192,9 @@ def test_instance_rzams(self): def test_instance_roche_lobe_radius(self): assert isroutine(totest.roche_lobe_radius) + def test_instance_check_for_RLO(self): + assert isroutine(totest.check_for_RLO) + def test_instance_orbital_separation_from_period(self): assert isroutine(totest.orbital_separation_from_period) @@ -556,6 +560,24 @@ def test_roche_lobe_radius(self, capsys): assert totest.roche_lobe_radius(m, 1.0)\ + totest.roche_lobe_radius(1.0, m) < 1.0 + def test_check_for_RLO(self): + # missing argument + with raises(TypeError, match="missing 5 required positional "\ + +"arguments: 'm1', 'r1', 'm2', 'r2', "\ + +"and 'separation'"): + totest.check_for_RLO() + + # examples + tests = [(1.0, 1.0, 1.0, 1.0, 1.0, 1e-3, True), + (2.0, 1.0, 1.0, 1.0, 2.5, 1e-3, True), + (1.0, 1.0, 2.0, 1.0, 2.5, 1e-3, True), + (1.0, 1.0, 1.0, 1.0, 4.0, 1e-3, False)] + + for (m1, r1, m2, r2, separation, tolerance, RLO) in tests: + assert totest.check_for_RLO(m1, r1, m2, r2, separation) == RLO + assert totest.check_for_RLO(m1, r1, m2, r2, separation, tolerance)\ + == RLO + def test_orbital_separation_from_period(self): # missing argument with raises(TypeError, match="missing 3 required positional "\ diff --git a/posydon/unit_tests/utils/test_posydonwarning.py b/posydon/unit_tests/utils/test_posydonwarning.py index 3fc65c5f43..0641a15206 100644 --- a/posydon/unit_tests/utils/test_posydonwarning.py +++ b/posydon/unit_tests/utils/test_posydonwarning.py @@ -27,8 +27,9 @@ def test_dir(self): 'ClassificationWarning', 'EvolutionWarning',\ 'InappropriateValueWarning', 'IncompletenessWarning',\ 'InterpolationWarning', 'MissingFilesWarning',\ - 'NoPOSYDONWarnings', 'OverwriteWarning', 'POSYDONWarning',\ - 'Pwarn', 'ReplaceValueWarning', 'SetPOSYDONWarnings',\ + 'NoPOSYDONWarnings', 'OverwriteWarning', 'SFHModelWarning',\ + 'POSYDONWarning','Pwarn', 'ReplaceValueWarning',\ + 'SetPOSYDONWarnings',\ 'UnsupportedModelWarning', '_CAUGHT_POSYDON_WARNINGS',\ '_Caught_POSYDON_Warnings', '_POSYDONWarning_subclasses',\ '_POSYDON_WARNINGS_REGISTRY', '__authors__',\ @@ -101,6 +102,10 @@ def test_instance_UnsupportedModelWarning(self): assert isclass(totest.UnsupportedModelWarning) assert issubclass(totest.UnsupportedModelWarning,\ totest.POSYDONWarning) + + def test_instance_SFHModelWarning(self): + assert isclass(totest.SFHModelWarning) + assert issubclass(totest.SFHModelWarning, totest.POSYDONWarning) def test_instance_POSYDONWarning_subclasses(self): assert isinstance(totest._POSYDONWarning_subclasses, (dict)) diff --git a/posydon/utils/common_functions.py b/posydon/utils/common_functions.py index 92e2503916..33c275be18 100644 --- a/posydon/utils/common_functions.py +++ b/posydon/utils/common_functions.py @@ -267,6 +267,41 @@ def roche_lobe_radius(m1, m2, a_orb=1): ) return RL +def check_for_RLO(m1, r1, m2, r2, separation, tolerance=1e-8): + """Check if either star in a binary is overfilling its Roche lobe. + + It uses roche_lobe_radius and the binary separation to determine if either + star is overfilling their Roche lobe. + + Parameters + ---------- + m1 : float + Mass of star 1 (in Msun) + r1 : float + Radius of star 1 (in Rsun) + m2 : float + Mass of star 2 (in Msun) + r2 : float + Radius of star 2 (in Rsun) + separation : float + Orbital separation (in Rsun) + tolerance : float + The tolerance to count Roche-lobe filling as Roche-lobe overflow (in Rsun). + + Returns + ------- + RLO: bool + Whether either star in the binary is overfilling its Roche Lobe. + """ + # First, calculate the Roche radii for each star + RL1 = roche_lobe_radius(m1, m2, separation) + RL2 = roche_lobe_radius(m2, m1, separation) + + # Now check for Roche-lobe overflow + if ((r1 - RL1) < tolerance and (r2 - RL2) < tolerance): + return False + else: + return True def orbital_separation_from_period(period_days, m1_solar, m2_solar): """Apply the Third Kepler law. diff --git a/posydon/utils/posydonwarning.py b/posydon/utils/posydonwarning.py index 738b4c86a2..78b347bad7 100644 --- a/posydon/utils/posydonwarning.py +++ b/posydon/utils/posydonwarning.py @@ -102,6 +102,11 @@ class UnsupportedModelWarning(POSYDONWarning): """Warnings related to selecting a model that is not supported.""" def __init__(self, message=''): super().__init__(message) + +class SFHModelWarning(POSYDONWarning): + """Warnings related to the SFH model.""" + def __init__(self, message=''): + super().__init__(message) # All POSYDON warnings subclasses should be defined beforehand diff --git a/posydon/visualization/interpolation.py b/posydon/visualization/interpolation.py index b402877e51..7c97caeb19 100644 --- a/posydon/visualization/interpolation.py +++ b/posydon/visualization/interpolation.py @@ -81,8 +81,8 @@ def __compute_errs(self): matrix = {} - # catch cases where nothing is cl,assified, e.g. S2_MODELXX_SN_type - # when S2 is a compact object + # catch cases where nothing is classified, e.g. + # S2_SN_MODEL_v2_XX_SN_type when S2 is a compact object if len(classes) == 1 and classes[0] == 'None': matrix['None'] = 1. else: @@ -136,7 +136,7 @@ def __find_labels(self, key): if key in interp.classifiers.keys(): # if classifier does not exist assign 'None' label - # this happens, e.g. for S2_MODELXX_SN_type, when S2 is + # this happens, e.g. for S2_SN_MODEL_v2_XX_SN_type, when S2 is # a compac object if interp.classifiers[key] is None: labels = ['None'] diff --git a/posydon/visualization/plot_defaults.py b/posydon/visualization/plot_defaults.py index 0e50e4526e..98a68e2485 100644 --- a/posydon/visualization/plot_defaults.py +++ b/posydon/visualization/plot_defaults.py @@ -20,6 +20,7 @@ from matplotlib import rcParams import shutil +from posydon.grids.SN_MODELS import SN_MODELS PLOT_PROPERTIES = { 'show_fig': False, @@ -1062,22 +1063,22 @@ def add_flag_to_MARKERS_COLORS_LEGENDS(MARKERS_COLORS_LEGENDS, flag): 'state' : [r'Remnant state', r'Remnant state'], } -# add core collapse MODEL variables -for i in range(1, 11): - DEFAULT_LABELS[f'MODEL{i:02d}_mass'] = [r'$M_\mathrm{CO} \, [M_\odot]$', +# add core collapse model variables +for SN_MODEL_NAME in SN_MODELS.keys(): + DEFAULT_LABELS[f'{SN_MODEL_NAME}_mass'] = [r'$M_\mathrm{CO} \, [M_\odot]$', r'$\log_{10}(M_\mathrm{CO} / M_\odot)$'] - DEFAULT_LABELS[f'MODEL{i:02d}_spin'] = [r'$\chi_\mathrm{CO}$', r'$\log_{10}(\chi_\mathrm{CO})$'] - DEFAULT_LABELS[f'MODEL{i:02d}_m_disk_accreted'] = [r'$M_\mathrm{disk, acc} \, [M_\odot]$', + DEFAULT_LABELS[f'{SN_MODEL_NAME}_spin'] = [r'$\chi_\mathrm{CO}$', r'$\log_{10}(\chi_\mathrm{CO})$'] + DEFAULT_LABELS[f'{SN_MODEL_NAME}_m_disk_accreted'] = [r'$M_\mathrm{disk, acc} \, [M_\odot]$', r'$\log_{10}(M_\mathrm{disk, acc} / M_\odot)$'] - DEFAULT_LABELS[f'MODEL{i:02d}_m_disk_radiated'] = [r'$M_\mathrm{disk, rad} \, [M_\odot]$', + DEFAULT_LABELS[f'{SN_MODEL_NAME}_m_disk_radiated'] = [r'$M_\mathrm{disk, rad} \, [M_\odot]$', r'$\log_{10}(M_\mathrm{disk, rad} / M_\odot)$'] - DEFAULT_LABELS[f'MODEL{i:02d}_M4'] = [r'$M_4 [= m/M_\odot]_{s=4}$', + DEFAULT_LABELS[f'{SN_MODEL_NAME}_M4'] = [r'$M_4 [= m/M_\odot]_{s=4}$', r'$\log_{10}(M_4)$'] - DEFAULT_LABELS[f'MODEL{i:02d}_mu4'] = [r'$\mu_4 \, [(dm/M_\odot)/(dr/1000\mathrm{km/s})]_{s=4}$', + DEFAULT_LABELS[f'{SN_MODEL_NAME}_mu4'] = [r'$\mu_4 \, [(dm/M_\odot)/(dr/1000\mathrm{km/s})]_{s=4}$', r'$\log_{10}(\mu_4)$'] - DEFAULT_LABELS[f'MODEL{i:02d}_h1_mass_ej'] = [r'$M_\mathrm{H,ej} \, [M_\odot]$', + DEFAULT_LABELS[f'{SN_MODEL_NAME}_h1_mass_ej'] = [r'$M_\mathrm{H,ej} \, [M_\odot]$', r'$\log_{10}(M_\mathrm{H,ej} / M_\odot)$'] - DEFAULT_LABELS[f'MODEL{i:02d}_he4_mass_ej'] = [r'$M_\mathrm{He,ej} \, [M_\odot]$', + DEFAULT_LABELS[f'{SN_MODEL_NAME}_he4_mass_ej'] = [r'$M_\mathrm{He,ej} \, [M_\odot]$', r'$\log_{10}(M_\mathrm{He,ej} / M_\odot)$'] @@ -1128,40 +1129,40 @@ def add_flag_to_MARKERS_COLORS_LEGENDS(MARKERS_COLORS_LEGENDS, flag): 'zmax' : -1 }, # SN stuff - 'S1_MODEL_DEFAULT_CO_type' : { + 'S1_SN_MODEL_DEFAULT_CO_type' : { 'zvar' : None, - 'term_flag' : 'S1_MODEL01_CO_type' + 'term_flag' : 'S1_' + list(SN_MODELS.keys())[0] + '_CO_type' }, - 'S1_MODEL_DEFAULT_SN_type' : { + 'S1_SN_MODEL_DEFAULT_SN_type' : { 'zvar' : None, - 'term_flag' : 'S1_MODEL01_SN_type' + 'term_flag' : 'S1_' + list(SN_MODELS.keys())[0] + '_SN_type' }, - 'S1_MODEL_DEFAULT_mass' : { + 'S1_SN_MODEL_DEFAULT_mass' : { 'zlog' : True, 'zmin' : 0., 'zmax' : 2. }, - 'S1_MODEL_DEFAULT_spin' : { + 'S1_SN_MODEL_DEFAULT_spin' : { 'zmin' : 0., 'zmax' : 1. }, - 'S1_MODEL_DEFAULT_m_disk_radiated' : { + 'S1_SN_MODEL_DEFAULT_m_disk_radiated' : { 'zmin' : 0., 'zmax' : 3. }, - 'S1_MODEL_DEFAULT_M4' : { + 'S1_SN_MODEL_DEFAULT_M4' : { 'zmin' : 1., 'zmax' : 4. }, - 'S1_MODEL_DEFAULT_mu4' : { + 'S1_SN_MODEL_DEFAULT_mu4' : { 'zmin' : 0.0, 'zmax' : .5 }, - 'S1_MODEL_DEFAULT_h1_mass_ej' : { + 'S1_SN_MODEL_DEFAULT_h1_mass_ej' : { 'zmin' : 0., 'zmax' : 20 }, - 'S1_MODEL_DEFAULT_he4_mass_ej' : { + 'S1_SN_MODEL_DEFAULT_he4_mass_ej' : { 'zmin' : 0., 'zmax' : 20 },