diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f629d32..7d0c476 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,14 @@ on: branches: - 'main' - '*.*' + - '!*backport*' tags: - 'v*' + - '!*dev*' + - '!*pre*' + - '!*post*' pull_request: + # Allow manual runs through the web UI workflow_dispatch: jobs: @@ -30,10 +35,10 @@ jobs: with: toxdeps: tox-pypi-filter envs: | - - windows: py310 + - linux: py310 libraries: - choco: - - boost-msvc-14.2 + apt: + - libboost-all-dev - macos: py311 libraries: brew: @@ -41,6 +46,33 @@ jobs: secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + test_windows: + # Do not run Windows on merges to main or on PRs (unless they have + # the 'Test on Windows' label) because installing boost on Windows takes + # a really long time. This will always be run on tags or on merges + # to release branches. + if: | + ( + github.event_name != 'pull_request' && ( + github.ref_name != 'main' || + github.event_name == 'workflow_dispatch' + ) + ) || ( + github.event_name == 'pull_request' && + contains(github.event.pull_request.labels.*.name, 'Test on Windows') + ) + needs: [core] + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 + with: + toxdeps: tox-pypi-filter + envs: | + - windows: py312 + libraries: + choco: + - boost-msvc-14.2 + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + docs: needs: [tests] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 @@ -68,7 +100,7 @@ jobs: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'Run publish') ) - needs: [tests] + needs: [tests, test_windows] uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish.yml@main with: test_extras: 'test' diff --git a/.readthedocs.yml b/.readthedocs.yml index 5e96f77..526c69c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,30 +1,28 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required version: 2 -# Set the version of Python and other tools you might need build: os: ubuntu-lts-latest tools: python: "mambaforge-latest" + jobs: + post_checkout: + - git fetch --unshallow || true + pre_install: + - git update-index --assume-unchanged .rtd-environment.yml docs/conf.py apt_packages: - libboost-all-dev -# Build documentation in the docs/ directory with Sphinx sphinx: + builder: html configuration: docs/conf.py + fail_on_warning: false -# Using conda to get a working version of graphviz conda: environment: .rtd-environment.yml -# Optionally build your docs in additional formats such as PDF and ePub -formats: [] +formats: +- htmlzip -# Optionally set the version of Python and requirements required to build your docs python: install: - method: pip diff --git a/.rtd-environment.yml b/.rtd-environment.yml index 4a69ec1..9697cdb 100644 --- a/.rtd-environment.yml +++ b/.rtd-environment.yml @@ -2,6 +2,6 @@ name: rtd_ebtelplusplus channels: - conda-forge dependencies: - - python=3.11 + - python=3.12 - pip - graphviz!=2.42.*,!=2.43.* diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..003832f --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,49 @@ +{ + "description": "ebtelplusplus is a Python package wrapping a C++ implementation of the enthalpy-based thermal evolution of loops (EBTEL) model for efficient hydrodynamics of dynamically-heated solar coronal loops.", + "license": "GPL v3", + "title": "ebtelplusplus", + "upload_type": "software", + "access_right": "open", + "creators": [ + { + "name": "Will Barnes", + "orcid": "0000-0001-9642-6089", + "affiliation": "Department of Physics, American University / NASA Goddard Space Flight Center" + }, + { + "name": "James Klimchuk", + "orcid": "0000-0003-2255-0305", + "affiliation": "NASA Goddard Space Flight Center" + }, + { + "name": "Peter Cargill", + "orcid": "", + "affiliation": "University of St Andrews / Imperial College, London" + }, + { + "name": "Stephen Bradshaw", + "orcid": "0000-0002-3300-6041", + "affiliation": "Department of Physics and Astronomy, Rice University" + }, + { + "name": "Jeffrey Reep", + "orcid": "0000-0003-4739-1152", + "affiliation": "University of Hawaii at Manoa" + }, + { + "name": "Samuel Schonfeld", + "orcid": "0000-0002-5476-2794", + "affiliation": "Air Force Research Laboratory" + }, + { + "name": "Nabil Freij", + "orcid": "0000-0002-6253-082X", + "affiliation": "Lockheed Martin Solar and Astrophysics Laboratory / Bay Area Environmental Research Institute" + }, + { + "name": "Rowan Collazzo", + "orcid": "", + "affiliation": "Department of Physics, American University" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 50556ab..0b758ef 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI Status](https://github.com/rice-solar-physics/ebtelplusplus/actions/workflows/ci.yml/badge.svg)](https://github.com/rice-solar-physics/ebtelPlusPlus/actions/workflows/ci.yml) [![Documentation Status](https://readthedocs.org/projects/ebtelplusplus/badge/?version=latest)](https://ebtelplusplus.readthedocs.io/en/latest/?badge=latest) -[![codecov](https://codecov.io/gh/rice-solar-physics/ebtelplusplus/graph/badge.svg?token=8G5H9T5AAH)](https://codecov.io/gh/rice-solar-physics/ebtelplusplus) +[![codecov](https://codecov.io/gh/rice-solar-physics/ebtelplusplus/branch/main/graph/badge.svg?token=8G5H9T5AAH)](https://codecov.io/gh/rice-solar-physics/ebtelplusplus) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.12675386.svg)](https://doi.org/10.5281/zenodo.12675386) `ebtelplusplus` is an implementation of the enthalpy-based thermal evolution of loops (EBTEL) model for doing diff --git a/docs/conf.py b/docs/conf.py index bb799bc..b6aa721 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,18 +7,27 @@ import datetime import os +from packaging.version import Version + # The full version, including alpha/beta/rc tags from ebtelplusplus import __version__ # -- Project information ----------------------------------------------------- - -release = __version__ -is_development = '.dev' in __version__ +_version_ = Version(__version__) +# NOTE: Avoid "post" appearing in version string in rendered docs +if _version_.is_postrelease: + version = release = f'{_version_.major}.{_version_.minor}.{_version_.micro}' +# NOTE: Avoid long githashes in rendered Sphinx docs +elif _version_.is_devrelease: + version = release = f'{_version_.major}.{_version_.minor}.dev{_version_.dev}' +else: + version = release = str(_version_) +is_development = _version_.is_devrelease project = "ebtelplusplus" author = "Will Barnes" -copyright = f"{datetime.datetime.now().year}, {author}" # noqa: A001 +copyright = f"{datetime.datetime.utcnow().year}, {author}" # noqa: A001 # -- General configuration --------------------------------------------------- @@ -83,6 +92,9 @@ html_theme = "pydata_sphinx_theme" html_theme_options = { + "logo": { + "text": f"ebtelplusplus {version}", + }, "use_edit_page_button": True, "icon_links": [ { diff --git a/docs/index.rst b/docs/index.rst index 79fb4b4..b9e0326 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Details regarding this formulation can be found in :cite:t:`klimchuk_highly_2008 .. note:: If you are looking for the original IDL implementation, - the repository for the original IDL code can be found `here `__. + the repository for that code can be found `here `__. Installation ------------ diff --git a/ebtelplusplus/extern/ebtel.cpp b/ebtelplusplus/extern/ebtel.cpp index cd95b42..a857935 100644 --- a/ebtelplusplus/extern/ebtel.cpp +++ b/ebtelplusplus/extern/ebtel.cpp @@ -112,18 +112,10 @@ py::dict run(py::dict& config_dict) } PYBIND11_MODULE(_core, m) { - m.doc() = R"pbdoc( - Pybind11 example plugin - ----------------------- - .. currentmodule:: python_example - .. autosummary:: - :toctree: _generate - add - subtract - )pbdoc"; - - m.def("run", &run, py::return_value_policy::copy, R"pbdoc( - Run ebtel++ - Some other explanation about ebtel++ + m.def("run", + &run, + py::return_value_policy::copy, + R"pbdoc( + Python binding to ebtelplusplus )pbdoc"); } diff --git a/ebtelplusplus/models.py b/ebtelplusplus/models.py index 166aa2f..c510534 100644 --- a/ebtelplusplus/models.py +++ b/ebtelplusplus/models.py @@ -27,10 +27,10 @@ class PhysicsModel: force_single_fluid: `bool` If true, electron and ion populations forced into equilibrium. c1_conduction: `float` - Nominal value of $c_1$ during the conductive cooling phase + Nominal value of :math:`c_1` during the conductive cooling phase See Appendix A of :cite:t:`barnes_inference_2016`. c1_radiation: `float` - Nominal value of $c_1$ during radiative phase. See Eq. 16 of + Nominal value of :math:`c_1` during radiative phase. See Eq. 16 of :cite:t:`cargill_enthalpy-based_2012`. use_c1_gravity_correction: `bool` Use correction in Eq. 12 of :cite:t:`cargill_enthalpy-based_2012`. diff --git a/examples/README.rst b/examples/README.rst index bbb70a1..b90c0e1 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -1,3 +1,3 @@ -=============== -Example Gallery -=============== +======== +Examples +======== diff --git a/examples/area_expansion.py b/examples/area_expansion.py new file mode 100644 index 0000000..83b848a --- /dev/null +++ b/examples/area_expansion.py @@ -0,0 +1,95 @@ +""" +The Effect of Cross-sectional Area Expansion +============================================ +In this example, we demonstrate the effect of expanding cross-sectional area on the time-evolution +of the temperature, density, and pressure. We will reproduce Figure 7 of :cite:t:`cargill_static_2022`. +""" +import astropy.units as u +import matplotlib.pyplot as plt + +from astropy.visualization import quantity_support + +import ebtelplusplus + +from ebtelplusplus.models import HeatingModel, PhysicsModel, TriangularHeatingEvent + +quantity_support() + +############################################################################## +# In `ebtelplusplus`, cross-sectional area expansion is defined through two ratios: +# the ratio between the cross-sectional area averaged over the transition +# region (TR) to the cross-sectional area averaged over the corona +# (:math:`A_{TR}/A_C`) and the ratio between the cross-sectional area at the +# TR-corona boundary and the cross-sectional area averaged over the corona +# (:math:`A_0/A_C`). An additional third parameter, :math:`L_{TR}/L`, the ratio between the +# length of the TR and the loop half-length, controls the thickness of the TR. +# +# We will explore the effect of three different expansion profiles: no expansion, +# gradual expansion from the TR through the corona, and rapid expansion in the +# corona. +# +# We start by defining our simple single-pulse heating model that we will use in +# all three cases. +# Note that we will use a heating partition of :math:`1/2` because we will assume +# a single fluid model in this case to be consistent with :cite:t:`cargill_static_2022`. + +heating = HeatingModel(background=3.5e-5*u.Unit('erg cm-3 s-1'), + partition=0.5, + events=[TriangularHeatingEvent(0*u.s, 200*u.s, 0.1*u.Unit('erg cm-3 s-1'))]) + +############################################################################## +# Next, we will set up our three expansion models following +# :cite:t:`cargill_static_2022`. +# In all cases except the no expansion case, we set :math:`L_{TR}/L_C=0.15` to +# model a TR with a small, but finite thickness. + +no_expansion = PhysicsModel(force_single_fluid=True) +gradual_expansion = PhysicsModel(force_single_fluid=True, + loop_length_ratio_tr_total=0.15, + area_ratio_tr_corona=1/3, + area_ratio_0_corona=2/3) +coronal_expansion = PhysicsModel(force_single_fluid=True, + loop_length_ratio_tr_total=0.15, + area_ratio_tr_corona=1/3, + area_ratio_0_corona=1/3) + +############################################################################## +# Now, run each simulation for a loop with a half length of 45 Mm for a total +# simulation time of 5000 s. + +loop_length = 45 * u.Mm +total_time = 5500 * u.s +r_no_expansion = ebtelplusplus.run(total_time, loop_length, heating, physics=no_expansion) +r_gradual_expansion = ebtelplusplus.run(total_time, loop_length, heating, physics=gradual_expansion) +r_coronal_expansion = ebtelplusplus.run(total_time, loop_length, heating, physics=coronal_expansion) + +############################################################################## +# Finally, let's visualize our results in the manner of Figure 7 of +# :cite:t:`cargill_static_2022`. + +fig, axes = plt.subplot_mosaic( + """ + TN + PO + """, + figsize=(8,8), + layout='constrained', +) +for result, model in [(r_no_expansion, no_expansion), + (r_gradual_expansion, gradual_expansion), + (r_coronal_expansion, coronal_expansion)]: + label = f'$A_{{TR}}/A_C={model.area_ratio_tr_corona:.2f},A_0/A_C={model.area_ratio_0_corona:.2f}$' + axes['T'].plot(result.time, result.electron_temperature.to('MK'), label=label) + axes['N'].plot(result.time, result.density) + axes['P'].plot(result.time, result.electron_pressure+result.ion_pressure) + axes['O'].plot(result.electron_temperature, result.density) +axes['T'].legend(frameon=False,loc=1) +for ax in ['T','N','P']: + axes[ax].set_xlim(0,5500) +axes['T'].set_ylim(0,15) +axes['N'].set_ylim(0,6e9) +axes['P'].set_ylim(0,8) +axes['O'].set_xlim(1e5,2e7) +axes['O'].set_ylim(7e7, 1e10) +axes['O'].set_xscale('log') +axes['O'].set_yscale('log') diff --git a/examples/electron_single_event.py b/examples/electron_single_event.py index bba997d..9341af6 100644 --- a/examples/electron_single_event.py +++ b/examples/electron_single_event.py @@ -1,6 +1,6 @@ """ -Electron heating: 1 triangular event -================================================ +Heating only the Electrons with One Triangular Event +==================================================== In this example, only the electrons are heated by a single triangular pulse lasting 500 seconds and injecting 10 ergs per cubic centimeter into the loop plasma. """ diff --git a/examples/ion_multi_event.py b/examples/ion_multi_event.py index e2b016d..48ced69 100644 --- a/examples/ion_multi_event.py +++ b/examples/ion_multi_event.py @@ -1,6 +1,6 @@ """ -Ion heating: Multiple square events -=================================== +Heating only the Ions with Multiple Square Events +================================================= In this example, only the ions are heated by multiple square pulses each lasting 200 seconds with heating rates chosen from a uniform distribution. """ diff --git a/examples/single_trapezoid_event.py b/examples/single_trapezoid_event.py index d5c349c..832ef7d 100644 --- a/examples/single_trapezoid_event.py +++ b/examples/single_trapezoid_event.py @@ -1,6 +1,6 @@ """ -Single fluid: 1 trapezoidal event -================================== +Using an Asymmetric Heating Profile Under the Single-fluid Approximation +======================================================================== In this example, we force the electron and ion populations to have the same temperature to illustrate the single fluid case. """ diff --git a/pyproject.toml b/pyproject.toml index f1ea6ce..205d172 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,6 +114,9 @@ filterwarnings = [ [tool.coverage] branch = true +omit = [ + "ebtelplusplus/tests/helpers.py", +] [tool.coverage.report]