diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..f7290f4 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: + - main + - stable + - v0.*.x + tags: + - v* + pull_request: + workflow_dispatch: + +jobs: + documentation: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install Python dependencies + run: python -m pip install --progress-bar off --upgrade tox + - name: Install language-pack-fr and tzdata + run: sudo apt-get install graphviz pandoc gfortran + - name: Build gallery + run: tox diff --git a/.gitignore b/.gitignore index c62cc19..9a1fea8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .tox/ generated/ _build/ +.idea gallery/pydata/ -gallery/themis_data/ \ No newline at end of file +gallery/themis_data/ diff --git a/README.md b/README.md index 327c9c8..98e7ebc 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ To contribute a new tutorial please provide a pull request. All tutorial files are stored in the `gallery\` directory and must be a python file. The gallery uses the [sphinx gallery](https://sphinx-gallery.readthedocs.io/en/latest/) plugin and all output including plots are generated automatically. You can see -an example gallery [here](https://sphinx-gallery.readthedocs.io/en/latest/auto_examples/index.html). +an example gallery [here](https://sphinx-gallery.github.io/stable/auto_examples/index.html). The [SunPy gallery](http://docs.sunpy.org/en/stable/generated/gallery/index.html) is also a good example. For the required syntax see the -[sphinx gallery syntax](https://sphinx-gallery.readthedocs.io/en/latest/syntax.html) +[sphinx gallery syntax](https://sphinx-gallery.github.io/stable/syntax.html) documentation. Since the syntax is fairly straightforward you may also just refer to an existing tutorial file. diff --git a/conf.py b/conf.py index 71ececa..8e58ce3 100644 --- a/conf.py +++ b/conf.py @@ -21,7 +21,7 @@ # -- Project information ----------------------------------------------------- project = 'PyHC Tutorials' -copyright = '2021, PyHC' +copyright = '2018–2022, PyHC' author = 'PyHC' # The short X.Y version @@ -29,7 +29,6 @@ # The full version, including alpha/beta/rc tags release = '' - # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -61,7 +60,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -71,7 +70,6 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = None - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -205,10 +203,10 @@ 'matplotlib': ('https://matplotlib.org/', (None, 'http://data.astropy.org/intersphinx/matplotlib.inv')), 'astropy': ('http://docs.astropy.org/en/stable/', None), - 'sunpy': ('http://docs.sunpy.org/en/stable/', None) + 'sunpy': ('http://docs.sunpy.org/en/stable/', None), + 'plasmapy': ('https://docs.plasmapy.org/en/stable', None), } - extensions += ["sphinx_gallery.gen_gallery"] path = pathlib.Path.cwd() example_dir = path.joinpath('gallery') diff --git a/gallery/coordinate_systems.py b/gallery/coordinate_systems.py index 86219a9..34c5e94 100644 --- a/gallery/coordinate_systems.py +++ b/gallery/coordinate_systems.py @@ -10,6 +10,7 @@ """ ############################################################################## + !pip install spacepy from spacepy.coordinates import Coords import spacepy.time as spt diff --git a/gallery/plasmapy_particles.py b/gallery/plasmapy_particles.py new file mode 100644 index 0000000..094c460 --- /dev/null +++ b/gallery/plasmapy_particles.py @@ -0,0 +1,303 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.0 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# # Using PlasmaPy Particles + +# [plasmapy.particles]: ../../particles/index.rst +# +# The [plasmapy.particles] subpackage contains functions to access basic particle data and classes to represent particles. + +from plasmapy.particles import * + +# ## Contents +# +# 1. [Particle properties](#Particle-properties) +# 2. [Particle objects](#Particle-objects) +# 3. [Custom particles](#Custom-particles) +# 4. [Molecules](#Molecules) +# 5. [Particle lists](#Particle-lists) +# 6. [Dimensionless particles](#Dimensionless-particles) +# 7. [Nuclear reactions](#Nuclear-reactions) + +# ## Particle properties + +# [representation of a particle]: https://docs.plasmapy.org/en/stable/api/plasmapy.particles.ParticleLike.html#particlelike +# +# There are several functions that provide information about different particles that might be present in a plasma. The input of these functions is a [representation of a particle], such as a string for the atomic symbol or the element name. + +atomic_number("Fe") + +# [atomic number]: https://en.wikipedia.org/wiki/Atomic_number +# +# We can provide a number that represents the [atomic number]. + +element_name(26) + +# We can also provide standard symbols or the names of particles. + +is_stable("e-") + +charge_number("proton") + +# [alpha particle]: https://en.wikipedia.org/wiki/Alpha_particle +# +# The symbols for many particles can even be used directly, such as for an [alpha particle]. To create an "α" in a Jupyter notebook, type `\alpha` and press tab. + +particle_mass("α") + +# [mass number]: https://en.wikipedia.org/wiki/Mass_number +# [half_life]: ../../api/plasmapy.particles.atomic.half_life.rst +# [Quantity]: https://docs.astropy.org/en/stable/units/quantity.html#quantity +# [astropy.units]: https://docs.astropy.org/en/stable/units/index.html +# +# We can represent isotopes with the atomic symbol followed by a hyphen and the [mass number]. In this example, [half_life] returns the time in seconds as a [Quantity] from [astropy.units]. + +half_life("C-14") + +# We typically represent an ion in a string by putting together the atomic symbol or isotope symbol, a space, the charge number, and the sign of the charge. + +charge_number("Fe-56 13+") + +# [plasmapy.particles]: ../../particles/index.rst +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# [particle-like]: https://docs.plasmapy.org/en/latest/glossary.html#term-particle-like +# +# Functions in [plasmapy.particles] are quite flexible in terms of string inputs representing particles. An input is [particle-like] if it can be transformed into a [Particle]. + +particle_mass("iron-56 +13") + +particle_mass("iron-56+++++++++++++") + +# Most of these functions take additional arguments, with `Z` representing the charge number of an ion and `mass_numb` representing the mass number of an isotope. These arguments are often [keyword-only](https://docs.plasmapy.org/en/latest/glossary.html#term-keyword-only) to avoid ambiguity. + +particle_mass("Fe", Z=13, mass_numb=56) + +# ## Particle objects + +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# Up until now, we have been using functions that accept representations of particles and then return particle properties. With the [Particle] class, we can create objects that represent physical particles. + +proton = Particle("p+") +electron = Particle("electron") +iron56_nuclide = Particle("Fe", Z=26, mass_numb=56) + +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# Particle properties can be accessed via attributes of the [Particle] class. + +proton.mass + +electron.charge + +electron.charge_number + +iron56_nuclide.binding_energy + +# ### Antiparticles +# +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# We can get antiparticles of fundamental particles by using the `antiparticle` attribute of a [Particle]. + +electron.antiparticle + +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# We can also use the tilde operator on a [Particle] to get its antiparticle. + +~proton + +# ### Ionization and recombination +# +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# The `recombine` and `ionize` methods of a [Particle] representing an ion or neutral atom will return a different [Particle] with fewer or more electrons. + +deuterium = Particle("D 0+") +deuterium.ionize() + +# When provided with a number, these methods tell how many bound electrons to add or remove. + +alpha = Particle("alpha") +alpha.recombine(2) + +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# If the ``inplace`` keyword is set to `True`, then the [Particle] will be replaced with the new particle. + +argon = Particle("Ar 0+") +argon.ionize(inplace=True) +print(argon) + +# ## Custom particles + +# [CustomParticle]: ../../api/plasmapy.particles.particle_class.CustomParticle.rst +# +# Sometimes we want to use a particle with custom properties. For example, we might want to represent an average ion in a multi-species plasma. For that we can use [CustomParticle]. + +# + +from astropy import constants as const +from astropy import units as u + +custom_particle = CustomParticle(9.27e-26 * u.kg, 13.6 * const.e.si, symbol="Fe 13.6+") +# - + +# [CustomParticle]: ../../api/plasmapy.particles.particle_class.CustomParticle.rst +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# Many of the attributes of [CustomParticle] are the same as in [Particle]. + +custom_particle.mass + +custom_particle.charge + +custom_particle.symbol + +# [nan]: https://numpy.org/doc/stable/reference/constants.html#numpy.nan +# +# If we do not include one of the physical quantities, it gets set to [nan] (not a number) in the appropriate units. + +CustomParticle(9.27e-26 * u.kg).charge + +# [CustomParticle]: ../../api/plasmapy.particles.particle_class.CustomParticle.rst +# [plasmapy.formulary]: ../../formulary/index.rst +# [plasmapy.particles]: ../../particles/index.rst +# +# [CustomParticle] objects are not yet able to be used by many of the functions in [plasmapy.formulary], but are expected to become compatible with them in a future release of PlasmaPy. Similarly, [CustomParticle] objects are not able to be used by the functions in [plasmapy.particles] that require that the particle be real. + +# ## Particle lists + +# [ParticleList]: ../../api/plasmapy.particles.particle_collections.ParticleList.rst +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# [CustomParticle]: ../../api/plasmapy.particles.particle_class.CustomParticle.rst +# +# The [ParticleList] class is a container for [Particle] and [CustomParticle] objects. + +iron_ions = ParticleList(["Fe 12+", "Fe 13+", "Fe 14+"]) + +# [ParticleList]: ../../api/plasmapy.particles.particle_collections.ParticleList.rst +# +# By using a [ParticleList], we can access the properties of multiple particles at once. + +iron_ions.mass + +iron_ions.charge + +iron_ions.symbols + +# [ParticleList]: ../../api/plasmapy.particles.particle_collections.ParticleList.rst +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# [CustomParticle]: ../../api/plasmapy.particles.particle_class.CustomParticle.rst +# +# We can also create a [ParticleList] by adding [Particle] and/or [CustomParticle] objects together. + +proton + electron + custom_particle + +# ## Molecules + +# [CustomParticle]: ../../api/plasmapy.particles.particle_class.CustomParticle.rst +# [molecule]: ../../api/plasmapy.particles.particle_class.molecule.rst +# +# We can use [molecule] to create a [CustomParticle] based on a chemical formula. The first argument to [molecule] is a string that represents a chemical formula, except that the subscript numbers are not given as subscripts. For example, water is ``"H2O"``. + +water = molecule("H2O") +water.symbol + +# The properties of the molecule are found automatically. + +water.mass + +acetic_acid_anion = molecule("CH3COOH 1-") +acetic_acid_anion.charge + +# ## Particle categorization + +# [categories]: ../../api/plasmapy.particles.particle_class.Particle.rst#plasmapy.particles.particle_class.Particle.categories +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# The [categories] attribute of a [Particle] provides a set of the categories that the [Particle] belongs to. + +muon = Particle("muon") +muon.categories + +# [is_category()]: ../../api/plasmapy.particles.particle_class.Particle.rst#plasmapy.particles.particle_class.Particle.is_category +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# The [is_category()] method lets us determine if a [Particle] belongs to one or more categories. + +muon.is_category("lepton") + +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# If we need to be more specific, we can use the `require` keyword for categories that a [Particle] must belong to, the `exclude` keyword for categories that the [Particle] cannot belong to, and the `any_of` keyword for categories of which a [Particle] needs to belong to at least one. + +electron.is_category(require="lepton", exclude="baryon", any_of={"boson", "fermion"}) + +# [is_category()]: ../../api/plasmapy.particles.particle_class.Particle.rst#plasmapy.particles.particle_class.Particle.is_category +# [Particle]: ../../api/plasmapy.particles.particle_class.Particle.rst +# +# The `valid_categories` attribute of [is_category()] for any [Particle] gives a set containing all valid categories. + +print(electron.is_category.valid_categories) + +# [is_category()]: ../../api/plasmapy.particles.particle_class.Particle.rst#plasmapy.particles.particle_class.Particle.is_category +# [ParticleList]: ../../api/plasmapy.particles.particle_collections.ParticleList.rst +# +# The [is_category()] method of [ParticleList] returns a `list` of boolean values which correspond to whether or not each particle in the list meets the categorization criteria. + +particles = ParticleList(["e-", "p+", "n"]) +particles.is_category(require="lepton") + +# ## Dimensionless particles + +# [DimensionlessParticle]: ../../api/plasmapy.particles.particle_class.DimensionlessParticle.rst +# +# When we need a dimensionless representation of a particle, we can use the [DimensionlessParticle] class. + +dimensionless_particle = DimensionlessParticle(mass=0.000545, charge=-1) + +# The properties of dimensionless particles may be accessed by its attributes. + +dimensionless_particle.mass + +dimensionless_particle.charge + +# [DimensionlessParticle]: ../../api/plasmapy.particles.particle_class.DimensionlessParticle.rst +# [ParticleList]: ../../api/plasmapy.particles.particle_collections.ParticleList.rst +# +# Because a [DimensionlessParticle] does not uniquely describe a physical particle, it cannot be contained in a [ParticleList]. + +# ## Nuclear reactions + +# [plasmapy.particles]: ../../particles/index.rst +# +# We can use [plasmapy.particles] to calculate the energy of a nuclear reaction using the `>` operator. + +deuteron = Particle("D+") +triton = Particle("T+") +alpha = Particle("α") +neutron = Particle("n") + +energy = deuteron + triton > alpha + neutron + +energy.to("MeV") + +# If the nuclear reaction is invalid, then an exception is raised that states the reason why. + +# + nbsphinx="hidden" +# %xmode minimal + +# + tags=["raises-exception"] +deuteron + triton > alpha + 3 * neutron diff --git a/gallery/retrieve_compress.py b/gallery/retrieve_compress.py index 19a5626..4b3a6ed 100644 --- a/gallery/retrieve_compress.py +++ b/gallery/retrieve_compress.py @@ -1,60 +1,120 @@ -# coding: utf-8 -""" -========================================================================= -Downloading and Compressing a FITS file using SunPy, aiapy, and Astropy -========================================================================= - -Written by Matt Wentzel-Long. The purpose of this demo is to demonstrate: -1) SunPy's ability to retrieve a Level 1 AIA data, 2) convert this to Level -1.5 AIA data using aiapy, 3) deconvolve the FITS file using aiapy, and 4) -demonstrate the compression ability of Astropy when saving the file. -""" - -############################################################################## -# First import the packages -import astropy -import astropy.units as u -from astropy.io.fits import CompImageHDU - -from sunpy.net import Fido, attrs as a -import sunpy.map - -import aiapy.psf as psf_ -from aiapy.calibrate import register, update_pointing - -import os - -############################################################################## -# Use the SunPy tool `Fido `_ to find and download level 1 AIA data. -q = Fido.search(a.Time('2011-06-07T06:52:00', '2011-06-07T06:52:10'), - a.Instrument('AIA'), - a.Wavelength(wavemin=171*u.angstrom, wavemax=171*u.angstrom)) -aia_map = sunpy.map.Map(Fido.fetch(q)) - -############################################################################## -# Convert to level 1.5 AIA data. See the `registering and aligning level 1 data `_ -# example in aiapy documentation for more details. -m_updated_pointing = update_pointing(aia_map) -m_registered = register(m_updated_pointing) -m_normalized = sunpy.map.Map( - m_registered.data/m_registered.exposure_time.to(u.s).value, - m_registered.meta) - -############################################################################## -# Compute the point-spread function (PSF) and use it to deconvolve the image. -# Warning: the PSF computation can take over 16 minutes on a CPU. If you have -# an NVIDIA GPU and CuPy installed, then PSF will automatically use it. -# See the `PSF documentation `_ for details. -psf = psf_.psf(m_normalized.wavelength) -map_deconvolved = psf_.deconvolve(m_normalized, psf=psf) - -############################################################################## -# Save the deconvolved image as a FITS file without compression using SunPy. -# Note: this resulted in a 128 MB file while testing. -map_deconvolved.save('aia_map_deconv.fits') -print(os.path.getsize('aia_map_deconv.fits')) - -# This time pass SunPy the `CompImagHDU `_ compression routine from Astropy. -sunpy.io.fits.write('aia_map_deconv_comp.fits', map_deconvolved.data, - map_deconvolved.fits_header, hdu_type=CompImageHDU) -print(os.path.getsize('aia_map_deconv_comp.fits')) +""" +========================================================================= +Downloading and Compressing a FITS file using SunPy, aiapy, and Astropy +========================================================================= + +Written by Matt Wentzel-Long. The purpose of this demo is to demonstrate: +1) SunPy's ability to retrieve a Level 1 AIA data, 2) convert this to Level +1.5 AIA data using aiapy, 3) deconvolve the FITS file using aiapy, and 4) +demonstrate the compression ability of Astropy when saving the file. +""" + +############################################################################## +# First import the packages +import astropy +import astropy.units as u +from astropy.io.fits import CompImageHDU + +from sunpy.net import Fido, attrs as a +import sunpy.map + +import aiapy.psf as psf_ +from aiapy.calibrate import register, update_pointing + +import os + +############################################################################## +# Use the SunPy tool `Fido `_ to find and download level 1 AIA data. +q = Fido.search(a.Time('2011-06-07T06:52:00', '2011-06-07T06:52:10'), + a.Instrument('AIA'), + a.Wavelength(wavemin=171*u.angstrom, wavemax=171*u.angstrom)) +aia_map = sunpy.map.Map(Fido.fetch(q)) + +############################################################################## +# Convert to level 1.5 AIA data. See the `registering and aligning level 1 data `_ +# example in aiapy documentation for more details. +m_updated_pointing = update_pointing(aia_map) +m_registered = register(m_updated_pointing) +m_normalized = sunpy.map.Map( + m_registered.data/m_registered.exposure_time.to(u.s).value, + m_registered.meta) + +############################################################################## +# Compute the point-spread function (PSF) and use it to deconvolve the image. +# Warning: the PSF computation can take over 16 minutes on a CPU. If you have +# an NVIDIA GPU and CuPy installed, then PSF will automatically use it. +# See the `PSF documentation `_ for details. +psf = psf_.psf(m_normalized.wavelength) +map_deconvolved = psf_.deconvolve(m_normalized, psf=psf) + +############################################################################## +# Save the deconvolved image as a FITS file without compression using SunPy. +# Note: this resulted in a 128 MB file while testing. +map_deconvolved.save('aia_map_deconv.fits') +print(os.path.getsize('aia_map_deconv.fits')) + +# This time pass SunPy the `CompImagHDU `_ compression routine from Astropy. +sunpy.io.fits.write('aia_map_deconv_comp.fits', map_deconvolved.data, + map_deconvolved.fits_header, hdu_type=CompImageHDU) +print(os.path.getsize('aia_map_deconv_comp.fits')) +======= +# coding: utf-8 +""" +========================================================================= +Downloading and Compressing a FITS file using SunPy, aiapy, and Astropy +========================================================================= + +Written by Matt Wentzel-Long. The purpose of this demo is to demonstrate: +1) SunPy's ability to retrieve a Level 1 AIA data, 2) convert this to Level +1.5 AIA data using aiapy, 3) deconvolve the FITS file using aiapy, and 4) +demonstrate the compression ability of Astropy when saving the file. +""" + +############################################################################## +# First import the packages +import astropy +import astropy.units as u +from astropy.io.fits import CompImageHDU + +from sunpy.net import Fido, attrs as a +import sunpy.map + +import aiapy.psf as psf_ +from aiapy.calibrate import register, update_pointing + +import os + +############################################################################## +# Use the SunPy tool `Fido `_ to find and download level 1 AIA data. +q = Fido.search(a.Time('2011-06-07T06:52:00', '2011-06-07T06:52:10'), + a.Instrument('AIA'), + a.Wavelength(wavemin=171*u.angstrom, wavemax=171*u.angstrom)) +aia_map = sunpy.map.Map(Fido.fetch(q)) + +############################################################################## +# Convert to level 1.5 AIA data. See the `registering and aligning level 1 data `_ +# example in aiapy documentation for more details. +m_updated_pointing = update_pointing(aia_map) +m_registered = register(m_updated_pointing) +m_normalized = sunpy.map.Map( + m_registered.data/m_registered.exposure_time.to(u.s).value, + m_registered.meta) + +############################################################################## +# Compute the point-spread function (PSF) and use it to deconvolve the image. +# Warning: the PSF computation can take over 16 minutes on a CPU. If you have +# an NVIDIA GPU and CuPy installed, then PSF will automatically use it. +# See the `PSF documentation `_ for details. +psf = psf_.psf(m_normalized.wavelength) +map_deconvolved = psf_.deconvolve(m_normalized, psf=psf) + +############################################################################## +# Save the deconvolved image as a FITS file without compression using SunPy. +# Note: this resulted in a 128 MB file while testing. +map_deconvolved.save('aia_map_deconv.fits') +print(os.path.getsize('aia_map_deconv.fits')) + +# This time pass SunPy the `CompImagHDU `_ compression routine from Astropy. +sunpy.io.fits.write('aia_map_deconv_comp.fits', map_deconvolved.data, + map_deconvolved.fits_header, hdu_type=CompImageHDU) +print(os.path.getsize('aia_map_deconv_comp.fits')) diff --git a/requirements.txt b/requirements.txt index 324935e..b6c530b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ pyspedas==1.2.8 aiapy==0.6.4 spacepy bs4 +plasmapy==0.8.1