Repository: PlasmaPy/PlasmaPy
Pull request #237 (2018-01-17 19:36:09)

Reorganize atomic subpackage
This pull request moves functions related to string parsing (`atomic_symbol`, `isotope_symbol`, etc.) from `atomic/atomic.py` into `atomic/names.py`.  The purpose of this move is to isolate the string functions so that they can be used in decorators for functions and for setting up classes.  The tests are continuing to be in `test_atomic.py` since there are some tests (mostly of exceptions and warnings) that are being done for functions in both `names.py` and `atomic.py`.



Patch:
diff --git a/plasmapy/atomic/__init__.py b/plasmapy/atomic/__init__.py
index 883da534..6c7b5762 100644
--- a/plasmapy/atomic/__init__.py
+++ b/plasmapy/atomic/__init__.py
@@ -1,19 +1,28 @@
 """Physical and mathematical constants for use within PlasmaPy."""
-from .atomic import (atomic_symbol,
-                     isotope_symbol,
-                     atomic_number,
-                     is_isotope_stable,
-                     half_life,
-                     mass_number,
-                     element_name,
-                     standard_atomic_weight,
-                     isotope_mass,
-                     ion_mass,
-                     known_isotopes,
-                     common_isotopes,
-                     stable_isotopes,
-                     isotopic_abundance,
-                     charge_state,
-                     electric_charge)
 
-from .nuclear import nuclear_binding_energy, nuclear_reaction_energy
+from .names import (
+    atomic_symbol,
+    isotope_symbol,
+    element_name,
+)
+
+from .atomic import (
+    atomic_number,
+    is_isotope_stable,
+    half_life,
+    mass_number,
+    standard_atomic_weight,
+    isotope_mass,
+    ion_mass,
+    known_isotopes,
+    common_isotopes,
+    stable_isotopes,
+    isotopic_abundance,
+    charge_state,
+    electric_charge,
+)
+
+from .nuclear import (
+    nuclear_binding_energy,
+    nuclear_reaction_energy,
+)
diff --git a/plasmapy/atomic/atomic.py b/plasmapy/atomic/atomic.py
index 4d7442ce..d97c7302 100644
--- a/plasmapy/atomic/atomic.py
+++ b/plasmapy/atomic/atomic.py
@@ -1,29 +1,37 @@
 """Functions that retrieve or are related to elemental or isotopic data."""
 
 import numpy as np
-import re
 import warnings
-from typing import (Union, Optional, Any, List, Tuple)
+from typing import (Union, Optional, List)
+
 from astropy import units as u, constants as const
 from astropy.units import Quantity
-from .elements import _atomic_symbols, _atomic_symbols_dict, _Elements
+
+from .elements import _Elements
 from .isotopes import _Isotopes
-from .particles import (_is_special_particle, _get_standard_symbol)
-from ..utils import (AtomicWarning,
-                     InvalidElementError,
-                     InvalidIsotopeError,
-                     InvalidIonError,
-                     AtomicError,
-                     MissingAtomicDataError,
-                     MissingAtomicDataWarning,
-                     InvalidParticleError,
-                     ChargeError)
-
-
-# The code contained within atomic_symbol(), isotope_symbol(), and
-# _extract_charge_state() is designed to catch all of the special
-# cases for different inputs.  Complexity is concentrated in these
-# functions so that the rest of the functions can be simpler.
+
+from ..utils import (
+    AtomicWarning,
+    InvalidElementError,
+    InvalidIsotopeError,
+    InvalidIonError,
+    AtomicError,
+    MissingAtomicDataError,
+    MissingAtomicDataWarning,
+    InvalidParticleError,
+    ChargeError)
+
+from .names import (
+    atomic_symbol,
+    isotope_symbol,
+    _extract_charge_state,
+    _is_proton,
+    _is_positron,
+    _is_antineutron,
+    _is_antiproton,
+    _is_electron,
+    _is_neutron)
+
 
 # TODO: refactor the atomic subpackage
 #  - Create a Particle class
@@ -33,31 +41,27 @@
 #     3. Checks to make sure that the inputs are of the correct types
 #     4. Returns the Particle class which would be the input to the
 #        inner function
-#    Would it be helpful to put the string parsing code into a new file
-#    called something like parsing.py?
 
-# TODO: Create an ion_symbol function
-# TODO: Create a particle_symbol function
 # TODO: Create a particle_mass function
 # TODO: Create lepton_number and baryon_number functions
 # TODO: Maybe create is_antimatter, is_lepton, is_baryon, is_boson, is_fermion
 
 
-def atomic_symbol(argument: Union[str, int]) -> str:
-    r"""Returns the atomic symbol.
+def atomic_number(argument: str) -> str:
+    r"""Returns the number of protons in an atom, isotope, or ion.
 
     Parameters
     ----------
 
-    argument: string or integer
-        A string representing an element, isotope, or ion; or an
-        integer representing an atomic number.
+    argument: string
+        A string representing an element, isotope, or ion.
 
     Returns
     -------
 
-    symbol: string
-        The atomic symbol of the element, isotope, or nucleon.
+    atomic_number: integer
+        An integer representing the atomic number of the element or
+        isotope.
 
     Raises
     ------
@@ -69,311 +73,128 @@ def atomic_symbol(argument: Union[str, int]) -> str:
         If the argument does not correspond to a valid particle.
 
     TypeError
-        If the argument is not a string or integer.
+        If the argument is not a string.
 
     See also
     --------
 
-    isotope_symbol : returns isotope symbol instead of atomic symbol.
-
-    element_name : returns the name of an element.
-
-    Notes
-    -----
-
-    This function returns the symbol of the element rather than the
-    symbol of an isotope.  For example, 'deuterium', 'T', or
-    'hydrogen-2' will yield 'H'; 'alpha' will yield 'He'; and
-    'iron-56' or 'Fe-56' will yield 'Fe'.
-
-    This function is case insensitive when there is no ambiguity
-    associated with case.  However, this function will return 'H' for
-    hydrogen for lower case 'p' but capital 'P' if the argument is 'P'
-    for phosphorus.  This function will return 'N' for nitrogen if the
-    argument is capital 'N', but will not accept lower case 'n' for
-    neutrons.
+    mass_number : returns the mass number (the total number of protons
+        and neutrons) of an isotope.
 
     Examples
     --------
-
-    >>> atomic_symbol('helium')
-    'He'
-    >>> atomic_symbol(42)
-    'Mo'
-    >>> atomic_symbol('D')
-    'H'
-    >>> atomic_symbol('C-13')
-    'C'
-    >>> atomic_symbol('alpha')
-    'He'
-    >>> atomic_symbol('79')
-    'Au'
-    >>> atomic_symbol('N')  # Nitrogen
-    'N'
-    >>> atomic_symbol('P'), atomic_symbol('p')  # Phosphorus, proton
-    ('P', 'H')
+    >>> atomic_number("H")
+    1
+    >>> atomic_number("tritium")
+    1
+    >>> atomic_number("alpha")
+    2
+    >>> atomic_number("oganesson")
+    118
 
     """
 
-    if _is_special_particle(argument):
-        raise InvalidElementError(f"{argument} is not a valid element.")
-
     try:
-        argument, Z = _extract_charge_state(argument)
+        element = atomic_symbol(argument)
+        atomic_numb = _Elements[element]['atomic_number']
+    except (InvalidElementError, KeyError):
+        raise InvalidElementError(
+            f"The argument {argument} to atomic_number does not correspond to "
+            "a valid element.") from None
     except InvalidParticleError:
-        raise InvalidParticleError("Invalid charge in atomic_symbol")
-
-    if not isinstance(argument, (str, int)):
-        raise TypeError("The first argument in atomic_symbol must be either "
-                        "a string representing an element or isotope, or an "
-                        "integer representing the atomic number (or 0 for "
-                        "neutrons).")
-
-    if isinstance(argument, str) and argument.isdigit():
-        argument = int(argument)
-
-    if isinstance(argument, int):
-
-        try:
-            element = _atomic_symbols[argument]
-        except KeyError:
-            raise InvalidParticleError(f"{argument} is an invalid atomic "
-                                       "number in atomic_symbol.")
-
-    elif _is_hydrogen(argument):
-        element = 'H'
-    elif _is_alpha(argument):
-        element = 'He'
-    elif isinstance(argument, str):
-
-        if argument.count('-') == 1:
-            dash_position = argument.find('-')
-            mass_numb = argument[dash_position+1:]
-            if not mass_numb.isdigit():
-                raise InvalidParticleError("Invalid isotope format in "
-                                           "atomic_symbol")
-            argument = argument[:dash_position]
-        else:
-            mass_numb = ''
-
-        if argument.lower() in _atomic_symbols_dict.keys():
-            element = _atomic_symbols_dict[argument.lower()]
-        elif argument in _atomic_symbols.values():
-            element = argument.capitalize()
-        else:
-            raise InvalidParticleError(f"{argument} is an invalid argument "
-                                       "for atomic_symbol")
-
-        if mass_numb.isdigit():
-
-            isotope = element.capitalize() + '-' + mass_numb
-
-            if isotope not in _Isotopes.keys():
-                raise InvalidParticleError(
-                    "The input in atomic_symbol corresponding "
-                    f"to {isotope} is not a valid isotope.")
-
-    if Z is not None and \
-            Z > _Elements[element]['atomic_number']:
-        raise InvalidParticleError("Cannot have an ionization state greater "
-                                   "than the atomic number.")
+        raise InvalidParticleError(f"The argument {argument} to atomic_number "
+                                   "is not a valid particle.") from None
+    except TypeError:
+        raise TypeError(f"The argument {argument} to atomic number is not a "
+                        f"string.") from None
 
-    return element
+    return atomic_numb
 
 
-def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
-    r"""Returns the symbol representing an isotope.
+def mass_number(isotope: str) -> int:
+    r"""Get the mass number (the number of protons and neutrons) of an
+    isotope.
 
     Parameters
     ----------
 
-    argument: integer or string
-        A string representing an element, isotope, or ion or an
-        integer representing an atomic number
-
-    mass_numb: integer or string
-        An integer or string representing the mass number of the
-        isotope.
+    isotope : string
+        A string representing an isotope or a neutron.
 
     Returns
     -------
 
-    symbol: string
-        The isotopic symbol. The result will generally be returned as
-        something like 'He-4' or 'Au-197', but will return 'D' for
-        deuterium and 'T' for tritium.
+    mass_number : integer
+       The total number of protons plus neutrons in a nuclide.
 
     Raises
     ------
 
-    InvalidIsotopeError
-        If the argument is a valid particle but not a valid isotope.
-
     InvalidParticleError
-        If the argument does not correspond to a valid particle
-        or contradictory information is provided.
+        If the argument does not correspond to a valid particle.
 
-    TypeError
-        If the argument is not a string or integer.
+    InvalidIsotopeError
+        If the argument does not correspond to a valid isotope.
 
-    Warns
-    -----
+    TypeError
+        The argument is not a string.
 
-    AtomicWarning
-        If redundant isotope information is provided.
 
     See also
     --------
 
-    atomic_symbol : returns atomic symbol instead of isotopic symbol
-
-    Notes
-    -----
-
-    This function returns the symbol of the element rather than the
-    symbol of an isotope.  For example, 'deuterium', 'T', or
-    'hydrogen-2' will yield 'H'; 'alpha' will yield 'He'; and
-    'iron-56' or 'Fe-56' will yield 'Fe'.
+    atomic_number : returns the number of protons in an isotope or
+        element
 
     Examples
     --------
-    >>> isotope_symbol('He', 4)
-    'He-4'
-    >>> isotope_symbol(79, 197)
-    'Au-197'
-    >>> isotope_symbol('hydrogen-2')
-    'D'
-    >>> isotope_symbol('carbon-13')
-    'C-13'
-    >>> isotope_symbol('alpha')
-    'He-4'
 
-    """
-
-    # TODO: Remove this functionality when particle_symbol comes online
-    if _is_neutron(argument, mass_numb):
-        return 'n'
+    >>> mass_number("H-1")
+    1
+    >>> mass_number("Pb-208")
+    208
+    >>> mass_number("tritium")
+    3
+    >>> mass_number("n")
+    1
+    >>> mass_number("alpha")
+    4
 
-    if _is_special_particle(argument):
-        raise InvalidIsotopeError("The argument {argument} does not "
-                                  "correspond to a valid isotope in "
-                                  "isotope_symbol.")
+    """
 
     try:
-        element = atomic_symbol(argument)
+        isotope = isotope_symbol(isotope)
+        mass_numb = _Isotopes[isotope]["mass_number"]
+    except InvalidIsotopeError:
+        raise InvalidIsotopeError("Invalid isotope in mass_number.")
     except InvalidParticleError:
         raise InvalidParticleError(
-            f"The argument {argument} to isotope_symbol is not a valid "
-            f"particle.")
-    except InvalidElementError:
-        raise InvalidIsotopeError(f"The argument {argument} to isotope_symbol "
-                                  f"does not correspond to a valid element or "
-                                  f"isotope.")
-
-    # If the argument is already in our standard form for an isotope,
-    # return the argument.
-
-    if mass_numb is None and argument in _Isotopes.keys():
-        return argument
-
-    if isinstance(argument, str):
-        argument, Z = _extract_charge_state(argument)
-
-    if isinstance(argument, str) and argument.isdigit():
-        argument = int(argument)
-
-    if isinstance(mass_numb, str) and mass_numb.isdigit():
-        mass_numb = int(mass_numb)
-
-    # This routine allows several forms of input, and must be able to handle
-    # all of the exceptions that can arise with useful error messages.
-
-    if not isinstance(argument, (str, int)):
-        raise TypeError("The first argument in isotope_symbol must be either "
-                        "a string representing an element or isotope, or an "
-                        "integer representing the atomic number (or 0 for "
-                        " neutrons).")
-
-    if not (isinstance(mass_numb, int) or
-            mass_numb is None):  # coveralls: ignore
-        raise TypeError("The second argument in isotope_symbol must be an "
-                        "integer (or a string containing an integer) that "
-                        "represents the mass number of an isotope.")
-
-    if isinstance(argument, int):
-        if not 0 <= argument <= 118:
-            raise InvalidParticleError(
-                "Invalid atomic number in isotope_symbol")
-        if mass_numb is None:
-            raise InvalidIsotopeError("Insufficient information to determine "
-                                      "isotope in isotope_symbol.")
-
-    # Get mass number from argument, check for redundancies, and take
-    # care of special cases.
-
-    if isinstance(argument, str):
-        if argument.count('-') == 1:
-            dash_position = argument.find('-')
-            mass_numb_from_arg = argument[dash_position+1:].strip()
-            mass_numb_from_arg = int(mass_numb_from_arg)
-        elif argument in ['p', 'p+'] or \
-                argument.lower() in ['protium', 'proton']:
-            mass_numb_from_arg = 1
-        elif argument.lower() in ['d', 'deuterium', 'deuteron']:
-            mass_numb_from_arg = 2
-        elif argument.lower() in ['t', 'tritium', 'triton']:
-            mass_numb_from_arg = 3
-        elif _is_alpha(argument):
-            mass_numb_from_arg = 4
-        else:
-            mass_numb_from_arg = None
-
-        if mass_numb is None and mass_numb_from_arg is None:
-            raise InvalidIsotopeError(
-                "Insufficient information to determine the mass"
-                " number from the inputs to isotope_symbol.")
-
-        if mass_numb is not None and mass_numb_from_arg is not None:
-            if mass_numb == mass_numb_from_arg:
-                warnings.warn(
-                    "Redundant mass number information in isotope_symbol "
-                    f"from inputs: {argument}, {mass_numb}", AtomicWarning)
-            else:  # coveralls: ignore
-                raise InvalidParticleError(
-                    "Contradictory mass number information in isotope_symbol.")
-
-        if mass_numb_from_arg is not None:
-            mass_numb = mass_numb_from_arg
-
-    isotope = f"{element}-{mass_numb}"
-
-    if isotope == 'H-2':
-        isotope = 'D'
-    elif isotope == 'H-3':
-        isotope = 'T'
-
-    if atomic_number(element) > mass_numb:
-        raise InvalidParticleError("The atomic number cannot exceed the mass "
-                                   "number in isotope_symbol.")
+            f"The argument {isotope} to mass_number does not "
+            "correspond to a valid isotope.") from None
+    except TypeError:
+        raise TypeError("The argument to mass_number is not a string.")
 
-    return isotope
+    return mass_numb
 
 
-def atomic_number(argument: str) -> str:
-    r"""Returns the number of protons in an atom, isotope, or ion.
+def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
+    r"""Returns the standard (conventional) atomic weight of an element
+    based on the relative abundances of isotopes in terrestrial
+    environments.
 
     Parameters
     ----------
 
-    argument: string
-        A string representing an element, isotope, or ion.
+    argument: string or integer
+        A string representing an element or an integer representing an
+        atomic number
 
     Returns
     -------
 
-    atomic_number: integer
-        An integer representing the atomic number of the element or
-        isotope.
+    atomic_weight: astropy.units.Quantity with units of u
+        The standard atomic weight of an element based on values from
+        NIST
 
     Raises
     ------
@@ -385,1020 +206,367 @@ def atomic_number(argument: str) -> str:
         If the argument does not correspond to a valid particle.
 
     TypeError
-        If the argument is not a string.
+        If the argument is not a string or integer.
 
     See also
     --------
 
-    mass_number : returns the mass number (the total number of protons
-        and neutrons) of an isotope.
+    isotope_mass : returns the atomic mass of an isotope.
+
+    ion_mass : returns the mass of an ion of an element or isotope,
+        accounting for reduction of mass from the neutral state due to
+        different numbers of electrons.
+
+    Notes
+    -----
+
+    Standard atomic weight data are most readily available for the
+    terrestrial environment, so this function may not be wholly
+    appropriate for space and astrophysical environments.
+
+    The relative abundances of different isotopes of an element
+    sometimes vary naturally in different locations within the
+    terrestrial environment.  The CIAAW provides ranges for these
+    element, which include H, Li, B, C, N, O, Mg, Si, S, Cl, Br, Tl.
+    This function provides a single value from the CIAWW 2015 standard
+    values when a single value is given, and the lower accuracy
+    conventional value given by Meija et al. (2013,
+    doi:10.1515/pac-2015-0305) for the elements where a range is
+    given.
 
     Examples
     --------
-    >>> atomic_number("H")
-    1
-    >>> atomic_number("tritium")
-    1
-    >>> atomic_number("alpha")
-    2
-    >>> atomic_number("oganesson")
-    118
+
+    >>> from astropy import units
+    >>> standard_atomic_weight("H")
+    <Quantity 1.008 u>
+    >>> # the following result accounts for small amount of deuterium
+    >>> standard_atomic_weight("H").to(units.kg)
+    <Quantity 1.67382335e-27 kg>
+    >>> isotope_mass("H-1")
+    <Quantity 1.00782503 u>
+    >>> standard_atomic_weight(82)
+    <Quantity 207.2 u>
+    >>> standard_atomic_weight("lead")
+    <Quantity 207.2 u>
 
     """
 
     try:
         element = atomic_symbol(argument)
-        atomic_numb = _Elements[element]['atomic_number']
-    except (InvalidElementError, KeyError):
-        raise InvalidElementError(
-            f"The argument {argument} to atomic_number does not correspond to "
-            "a valid element.") from None
     except InvalidParticleError:
-        raise InvalidParticleError(f"The argument {argument} to atomic_number "
-                                   "is not a valid particle.") from None
-    except TypeError:
-        raise TypeError(f"The argument {argument} to atomic number is not a "
-                        f"string.") from None
+        raise InvalidParticleError(f"{argument} is an invalid argument to "
+                                   "standard_atomic_weight.")
+    except InvalidElementError:
+        raise InvalidElementError(f"{argument} is not a valid element in "
+                                  "standard_atomic_weight.")
 
-    return atomic_numb
+    try:
+        charge_state(argument)
+    except ChargeError:
+        pass
+    else:
+        raise AtomicError("Use ion_mass to get masses of ions.")
+
+    try:
+        isotope_symbol(argument)
+    except InvalidIsotopeError:
+        pass
+    else:
+        raise AtomicError("Use isotope_mass to get masses of isotopes.")
+
+    try:
+        atomic_weight = _Elements[element]['atomic_mass']
+    except KeyError:
+        raise MissingAtomicDataError(
+            f"No standard atomic weight is available for {element}.")
 
+    return atomic_weight
 
-def is_isotope_stable(argument: Union[str, int],
-                      mass_numb: int = None) -> bool:
-    r"""Returns true for stable isotopes and false otherwise.
+
+def isotope_mass(argument: Union[str, int],
+                 mass_numb: int = None) -> Quantity:
+    r"""Return the mass of an isotope.
 
     Parameters
     ----------
 
-    argument: integer or string
-        A string representing an isotope or an integer representing an
-        atomic number
+    argument : string or integer
+        A string representing an element, isotope, or ion or an
+        integer representing an atomic number
 
-    mass_numb: integer
+    mass_numb : integer (optional)
         The mass number of the isotope.
 
     Returns
     -------
 
-    is_stable: boolean
-        True if the isotope is stable, False if it is unstable.
+    isotope_mass : Quantity
+        The atomic mass of a neutral atom of an isotope.
 
     Raises
     ------
 
     InvalidIsotopeError
-        If the arguments correspond to a valid particle but not a
-        valid isotope.
+        If the argument is a valid particle but not a valid isotope.
 
     InvalidParticleError
-        If the arguments do not correspond to a valid particle.
+        If the argument does not correspond to a valid particle.
 
-    TypeError
-        If the argument is not a string or integer.
+    AtomicError
+        If the charge of the particle is given, in which case ion_mass
+        should be used instead.
 
-    MissingAtomicDataError
-        If stability information is not available.
+    TypeError
+        If the argument is not a string.
 
-    Examples
+    See also
     --------
 
-    >>> is_isotope_stable("H-1")
-    True
-    >>> is_isotope_stable("tritium")
-    False
-
-    """
-
-    try:
-        isotope = isotope_symbol(argument, mass_numb)
-        is_stable = _Isotopes[isotope]['is_stable']
-    except InvalidIsotopeError:
-        raise InvalidIsotopeError("Invalid isotope in is_isotope_stable")
-    except InvalidParticleError:
-        raise InvalidParticleError("Invalid particle in is_isotope_stable")
-    except KeyError:
-        raise MissingAtomicDataError(f"No data on stability of {isotope}.")
-    except TypeError:
-        raise TypeError("The argument to is_isotope_stable must be a string.")
-
-    return is_stable
-
-
-def half_life(argument: Union[int, str], mass_numb: int = None) -> Quantity:
-    r"""Returns the half-life in seconds for unstable isotopes, and
-    numpy.inf for stable isotopes.
-
-    Parameters
-    ----------
-
-    argument: integer or string
-        A string representing an isotope or an integer representing an
-        atomic number
-
-    mass_numb: integer
-        The mass number of the isotope.
-
-    Returns
-    -------
-
-    half_life_sec: astropy Quantity
-        The half-life in units of seconds.
-
-    Raises:
-    -------
-
-    InvalidIsotopeError
-        If the argument is a valid particle but not a valid isotope.
-
-    InvalidParticleError
-        If the argument does not correspond to a valid particle
-        or contradictory information is provided.
+    standard_atomic_weight : returns atomic weight of an element based
+        on terrestrial abundances of isotopes
 
-    MissingAtomicDataError
-        If no half-life data is available for the isotope.
+    ion_mass : returns the mass of an ion of an element or isotope,
+        accounting for loss of electrons
 
-    TypeError
-        The argument is not an integer or string or the mass number is
-        not an integer.
+    Notes
+    -----
 
-    AtomicWarning
-        The half-life is unavailable so the routine returns None.
+    The masses of rare isotopes may be unavailable.
 
-    Notes:
-    ------
+    Examples
+    --------
 
-    At present there is limited half-life data available.
+    >>> from astropy import units as u
+    >>> isotope_mass("H-1")
+    <Quantity 1.00782503 u>
+    >>> isotope_mass("H-1").to(u.kg)
+    <Quantity 1.67353281e-27 kg>
+    >>> isotope_mass("He", 4)
+    <Quantity 4.00260325 u>
+    >>> isotope_mass(2, 4)
+    <Quantity 4.00260325 u>
 
-    Examples:
-    ---------
+    """
 
-    >>> half_life('T')
-    <Quantity 3.888e+08 s>
-    >>> half_life('n')
-    <Quantity 881.5 s>
-    >>> half_life('H-1')
-    <Quantity inf s>
+    argument, charge_state = _extract_charge_state(argument)
 
-    """
+    if charge_state is not None and charge_state != 0:
+        raise AtomicError("Use ion_mass instead of isotope_mass for masses of "
+                          "charged particles.")
 
     try:
-
         isotope = isotope_symbol(argument, mass_numb)
-
-        if _Isotopes[isotope]['is_stable']:
-            half_life_sec = np.inf * u.s
-        else:
-            half_life_sec = _Isotopes[isotope].get('half_life', None)
-
+        atomic_mass = _Isotopes[isotope]['atomic_mass']
     except InvalidParticleError:
-        raise InvalidParticleError("Invalid element in isotope_symbol.")
+        raise InvalidParticleError("Invalid particle in isotope_mass.")
     except InvalidIsotopeError:
-        raise InvalidIsotopeError(
-            "Cannot determine isotope information from these inputs to "
-            f"half_life: {argument}, {mass_numb}")
+        raise InvalidIsotopeError("Cannot identify isotope in isotope_mass.")
     except TypeError:
-        raise TypeError("Incorrect argument type for half_life")
-
-    if half_life_sec is None:
-        warnings.warn(f"The half-life for isotope {isotope} is not"
-                      "available; returning None.", MissingAtomicDataWarning)
+        raise TypeError("Invalid input to isotope_mass")
 
-    return half_life_sec
+    return atomic_mass
 
 
-def mass_number(isotope: str) -> int:
-    r"""Get the mass number (the number of protons and neutrons) of an
-    isotope.
+def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
+             mass_numb: int = None) -> Quantity:
+    r"""Returns the mass of an ion by finding the standard atomic
+    weight of an element or the atomic mass of an isotope, and then
+    accounting for the change in mass due to loss of electrons from
+    ionization.
 
     Parameters
     ----------
 
-    isotope : string
-        A string representing an isotope or a neutron.
+    argument: string, integer, or Quantity
+        A string representing an element, isotope, or ion; an integer
+        representing an atomic number; or a Quantity in units of mass
+        within the mass range that is appropriate for an ion.
+
+    Z: integer (optional)
+        The ionization state of the ion (defaulting to a charge of
+        Z=1)
+
+    mass_numb: integer (optional)
+        The mass number of an isotope.
 
     Returns
     -------
 
-    mass_number : integer
-       The total number of protons plus neutrons in a nuclide.
+    m_i: Quantity
+        The mass of a single ion of the isotope or element with charge
+        state Z.
 
     Raises
     ------
 
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
+    TypeError
+        The argument is not a string, integer, or Quantity.
 
-    InvalidIsotopeError
-        If the argument does not correspond to a valid isotope.
+    InvalidIonError
+        If the argument represents a particle other than an ion, the
+        ionization state exceeds the atomic number, or no isotope mass
+        or standard atomic weight is available.
 
-    TypeError
-        The argument is not a string.
+    AtomicWarning
+        If a mass was inputted and it is outside of the range of known
+        isotopes or electrons/positrons.
 
+    UnitConversionError
+        If the argument is a Quantity but does not have units of mass.
 
     See also
     --------
 
-    atomic_number : returns the number of protons in an isotope or
-        element
-
-    Examples
-    --------
+    standard_atomic_mass : returns the conventional atomic mass of an
+        element based on terrestrial values and assuming the atom is
+        neutral.
 
-    >>> mass_number("H-1")
-    1
-    >>> mass_number("Pb-208")
-    208
-    >>> mass_number("tritium")
-    3
-    >>> mass_number("n")
-    1
-    >>> mass_number("alpha")
-    4
+    isotope_mass : returns the mass of an isotope (if available)
+        assuming the atom is neutral.
 
-    """
+    Notes
+    -----
 
-    try:
-        isotope = isotope_symbol(isotope)
-        mass_numb = _Isotopes[isotope]["mass_number"]
-    except InvalidIsotopeError:
-        raise InvalidIsotopeError("Invalid isotope in mass_number.")
-    except InvalidParticleError:
-        raise InvalidParticleError(
-            f"The argument {isotope} to mass_number does not "
-            "correspond to a valid isotope.") from None
-    except TypeError:
-        raise TypeError("The argument to mass_number is not a string.")
+    This function in general finds the mass of an isotope (or the
+    standard atomic weight based on terrestrial values if a unique
+    isotope cannot be identified), and then substracts the mass of Z
+    electrons.  If Z is not provided as an input, then this function
+    assumes that the ion is singly ionized.
 
-    return mass_numb
+    Specific values are returns for protons, deuterons, tritons, alpha
+    particles, and positrons.
 
+    Calling ion_mass('H') does not return the mass of a proton but
+    instead uses hydrogen's standard atomic weight based on
+    terrestrial values.  To get the mass of a proton, use
+    ion_mass('p').
 
-def element_name(argument: Union[str, int]) -> str:
-    r"""Returns the name of an element.
+    This function can accept a Quantity in units of mass.  If the
+    Quantity is close to the mass of an electron or positron, it will
+    return the mass of an electron or positron to full known
+    precision.  If the Quantity is within the mass range of known
+    isotopes, it will return the mass that is inputted to it.
 
-    Parameters
-    ----------
+    Examples
+    --------
 
-    argument : string or integer
-        A string representing an element, isotope, or ion or an
-        integer representing an atomic number
+    >>> print(ion_mass('p').si.value)
+    1.672621898e-27
+    >>> ion_mass('H+')  # assumes terrestrial abundance of D
+    <Quantity 1.67291241e-27 kg>
+    >>> ion_mass('H+') == ion_mass('p')
+    False
+    >>> ion_mass('H-1') == ion_mass('p')
+    True
+    >>> ion_mass('P+')  # phosphorus
+    <Quantity 5.14322301e-26 kg>
+    >>> ion_mass('He-4', Z=2)
+    <Quantity 6.64465709e-27 kg>
+    >>> ion_mass('T+')
+    <Quantity 5.00735666e-27 kg>
+    >>> ion_mass(26, Z=1, mass_numb=56)
+    <Quantity 9.28812345e-26 kg>
+    >>> ion_mass('Fe-56 1+')
+    <Quantity 9.28812345e-26 kg>
+    >>> ion_mass(9.11e-31*u.kg).si.value
+    9.10938356e-31
+    >>> ion_mass(1.67e-27*u.kg)
+    <Quantity 1.67e-27 kg>
 
-    Returns
-    -------
+    """
 
-    name : string
-        The name of the element.
-
-    Raises
-    ------
-
-    InvalidElementError
-        If the argument is a valid particle but not a valid element.
-
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
-
-    TypeError
-        If the argument is not a string or integer.
-
-    See also
-    --------
-
-    atomic_symbol : returns the atomic symbol
-
-    isotope_symbol : returns the symbol of an isotope
-
-    Examples
-    --------
-
-    >>> element_name("H")
-    'hydrogen'
-    >>> element_name("T")
-    'hydrogen'
-    >>> element_name("alpha")
-    'helium'
-    >>> element_name(42)
-    'molybdenum'
-    >>> element_name("C-12")
-    'carbon'
-
-    """
-
-    try:
-        element = atomic_symbol(argument)
-        name = _Elements[element]["name"]
-    except InvalidElementError:
-        raise InvalidElementError("Invalid element in element_name.")
-    except InvalidParticleError:
-        raise InvalidParticleError("Invalid particle in element_name.")
-    except TypeError:
-        raise TypeError("Invalid input to element_name.")
-
-    return name
-
-
-def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
-    r"""Returns the standard (conventional) atomic weight of an element
-    based on the relative abundances of isotopes in terrestrial
-    environments.
-
-    Parameters
-    ----------
-
-    argument: string or integer
-        A string representing an element or an integer representing an
-        atomic number
-
-    Returns
-    -------
-
-    atomic_weight: astropy.units.Quantity with units of u
-        The standard atomic weight of an element based on values from
-        NIST
-
-    Raises
-    ------
-
-    InvalidElementError
-        If the argument is a valid particle but not a valid element.
-
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
-
-    TypeError
-        If the argument is not a string or integer.
-
-    See also
-    --------
-
-    isotope_mass : returns the atomic mass of an isotope.
-
-    ion_mass : returns the mass of an ion of an element or isotope,
-        accounting for reduction of mass from the neutral state due to
-        different numbers of electrons.
-
-    Notes
-    -----
-
-    Standard atomic weight data are most readily available for the
-    terrestrial environment, so this function may not be wholly
-    appropriate for space and astrophysical environments.
-
-    The relative abundances of different isotopes of an element
-    sometimes vary naturally in different locations within the
-    terrestrial environment.  The CIAAW provides ranges for these
-    element, which include H, Li, B, C, N, O, Mg, Si, S, Cl, Br, Tl.
-    This function provides a single value from the CIAWW 2015 standard
-    values when a single value is given, and the lower accuracy
-    conventional value given by Meija et al. (2013,
-    doi:10.1515/pac-2015-0305) for the elements where a range is
-    given.
-
-    Examples
-    --------
-
-    >>> from astropy import units
-    >>> standard_atomic_weight("H")
-    <Quantity 1.008 u>
-    >>> # the following result accounts for small amount of deuterium
-    >>> standard_atomic_weight("H").to(units.kg)
-    <Quantity 1.67382335e-27 kg>
-    >>> isotope_mass("H-1")
-    <Quantity 1.00782503 u>
-    >>> standard_atomic_weight(82)
-    <Quantity 207.2 u>
-    >>> standard_atomic_weight("lead")
-    <Quantity 207.2 u>
-
-    """
-
-    try:
-        element = atomic_symbol(argument)
-    except InvalidParticleError:
-        raise InvalidParticleError(f"{argument} is an invalid argument to "
-                                   "standard_atomic_weight.")
-    except InvalidElementError:
-        raise InvalidElementError(f"{argument} is not a valid element in "
-                                  "standard_atomic_weight.")
-
-    try:
-        charge_state(argument)
-    except ChargeError:
-        pass
-    else:
-        raise AtomicError("Use ion_mass to get masses of ions.")
-
-    try:
-        isotope_symbol(argument)
-    except InvalidIsotopeError:
-        pass
-    else:
-        raise AtomicError("Use isotope_mass to get masses of isotopes.")
-
-    try:
-        atomic_weight = _Elements[element]['atomic_mass']
-    except KeyError:
-        raise MissingAtomicDataError(
-            f"No standard atomic weight is available for {element}.")
-
-    return atomic_weight
-
-
-def isotope_mass(argument: Union[str, int],
-                 mass_numb: int = None) -> Quantity:
-    r"""Return the mass of an isotope.
-
-    Parameters
-    ----------
-
-    argument : string or integer
-        A string representing an element, isotope, or ion or an
-        integer representing an atomic number
-
-    mass_numb : integer (optional)
-        The mass number of the isotope.
-
-    Returns
-    -------
-
-    isotope_mass : Quantity
-        The atomic mass of a neutral atom of an isotope.
-
-    Raises
-    ------
-
-    InvalidIsotopeError
-        If the argument is a valid particle but not a valid isotope.
-
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
-
-    AtomicError
-        If the charge of the particle is given, in which case ion_mass
-        should be used instead.
-
-    TypeError
-        If the argument is not a string.
-
-    See also
-    --------
-
-    standard_atomic_weight : returns atomic weight of an element based
-        on terrestrial abundances of isotopes
-
-    ion_mass : returns the mass of an ion of an element or isotope,
-        accounting for loss of electrons
-
-    Notes
-    -----
-
-    The masses of rare isotopes may be unavailable.
-
-    Examples
-    --------
-
-    >>> from astropy import units as u
-    >>> isotope_mass("H-1")
-    <Quantity 1.00782503 u>
-    >>> isotope_mass("H-1").to(u.kg)
-    <Quantity 1.67353281e-27 kg>
-    >>> isotope_mass("He", 4)
-    <Quantity 4.00260325 u>
-    >>> isotope_mass(2, 4)
-    <Quantity 4.00260325 u>
-
-    """
-
-    argument, charge_state = _extract_charge_state(argument)
-
-    if charge_state is not None and charge_state != 0:
-        raise AtomicError("Use ion_mass instead of isotope_mass for masses of "
-                          "charged particles.")
-
-    try:
-        isotope = isotope_symbol(argument, mass_numb)
-        atomic_mass = _Isotopes[isotope]['atomic_mass']
-    except InvalidParticleError:
-        raise InvalidParticleError("Invalid particle in isotope_mass.")
-    except InvalidIsotopeError:
-        raise InvalidIsotopeError("Cannot identify isotope in isotope_mass.")
-    except TypeError:
-        raise TypeError("Invalid input to isotope_mass")
-
-    return atomic_mass
-
-
-def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
-             mass_numb: int = None) -> Quantity:
-    r"""Returns the mass of an ion by finding the standard atomic
-    weight of an element or the atomic mass of an isotope, and then
-    accounting for the change in mass due to loss of electrons from
-    ionization.
-
-    Parameters
-    ----------
-
-    argument: string, integer, or Quantity
-        A string representing an element, isotope, or ion; an integer
-        representing an atomic number; or a Quantity in units of mass
-        within the mass range that is appropriate for an ion.
-
-    Z: integer (optional)
-        The ionization state of the ion (defaulting to a charge of
-        Z=1)
-
-    mass_numb: integer (optional)
-        The mass number of an isotope.
-
-    Returns
-    -------
-
-    m_i: Quantity
-        The mass of a single ion of the isotope or element with charge
-        state Z.
-
-    Raises
-    ------
-
-    TypeError
-        The argument is not a string, integer, or Quantity.
-
-    InvalidIonError
-        If the argument represents a particle other than an ion, the
-        ionization state exceeds the atomic number, or no isotope mass
-        or standard atomic weight is available.
-
-    AtomicWarning
-        If a mass was inputted and it is outside of the range of known
-        isotopes or electrons/positrons.
-
-    UnitConversionError
-        If the argument is a Quantity but does not have units of mass.
-
-    See also
-    --------
-
-    standard_atomic_mass : returns the conventional atomic mass of an
-        element based on terrestrial values and assuming the atom is
-        neutral.
-
-    isotope_mass : returns the mass of an isotope (if available)
-        assuming the atom is neutral.
-
-    Notes
-    -----
-
-    This function in general finds the mass of an isotope (or the
-    standard atomic weight based on terrestrial values if a unique
-    isotope cannot be identified), and then substracts the mass of Z
-    electrons.  If Z is not provided as an input, then this function
-    assumes that the ion is singly ionized.
-
-    Specific values are returns for protons, deuterons, tritons, alpha
-    particles, and positrons.
-
-    Calling ion_mass('H') does not return the mass of a proton but
-    instead uses hydrogen's standard atomic weight based on
-    terrestrial values.  To get the mass of a proton, use
-    ion_mass('p').
-
-    This function can accept a Quantity in units of mass.  If the
-    Quantity is close to the mass of an electron or positron, it will
-    return the mass of an electron or positron to full known
-    precision.  If the Quantity is within the mass range of known
-    isotopes, it will return the mass that is inputted to it.
-
-    Examples
-    --------
-
-    >>> print(ion_mass('p').si.value)
-    1.672621898e-27
-    >>> ion_mass('H+')  # assumes terrestrial abundance of D
-    <Quantity 1.67291241e-27 kg>
-    >>> ion_mass('H+') == ion_mass('p')
-    False
-    >>> ion_mass('H-1') == ion_mass('p')
-    True
-    >>> ion_mass('P+')  # phosphorus
-    <Quantity 5.14322301e-26 kg>
-    >>> ion_mass('He-4', Z=2)
-    <Quantity 6.64465709e-27 kg>
-    >>> ion_mass('T+')
-    <Quantity 5.00735666e-27 kg>
-    >>> ion_mass(26, Z=1, mass_numb=56)
-    <Quantity 9.28812345e-26 kg>
-    >>> ion_mass('Fe-56 1+')
-    <Quantity 9.28812345e-26 kg>
-    >>> ion_mass(9.11e-31*u.kg).si.value
-    9.10938356e-31
-    >>> ion_mass(1.67e-27*u.kg)
-    <Quantity 1.67e-27 kg>
-
-    """
-
-    if isinstance(argument, Quantity) and Z is None and mass_numb is None:
-
-        try:
-            m_i = argument.to(u.kg)
-        except Exception:
-            raise u.UnitConversionError("If the ion in given as a Quantity, "
-                                        "then it must have units of mass.")
-
-        if np.isclose(m_i.value, const.m_e.value, atol=1e-33):  # positrons
-            return const.m_e
-        elif 1.66e-27 <= m_i.value < 7e-25:  # mass range of known isotopes
-            return m_i
-        else:
-            warnings.warn(
-                "The mass that was inputted to ion_mass and is being returned"
-                " from ion_mass is outside of the range of known isotopes or "
-                "electrons/ions.", AtomicWarning)
-            return m_i
-
-    if _is_electron(argument) or _is_positron(argument):
-        return const.m_e
-    elif _is_proton(argument, Z, mass_numb) or _is_antiproton(argument):
-        return const.m_p
-    elif _is_neutron(argument, mass_numb):
-        raise InvalidIonError("Use isotope_mass or m_n to get mass of neutron")
-
-    if isinstance(argument, str):
-        argument, Z_from_arg = _extract_charge_state(argument)
-    else:
-        Z_from_arg = None
-
-    if Z is None and Z_from_arg is None:
-        Z = 1
-    elif Z is not None and Z_from_arg is not None and Z != Z_from_arg:
-        raise InvalidIonError("Inconsistent charge state information in"
-                              "ion_mass.")
-    elif Z is None and Z_from_arg is not None:
-        Z = Z_from_arg
-
-    if isinstance(Z, str) and Z.isdigit():
-        Z = int(Z)
-    if isinstance(mass_numb, str) and mass_numb.isdigit():
-        mass_numb = int(mass_numb)
-
-    if not isinstance(Z, int):
-        raise TypeError("In ion_mass, Z must be an integer representing the "
-                        "ionization state (e.g., Z=1 for singly ionized).")
-
-    if not isinstance(mass_numb, int) and mass_numb is not None:
-        raise TypeError("In ion_mass, mass_numb must be an integer "
-                        "representing the mass number of an isotope.")
-
-    if atomic_number(argument) < Z:
-        raise InvalidParticleError("The ionization state cannot exceed the "
-                                   "atomic number in ion_mass")
-
-    try:
-        isotope = isotope_symbol(argument, mass_numb)
-    except Exception:
-        is_isotope = False
-    else:
-        is_isotope = True
-
-    if is_isotope:
-
-        if isotope == 'H-1' and Z == 1:
-            return const.m_p
-        elif isotope == 'D' and Z == 1:
-            return 3.343583719e-27 * u.kg
-        elif isotope == 'T' and Z == 1:
-            return 5.007356665e-27 * u.kg
-
-        atomic_mass = isotope_mass(isotope)
-
-    else:
-
-        try:
-            atomic_mass = standard_atomic_weight(argument)
-        except Exception:  # coveralls: ignore
-
-            errormessage = ("No isotope mass or standard atomic weight is "
-                            f"available to get ion mass for {argument}")
-
-            if isinstance(mass_numb, int):
-                errormessage += f" with mass number {mass_numb}"
-
-            raise
-
-    m_i = (atomic_mass - Z * const.m_e).to(u.kg)
-
-    return m_i
-
-
-def known_isotopes(argument: Union[str, int] = None) -> List[str]:
-    r"""Returns a list of all known isotopes of an element, or a list
-    of all known isotopes of every element if no input is provided.
-
-    Parameters
-    ----------
-
-    argument: integer or string, optional
-        A string representing an element, isotope, or ion or an
-        integer representing an atomic number
-
-    Returns
-    -------
-
-    isotopes_list: list of strings or empty list
-        List of all of the isotopes of an element that have been
-        discovered, sorted from lowest mass number to highest mass
-        number.  If no argument is provided, then a list of all known
-        isotopes of every element will be returned that is sorted by
-        atomic number, with entries for each element sorted by mass
-        number.
-
-    Raises
-    ------
-
-    InvalidElementError
-        If the argument is a valid particle but not a valid element.
-
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
-
-    TypeError
-        If the argument is not a string or integer.
-
-    Notes
-    -----
-
-    This list returns both natural and artifically produced isotopes.
-
-    See also
-    --------
-
-    common_isotopes : returns isotopes with non-zero isotopic
-        abundances
-
-    stable_isotopes : returns isotopes that are stable against
-        radioactive decay
-
-    Examples
-    --------
-    >>> known_isotopes('H')
-    ['H-1', 'D', 'T', 'H-4', 'H-5', 'H-6', 'H-7']
-    >>> known_isotopes('helium 1+')
-    ['He-3', 'He-4', 'He-5', 'He-6', 'He-7', 'He-8', 'He-9', 'He-10']
-    >>> known_isotopes()[0:10]
-    ['H-1', 'D', 'T', 'H-4', 'H-5', 'H-6', 'H-7', 'He-3', 'He-4', 'He-5']
-    >>> len(known_isotopes())
-    3352
-
-    """
-
-    def known_isotopes_for_element(argument):
-        element = atomic_symbol(argument)
-        isotopes = []
-        for isotope in _Isotopes.keys():
-            if element + '-' in isotope and isotope[0:len(element)] == element:
-                isotopes.append(isotope)
-        if element == 'H':
-            isotopes.insert(1, 'D')
-            isotopes.insert(2, 'T')
-        mass_numbers = [mass_number(isotope) for isotope in isotopes]
-        sorted_isotopes = [mass_number for (isotope, mass_number) in
-                           sorted(zip(mass_numbers, isotopes))]
-        return sorted_isotopes
-
-    if argument is not None:
-        try:
-            element = atomic_symbol(argument)
-            isotopes_list = known_isotopes_for_element(element)
-        except InvalidElementError:
-            raise InvalidElementError("known_isotopes is unable to get "
-                                      f"isotopes from an input of: {argument}")
-        except InvalidParticleError:
-            raise InvalidParticleError("Invalid particle in known_isotopes.")
-    elif argument is None:
-        isotopes_list = []
-        for atomic_numb in range(1, 119):
-            isotopes_list += known_isotopes_for_element(atomic_numb)
-
-    return isotopes_list
-
-
-def common_isotopes(argument: Union[str, int] = None,
-                    most_common_only: bool = False) -> List[str]:
-    r"""Returns a list of isotopes of an element with an isotopic
-    abundances greater than zero, or if no input is provided, a list
-    of all such isotopes for every element.
-
-    Parameters
-    ----------
-
-    argument: integer or string, optional
-        A string or integer representing an atomic number or element,
-        or a string represnting an isotope.
-
-    most_common_only: boolean
-        If set to True, return only the most common isotope
-
-    Returns
-    -------
-
-    isotopes_list: list of strings or empty list
-        List of all isotopes of an element with isotopic abundances
-        greater than zero, sorted from most abundant to least
-        abundant.  If no isotopes have isotopic abundances greater
-        than zero, this function will return an empty list.  If no
-        arguments are provided, then a list of all common isotopes of
-        all elements will be provided that is sorted by atomic number,
-        with entries for each element sorted from most abundant to
-        least abundant.
-
-    Raises
-    ------
-
-    InvalidElementError
-        If the argument is a valid particle but not a valid element.
-
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
-
-    TypeError
-        If the argument is not a string or integer.
-
-    Notes
-    -----
-
-    The isotopic abundances are based on the terrestrial environment
-    and may not be wholly appropriate for space and astrophysical
-    applications.
-
-    See also
-    --------
-
-    known_isotopes : returns a list of isotopes that have been
-        discovered
-
-    stable_isotopes : returns isotopes that are stable against
-        radioactive decay
-
-    isotopic_abundance : returns the relative isotopic abundance
-
-    Examples
-    --------
-
-    >>> common_isotopes('H')
-    ['H-1', 'D']
-    >>> common_isotopes(44)
-    ['Ru-102', 'Ru-104', 'Ru-101', 'Ru-99', 'Ru-100', 'Ru-96', 'Ru-98']
-    >>> common_isotopes('beryllium 2+')
-    ['Be-9']
-    >>> common_isotopes('Fe')
-    ['Fe-56', 'Fe-54', 'Fe-57', 'Fe-58']
-    >>> common_isotopes('Fe', most_common_only=True)
-    ['Fe-56']
-    >>> common_isotopes()[0:7]
-    ['H-1', 'D', 'He-4', 'He-3', 'Li-7', 'Li-6', 'Be-9']
-
-    """
-
-    def common_isotopes_for_element(argument: Union[str, int],
-                                    most_common_only: Optional[bool])\
-            -> List[str]:
-
-        isotopes = known_isotopes(argument)
-        CommonIsotopes = [isotope for isotope in isotopes if
-                          'isotopic_abundance' in _Isotopes[isotope].keys()]
-        isotopic_abundances = [_Isotopes[isotope]['isotopic_abundance']
-                               for isotope in CommonIsotopes]
-        sorted_isotopes = [iso_comp for (isotope, iso_comp) in
-                           sorted(zip(isotopic_abundances, CommonIsotopes))]
-
-        sorted_isotopes.reverse()
-
-        if most_common_only and len(sorted_isotopes) > 1:
-            sorted_isotopes = sorted_isotopes[0:1]
-
-        return sorted_isotopes
-
-    if argument is not None:
+    if isinstance(argument, Quantity) and Z is None and mass_numb is None:
 
         try:
-            element = atomic_symbol(argument)
-            isotopes_list = \
-                common_isotopes_for_element(element, most_common_only)
-        except InvalidParticleError:
-            raise InvalidParticleError("Invalid particle")
-        except InvalidElementError:
-            raise InvalidElementError(
-                "common_isotopes is unable to get isotopes "
-                f"from an input of: {argument}")
-
-    elif argument is None:
-        isotopes_list = []
-        for atomic_numb in range(1, 119):
-            isotopes_list += \
-                common_isotopes_for_element(atomic_numb, most_common_only)
-
-    return isotopes_list
-
-
-def stable_isotopes(argument: Union[str, int] = None,
-                    unstable: bool = False) -> List[str]:
-    r"""Returns a list of all stable isotopes of an element, or if no
-    input is provided, a list of all such isotopes for every element.
-
-    Parameters
-    ----------
-
-    argument: integer or string
-        A string or integer representing an atomic number or element,
-        or a string represnting an isotope.
-
-    unstable: boolean
-        If set to True, this function will return a list of the
-        unstable isotopes instead of the stable isotopes.
+            m_i = argument.to(u.kg)
+        except Exception:
+            raise u.UnitConversionError("If the ion in given as a Quantity, "
+                                        "then it must have units of mass.")
 
-    Returns
-    -------
+        if np.isclose(m_i.value, const.m_e.value, atol=1e-33):  # positrons
+            return const.m_e
+        elif 1.66e-27 <= m_i.value < 7e-25:  # mass range of known isotopes
+            return m_i
+        else:
+            warnings.warn(
+                "The mass that was inputted to ion_mass and is being returned"
+                " from ion_mass is outside of the range of known isotopes or "
+                "electrons/ions.", AtomicWarning)
+            return m_i
 
-    StableIsotopes: list of strings or empty list
-        List of all stable isotopes of an element, sorted from lowest
-        mass number.  If an element has no stable isotopes, this
-        function returns an empty list.
+    if _is_electron(argument) or _is_positron(argument):
+        return const.m_e
+    elif _is_proton(argument, Z, mass_numb) or _is_antiproton(argument):
+        return const.m_p
+    elif _is_neutron(argument, mass_numb):
+        raise InvalidIonError("Use isotope_mass or m_n to get mass of neutron")
 
-    Raises
-    ------
+    if isinstance(argument, str):
+        argument, Z_from_arg = _extract_charge_state(argument)
+    else:
+        Z_from_arg = None
 
-    InvalidElementError
-        If the argument is a valid particle but not a valid element.
+    if Z is None and Z_from_arg is None:
+        Z = 1
+    elif Z is not None and Z_from_arg is not None and Z != Z_from_arg:
+        raise InvalidIonError("Inconsistent charge state information in"
+                              "ion_mass.")
+    elif Z is None and Z_from_arg is not None:
+        Z = Z_from_arg
 
-    InvalidParticleError
-        If the argument does not correspond to a valid particle.
+    if isinstance(Z, str) and Z.isdigit():
+        Z = int(Z)
+    if isinstance(mass_numb, str) and mass_numb.isdigit():
+        mass_numb = int(mass_numb)
 
-    TypeError
-        If the argument is not a string or integer.
+    if not isinstance(Z, int):
+        raise TypeError("In ion_mass, Z must be an integer representing the "
+                        "ionization state (e.g., Z=1 for singly ionized).")
 
-    Notes
-    -----
+    if not isinstance(mass_numb, int) and mass_numb is not None:
+        raise TypeError("In ion_mass, mass_numb must be an integer "
+                        "representing the mass number of an isotope.")
 
-    There are 254 isotopes for which no radioactive decay has been
-    observed.  It is possible that some isotopes will be discovered to
-    be unstable but with extremely long half-lives.  For example,
-    bismuth-209 was recently discovered to have a half-life of about
-    1.9e19 years.  However, such isotopes can be regarded as virtually
-    stable for most applications.
+    if atomic_number(argument) < Z:
+        raise InvalidParticleError("The ionization state cannot exceed the "
+                                   "atomic number in ion_mass")
 
-    See also
-    --------
+    try:
+        isotope = isotope_symbol(argument, mass_numb)
+    except Exception:
+        is_isotope = False
+    else:
+        is_isotope = True
 
-    known_isotopes : returns a list of isotopes that have been
-        discovered
+    if is_isotope:
 
-    common_isotopes : returns isotopes with non-zero isotopic
-        abundances
+        if isotope == 'H-1' and Z == 1:
+            return const.m_p
+        elif isotope == 'D' and Z == 1:
+            return 3.343583719e-27 * u.kg
+        elif isotope == 'T' and Z == 1:
+            return 5.007356665e-27 * u.kg
 
-    Examples
-    --------
+        atomic_mass = isotope_mass(isotope)
 
-    >>> stable_isotopes('H')
-    ['H-1', 'D']
-    >>> stable_isotopes(44)
-    ['Ru-96', 'Ru-98', 'Ru-99', 'Ru-100', 'Ru-101', 'Ru-102', 'Ru-104']
-    >>> stable_isotopes('beryllium')
-    ['Be-9']
-    >>> stable_isotopes('Pb-209')
-    ['Pb-204', 'Pb-206', 'Pb-207', 'Pb-208']
-    >>> stable_isotopes(118)
-    []
+    else:
 
-    Find unstable isotopes
+        try:
+            atomic_mass = standard_atomic_weight(argument)
+        except Exception:  # coveralls: ignore
 
-    >>> stable_isotopes('U', unstable=True)[:5] # only first five
-    ['U-217', 'U-218', 'U-219', 'U-220', 'U-221']
+            errormessage = ("No isotope mass or standard atomic weight is "
+                            f"available to get ion mass for {argument}")
 
-    """
+            if isinstance(mass_numb, int):
+                errormessage += f" with mass number {mass_numb}"
 
-    def stable_isotopes_for_element(argument: Union[str, int],
-                                    stable_only: Optional[bool]) -> List[str]:
-        KnownIsotopes = known_isotopes(argument)
-        StableIsotopes = [isotope for isotope in KnownIsotopes if
-                          _Isotopes[isotope]['is_stable'] == stable_only]
-        return StableIsotopes
+            raise
 
-    if argument is not None:
-        try:
-            element = atomic_symbol(argument)
-            isotopes_list = \
-                stable_isotopes_for_element(element, not unstable)
-        except InvalidParticleError:
-            raise InvalidParticleError("Invalid particle in stable_isotopes")
-        except Exception:
-            raise InvalidElementError(
-                "stable_isotopes is unable to get isotopes "
-                f"from an input of: {argument}")
-    elif argument is None:
-        isotopes_list = []
-        for atomic_numb in range(1, 119):
-            isotopes_list += \
-                stable_isotopes_for_element(atomic_numb, not unstable)
+    m_i = (atomic_mass - Z * const.m_e).to(u.kg)
 
-    return isotopes_list
+    return m_i
 
 
 def isotopic_abundance(argument: Union[str, int],
@@ -1635,257 +803,457 @@ def electric_charge(particle: str) -> Quantity:
     return charge
 
 
-def _extract_charge_state(argument: str) -> Tuple[str, int]:
-    r"""Splits strings containing element or isotope and charge state
-    information into a string without the charge state information and
-    the charge state as an integer (or None if no charge state
-    information is given).
+def is_isotope_stable(argument: Union[str, int],
+                      mass_numb: int = None) -> bool:
+    r"""Returns true for stable isotopes and false otherwise.
+
+    Parameters
+    ----------
+
+    argument: integer or string
+        A string representing an isotope or an integer representing an
+        atomic number
+
+    mass_numb: integer
+        The mass number of the isotope.
+
+    Returns
+    -------
+
+    is_stable: boolean
+        True if the isotope is stable, False if it is unstable.
+
+    Raises
+    ------
+
+    InvalidIsotopeError
+        If the arguments correspond to a valid particle but not a
+        valid isotope.
+
+    InvalidParticleError
+        If the arguments do not correspond to a valid particle.
+
+    TypeError
+        If the argument is not a string or integer.
+
+    MissingAtomicDataError
+        If stability information is not available.
+
+    Examples
+    --------
+
+    >>> is_isotope_stable("H-1")
+    True
+    >>> is_isotope_stable("tritium")
+    False
+
+    """
+
+    try:
+        isotope = isotope_symbol(argument, mass_numb)
+        is_stable = _Isotopes[isotope]['is_stable']
+    except InvalidIsotopeError:
+        raise InvalidIsotopeError("Invalid isotope in is_isotope_stable")
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid particle in is_isotope_stable")
+    except KeyError:
+        raise MissingAtomicDataError(f"No data on stability of {isotope}.")
+    except TypeError:
+        raise TypeError("The argument to is_isotope_stable must be a string.")
+
+    return is_stable
+
+
+def half_life(argument: Union[int, str], mass_numb: int = None) -> Quantity:
+    r"""Returns the half-life in seconds for unstable isotopes, and
+    numpy.inf for stable isotopes.
+
+    Parameters
+    ----------
+
+    argument: integer or string
+        A string representing an isotope or an integer representing an
+        atomic number
+
+    mass_numb: integer
+        The mass number of the isotope.
+
+    Returns
+    -------
+
+    half_life_sec: astropy Quantity
+        The half-life in units of seconds.
+
+    Raises:
+    -------
+
+    InvalidIsotopeError
+        If the argument is a valid particle but not a valid isotope.
+
+    InvalidParticleError
+        If the argument does not correspond to a valid particle
+        or contradictory information is provided.
+
+    MissingAtomicDataError
+        If no half-life data is available for the isotope.
+
+    TypeError
+        The argument is not an integer or string or the mass number is
+        not an integer.
+
+    AtomicWarning
+        The half-life is unavailable so the routine returns None.
+
+    Notes:
+    ------
+
+    At present there is limited half-life data available.
+
+    Examples:
+    ---------
+
+    >>> half_life('T')
+    <Quantity 3.888e+08 s>
+    >>> half_life('n')
+    <Quantity 881.5 s>
+    >>> half_life('H-1')
+    <Quantity inf s>
+
+    """
+
+    try:
+
+        isotope = isotope_symbol(argument, mass_numb)
+
+        if _Isotopes[isotope]['is_stable']:
+            half_life_sec = np.inf * u.s
+        else:
+            half_life_sec = _Isotopes[isotope].get('half_life', None)
+
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid element in isotope_symbol.")
+    except InvalidIsotopeError:
+        raise InvalidIsotopeError(
+            "Cannot determine isotope information from these inputs to "
+            f"half_life: {argument}, {mass_numb}")
+    except TypeError:
+        raise TypeError("Incorrect argument type for half_life")
+
+    if half_life_sec is None:
+        warnings.warn(f"The half-life for isotope {isotope} is not"
+                      "available; returning None.", MissingAtomicDataWarning)
+
+    return half_life_sec
+
+
+def known_isotopes(argument: Union[str, int] = None) -> List[str]:
+    r"""Returns a list of all known isotopes of an element, or a list
+    of all known isotopes of every element if no input is provided.
 
     Parameters
     ----------
 
-    argument : string
-        String containing information for an element or isotope in any
-        of the allowed formats, followed by charge state information.
+    argument: integer or string, optional
+        A string representing an element, isotope, or ion or an
+        integer representing an atomic number
 
     Returns
     -------
 
-    argument : string
-        The original string with charge state information removed.
-
-    Z : integer
-        The charge state of an ion (e.g., this will return 1 if one
-        electron has been removed and -1 if one electron has been
-        gained)
+    isotopes_list: list of strings or empty list
+        List of all of the isotopes of an element that have been
+        discovered, sorted from lowest mass number to highest mass
+        number.  If no argument is provided, then a list of all known
+        isotopes of every element will be returned that is sorted by
+        atomic number, with entries for each element sorted by mass
+        number.
 
     Raises
     ------
 
+    InvalidElementError
+        If the argument is a valid particle but not a valid element.
+
     InvalidParticleError
-        If invalid charge information is included.
+        If the argument does not correspond to a valid particle.
+
+    TypeError
+        If the argument is not a string or integer.
 
     Notes
     -----
 
-    If the argument is not a string, this function will return the
-    original argument and None.
-
-    This function does not check that the charge state is valid.
+    This list returns both natural and artifically produced isotopes.
 
-    Examples
+    See also
     --------
 
-    >>> isotope, Z = _extract_charge_state('Fe-56+++')
-    >>> print(isotope)
-    Fe-56
-    >>> print(Z)
-    3
-    >>> _extract_charge_state('D +1')
-    ('D', 1)
-
-    """
-
-    if not isinstance(argument, str):
-        return argument, None
-
-    argument = _get_standard_symbol(argument)
-
-    if argument in ['n', 'antineutron'] or 'nu_' in argument:
-        return argument, 0
-    if argument in ['e-', 'mu-', 'tau-', 'p-']:
-        return argument, -1
-    elif argument in ['e+', 'mu+', 'tau+', 'p']:
-        return argument, 1
-
-    if argument.lower() == 'alpha':
-        return 'He-4', 1
-    elif 'alpha' in argument.lower():
-        raise InvalidParticleError("Invalid representation of an alpha"
-                                   " particle")
-
-    if argument.count(' ') == 1:  # For cases like 'Fe +2' and 'Fe-56 2+'
+    common_isotopes : returns isotopes with non-zero isotopic
+        abundances
 
-        argument, ion_info = argument.split()
+    stable_isotopes : returns isotopes that are stable against
+        radioactive decay
 
-        check1 = (ion_info.endswith(('-', '+')) ^
-                  ion_info.startswith(('-', '+')))
+    Examples
+    --------
+    >>> known_isotopes('H')
+    ['H-1', 'D', 'T', 'H-4', 'H-5', 'H-6', 'H-7']
+    >>> known_isotopes('helium 1+')
+    ['He-3', 'He-4', 'He-5', 'He-6', 'He-7', 'He-8', 'He-9', 'He-10']
+    >>> known_isotopes()[0:10]
+    ['H-1', 'D', 'T', 'H-4', 'H-5', 'H-6', 'H-7', 'He-3', 'He-4', 'He-5']
+    >>> len(known_isotopes())
+    3352
 
-        check2 = ((ion_info.count('-') == 1 and ion_info.count('+') == 0) or
-                  (ion_info.count('-') == 0 and ion_info.count('+') == 1))
+    """
 
-        if '-' in ion_info:
-            sign = -1
-            charge = ion_info.replace('-', '')
-        elif '+' in ion_info:
-            sign = +1
-            charge = ion_info.replace('+', '')
+    def known_isotopes_for_element(argument):
+        element = atomic_symbol(argument)
+        isotopes = []
+        for isotope in _Isotopes.keys():
+            if element + '-' in isotope and isotope[0:len(element)] == element:
+                isotopes.append(isotope)
+        if element == 'H':
+            isotopes.insert(1, 'D')
+            isotopes.insert(2, 'T')
+        mass_numbers = [mass_number(isotope) for isotope in isotopes]
+        sorted_isotopes = [mass_number for (isotope, mass_number) in
+                           sorted(zip(mass_numbers, isotopes))]
+        return sorted_isotopes
 
+    if argument is not None:
         try:
-            charge_state = sign * int(charge)
-            check3 = True
-        except Exception:
-            check3 = False
-
-        if not (check1 and check2 and check3):
-            raise InvalidParticleError(
-                "The following input does not have valid charge: "
-                f"state information: {argument}{ion_info}")
+            element = atomic_symbol(argument)
+            isotopes_list = known_isotopes_for_element(element)
+        except InvalidElementError:
+            raise InvalidElementError("known_isotopes is unable to get "
+                                      f"isotopes from an input of: {argument}")
+        except InvalidParticleError:
+            raise InvalidParticleError("Invalid particle in known_isotopes.")
+    elif argument is None:
+        isotopes_list = []
+        for atomic_numb in range(1, 119):
+            isotopes_list += known_isotopes_for_element(atomic_numb)
 
-    elif argument.endswith(('-', '+')):  # For cases like 'Fe++' or 'Si-'
+    return isotopes_list
 
-        char = argument[-1]
-        match = re.match(r"["+char+"]*", argument[::-1])
 
-        charge_state = match.span()[1]
+def common_isotopes(argument: Union[str, int] = None,
+                    most_common_only: bool = False) -> List[str]:
+    r"""Returns a list of isotopes of an element with an isotopic
+    abundances greater than zero, or if no input is provided, a list
+    of all such isotopes for every element.
 
-        if char == '-':
-            charge_state = -charge_state
+    Parameters
+    ----------
 
-        argument = argument[0:len(argument)-match.span()[1]]
+    argument: integer or string, optional
+        A string or integer representing an atomic number or element,
+        or a string represnting an isotope.
 
-        if argument.endswith(('-', '+')):
-            raise InvalidParticleError("Invalid charge state information")
+    most_common_only: boolean
+        If set to True, return only the most common isotope
 
-    else:
-        charge_state = None
+    Returns
+    -------
 
-    if charge_state is not None and charge_state < -3:
-        warnings.warn(f"Element {atomic_symbol(argument)} has a charge of "
-                      f"{charge_state} which is unlikely to occur in nature.",
-                      AtomicWarning)
+    isotopes_list: list of strings or empty list
+        List of all isotopes of an element with isotopic abundances
+        greater than zero, sorted from most abundant to least
+        abundant.  If no isotopes have isotopic abundances greater
+        than zero, this function will return an empty list.  If no
+        arguments are provided, then a list of all common isotopes of
+        all elements will be provided that is sorted by atomic number,
+        with entries for each element sorted from most abundant to
+        least abundant.
 
-    return argument, charge_state
+    Raises
+    ------
 
+    InvalidElementError
+        If the argument is a valid particle but not a valid element.
 
-def _is_neutron(argument: Any, mass_numb: int = None) -> bool:
-    r"""Returns True if the argument corresponds to a neutron, and
-    False otherwise."""
+    InvalidParticleError
+        If the argument does not correspond to a valid particle.
 
-    if argument == 0 and mass_numb == 1:
-        return True
-    elif isinstance(argument, str) and mass_numb is None:
-        if argument in ('n', 'n-1') or argument.lower() in ['neutron', 'n0']:
-            return True
-        else:
-            return False
-    else:
-        return False
+    TypeError
+        If the argument is not a string or integer.
 
+    Notes
+    -----
 
-def _is_hydrogen(argument: Any,
-                 can_be_atomic_number: Optional[bool] = False) -> bool:
-    r"""Returns True if the argument corresponds to hydrogen, and False
-    otherwise."""
+    The isotopic abundances are based on the terrestrial environment
+    and may not be wholly appropriate for space and astrophysical
+    applications.
 
-    case_sensitive_aliases = ['p', 'p+', 'H', 'D', 'T']
+    See also
+    --------
 
-    case_insensitive_aliases = ['proton', 'protium', 'deuterium',
-                                'deuteron', 'triton', 'tritium', 'hydrogen']
+    known_isotopes : returns a list of isotopes that have been
+        discovered
 
-    for mass_numb in range(1, 8):
-        case_sensitive_aliases.append(f'H-{mass_numb}')
-        case_insensitive_aliases.append(f'hydrogen-{mass_numb}')
+    stable_isotopes : returns isotopes that are stable against
+        radioactive decay
 
-    if isinstance(argument, str):
+    isotopic_abundance : returns the relative isotopic abundance
 
-        argument, Z = _extract_charge_state(argument)
+    Examples
+    --------
 
-        if argument in case_sensitive_aliases:
-            is_hydrogen = True
-        else:
-            is_hydrogen = argument.lower() in case_insensitive_aliases
+    >>> common_isotopes('H')
+    ['H-1', 'D']
+    >>> common_isotopes(44)
+    ['Ru-102', 'Ru-104', 'Ru-101', 'Ru-99', 'Ru-100', 'Ru-96', 'Ru-98']
+    >>> common_isotopes('beryllium 2+')
+    ['Be-9']
+    >>> common_isotopes('Fe')
+    ['Fe-56', 'Fe-54', 'Fe-57', 'Fe-58']
+    >>> common_isotopes('Fe', most_common_only=True)
+    ['Fe-56']
+    >>> common_isotopes()[0:7]
+    ['H-1', 'D', 'He-4', 'He-3', 'Li-7', 'Li-6', 'Be-9']
 
-        if is_hydrogen and Z is not None and Z > 1:
-            raise InvalidParticleError("Invalid charge state of hydrogen")
+    """
 
-    elif argument == 1 and can_be_atomic_number:
-        is_hydrogen = True
-    else:
-        is_hydrogen = False
+    def common_isotopes_for_element(argument: Union[str, int],
+                                    most_common_only: Optional[bool]) \
+            -> List[str]:
 
-    return is_hydrogen
+        isotopes = known_isotopes(argument)
+        CommonIsotopes = [isotope for isotope in isotopes if
+                          'isotopic_abundance' in _Isotopes[isotope].keys()]
+        isotopic_abundances = [_Isotopes[isotope]['isotopic_abundance']
+                               for isotope in CommonIsotopes]
+        sorted_isotopes = [iso_comp for (isotope, iso_comp) in
+                           sorted(zip(isotopic_abundances, CommonIsotopes))]
 
+        sorted_isotopes.reverse()
 
-def _is_electron(arg: Any) -> bool:
-    r"""Returns True if the argument corresponds to an electron, and False
-    otherwise."""
+        if most_common_only and len(sorted_isotopes) > 1:
+            sorted_isotopes = sorted_isotopes[0:1]
 
-    if not isinstance(arg, str):
-        return False
+        return sorted_isotopes
 
-    return arg in ['e', 'e-'] or arg.lower() == 'electron'
+    if argument is not None:
 
+        try:
+            element = atomic_symbol(argument)
+            isotopes_list = \
+                common_isotopes_for_element(element, most_common_only)
+        except InvalidParticleError:
+            raise InvalidParticleError("Invalid particle")
+        except InvalidElementError:
+            raise InvalidElementError(
+                "common_isotopes is unable to get isotopes "
+                f"from an input of: {argument}")
 
-def _is_positron(arg: Any) -> bool:
-    r"""Returns True if the argument corresponds to a positron, and False
-    otherwise."""
+    elif argument is None:
+        isotopes_list = []
+        for atomic_numb in range(1, 119):
+            isotopes_list += \
+                common_isotopes_for_element(atomic_numb, most_common_only)
 
-    if not isinstance(arg, str):
-        return False
+    return isotopes_list
 
-    return arg == 'e+' or arg.lower() == 'positron'
 
+def stable_isotopes(argument: Union[str, int] = None,
+                    unstable: bool = False) -> List[str]:
+    r"""Returns a list of all stable isotopes of an element, or if no
+    input is provided, a list of all such isotopes for every element.
 
-def _is_antiproton(arg: Any) -> bool:
-    r"""Returns True if the argument corresponds to an antiproton, and
-    False otherwise."""
+    Parameters
+    ----------
 
-    if not isinstance(arg, str):
-        return False
+    argument: integer or string
+        A string or integer representing an atomic number or element,
+        or a string represnting an isotope.
 
-    return arg == 'p-' or arg.lower() == 'antiproton'
+    unstable: boolean
+        If set to True, this function will return a list of the
+        unstable isotopes instead of the stable isotopes.
 
+    Returns
+    -------
 
-def _is_antineutron(arg: Any) -> bool:
-    r"""Returns True if the argument corresponds to an antineutron, and
-    False otherwise."""
+    StableIsotopes: list of strings or empty list
+        List of all stable isotopes of an element, sorted from lowest
+        mass number.  If an element has no stable isotopes, this
+        function returns an empty list.
 
-    if not isinstance(arg, str):
-        return False
+    Raises
+    ------
 
-    return arg.lower() == 'antineutron'
+    InvalidElementError
+        If the argument is a valid particle but not a valid element.
 
+    InvalidParticleError
+        If the argument does not correspond to a valid particle.
 
-def _is_proton(arg: Any, Z: int = None, mass_numb: int = None) -> bool:
-    r"""Returns True if the argument corresponds to a proton, and
-    False otherwise.  This function returns False for 'H-1' if no
-    charge state is given."""
+    TypeError
+        If the argument is not a string or integer.
 
-    try:
+    Notes
+    -----
 
-        isotope = isotope_symbol(arg, mass_numb)
+    There are 254 isotopes for which no radioactive decay has been
+    observed.  It is possible that some isotopes will be discovered to
+    be unstable but with extremely long half-lives.  For example,
+    bismuth-209 was recently discovered to have a half-life of about
+    1.9e19 years.  However, such isotopes can be regarded as virtually
+    stable for most applications.
 
-        if Z is None:
-            Z = charge_state(arg)
+    See also
+    --------
 
-        return isotope == 'H-1' and Z == 1
+    known_isotopes : returns a list of isotopes that have been
+        discovered
 
-    except Exception:
+    common_isotopes : returns isotopes with non-zero isotopic
+        abundances
 
-        return False
+    Examples
+    --------
 
+    >>> stable_isotopes('H')
+    ['H-1', 'D']
+    >>> stable_isotopes(44)
+    ['Ru-96', 'Ru-98', 'Ru-99', 'Ru-100', 'Ru-101', 'Ru-102', 'Ru-104']
+    >>> stable_isotopes('beryllium')
+    ['Be-9']
+    >>> stable_isotopes('Pb-209')
+    ['Pb-204', 'Pb-206', 'Pb-207', 'Pb-208']
+    >>> stable_isotopes(118)
+    []
 
-def _is_alpha(arg: Any) -> bool:
-    r"""Returns True if the argument corresponds to an alpha particle,
-    and False otherwise."""
+    Find unstable isotopes
 
-    if not isinstance(arg, str):
-        return False
+    >>> stable_isotopes('U', unstable=True)[:5] # only first five
+    ['U-217', 'U-218', 'U-219', 'U-220', 'U-221']
 
-    if arg.lower() == 'alpha':
-        return True
-    elif 'alpha' in arg.lower():
-        raise InvalidParticleError(
-            f"{arg} is an invalid representation of an alpha particle")
-    else:
-        arg, Z = _extract_charge_state(arg)
+    """
 
-        if Z != 2 or arg[-2:] != '-4':
-            return False
-        else:
+    def stable_isotopes_for_element(argument: Union[str, int],
+                                    stable_only: Optional[bool]) -> List[str]:
+        KnownIsotopes = known_isotopes(argument)
+        StableIsotopes = [isotope for isotope in KnownIsotopes if
+                          _Isotopes[isotope]['is_stable'] == stable_only]
+        return StableIsotopes
 
-            dash_position = arg.find('-')
-            arg = arg[:dash_position]
+    if argument is not None:
+        try:
+            element = atomic_symbol(argument)
+            isotopes_list = \
+                stable_isotopes_for_element(element, not unstable)
+        except InvalidParticleError:
+            raise InvalidParticleError("Invalid particle in stable_isotopes")
+        except Exception:
+            raise InvalidElementError(
+                "stable_isotopes is unable to get isotopes "
+                f"from an input of: {argument}")
+    elif argument is None:
+        isotopes_list = []
+        for atomic_numb in range(1, 119):
+            isotopes_list += \
+                stable_isotopes_for_element(atomic_numb, not unstable)
 
-            return arg.lower() == 'helium' or arg == 'He'
+    return isotopes_list
diff --git a/plasmapy/atomic/names.py b/plasmapy/atomic/names.py
new file mode 100644
index 00000000..a103c73e
--- /dev/null
+++ b/plasmapy/atomic/names.py
@@ -0,0 +1,661 @@
+r"""Functions that deal with string representations of atomic symbols
+and numbers."""
+
+import re
+import warnings
+from typing import (Union, Optional, Any, Tuple)
+
+from .elements import (_atomic_symbols, _atomic_symbols_dict, _Elements)
+
+from .isotopes import _Isotopes
+
+from .particles import (_is_special_particle, _get_standard_symbol)
+
+from ..utils import (AtomicWarning,
+                     InvalidElementError,
+                     InvalidIsotopeError,
+                     InvalidIonError,
+                     AtomicError,
+                     InvalidParticleError,
+                     ChargeError)
+
+# TODO: Create an ion_symbol function
+# TODO: Create a particle_symbol function
+
+
+def atomic_symbol(argument: Union[str, int]) -> str:
+    r"""Returns the atomic symbol.
+
+    Parameters
+    ----------
+
+    argument: string or integer
+        A string representing an element, isotope, or ion; or an
+        integer representing an atomic number.
+
+    Returns
+    -------
+
+    symbol: string
+        The atomic symbol of the element, isotope, or nucleon.
+
+    Raises
+    ------
+
+    InvalidElementError
+        If the argument is a valid particle but not a valid element.
+
+    InvalidParticleError
+        If the argument does not correspond to a valid particle.
+
+    TypeError
+        If the argument is not a string or integer.
+
+    See also
+    --------
+
+    isotope_symbol : returns isotope symbol instead of atomic symbol.
+
+    element_name : returns the name of an element.
+
+    Notes
+    -----
+
+    This function returns the symbol of the element rather than the
+    symbol of an isotope.  For example, 'deuterium', 'T', or
+    'hydrogen-2' will yield 'H'; 'alpha' will yield 'He'; and
+    'iron-56' or 'Fe-56' will yield 'Fe'.
+
+    This function is case insensitive when there is no ambiguity
+    associated with case.  However, this function will return 'H' for
+    hydrogen for lower case 'p' but capital 'P' if the argument is 'P'
+    for phosphorus.  This function will return 'N' for nitrogen if the
+    argument is capital 'N', but will not accept lower case 'n' for
+    neutrons.
+
+    Examples
+    --------
+
+    >>> atomic_symbol('helium')
+    'He'
+    >>> atomic_symbol(42)
+    'Mo'
+    >>> atomic_symbol('D')
+    'H'
+    >>> atomic_symbol('C-13')
+    'C'
+    >>> atomic_symbol('alpha')
+    'He'
+    >>> atomic_symbol('79')
+    'Au'
+    >>> atomic_symbol('N')  # Nitrogen
+    'N'
+    >>> atomic_symbol('P'), atomic_symbol('p')  # Phosphorus, proton
+    ('P', 'H')
+
+    """
+
+    if _is_special_particle(argument):
+        raise InvalidElementError(f"{argument} is not a valid element.")
+
+    try:
+        argument, Z = _extract_charge_state(argument)
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid charge in atomic_symbol")
+
+    if not isinstance(argument, (str, int)):
+        raise TypeError("The first argument in atomic_symbol must be either "
+                        "a string representing an element or isotope, or an "
+                        "integer representing the atomic number (or 0 for "
+                        "neutrons).")
+
+    if isinstance(argument, str) and argument.isdigit():
+        argument = int(argument)
+
+    if isinstance(argument, int):
+
+        try:
+            element = _atomic_symbols[argument]
+        except KeyError:
+            raise InvalidParticleError(f"{argument} is an invalid atomic "
+                                       "number in atomic_symbol.")
+
+    elif _is_hydrogen(argument):
+        element = 'H'
+    elif _is_alpha(argument):
+        element = 'He'
+    elif isinstance(argument, str):
+
+        if argument.count('-') == 1:
+            dash_position = argument.find('-')
+            mass_numb = argument[dash_position+1:]
+            if not mass_numb.isdigit():
+                raise InvalidParticleError("Invalid isotope format in "
+                                           "atomic_symbol")
+            argument = argument[:dash_position]
+        else:
+            mass_numb = ''
+
+        if argument.lower() in _atomic_symbols_dict.keys():
+            element = _atomic_symbols_dict[argument.lower()]
+        elif argument in _atomic_symbols.values():
+            element = argument.capitalize()
+        else:
+            raise InvalidParticleError(f"{argument} is an invalid argument "
+                                       "for atomic_symbol")
+
+        if mass_numb.isdigit():
+
+            isotope = element.capitalize() + '-' + mass_numb
+
+            if isotope not in _Isotopes.keys():
+                raise InvalidParticleError(
+                    "The input in atomic_symbol corresponding "
+                    f"to {isotope} is not a valid isotope.")
+
+    if Z is not None and \
+            Z > _Elements[element]['atomic_number']:
+        raise InvalidParticleError("Cannot have an ionization state greater "
+                                   "than the atomic number.")
+
+    return element
+
+
+def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
+    r"""Returns the symbol representing an isotope.
+
+    Parameters
+    ----------
+
+    argument: integer or string
+        A string representing an element, isotope, or ion or an
+        integer representing an atomic number
+
+    mass_numb: integer or string
+        An integer or string representing the mass number of the
+        isotope.
+
+    Returns
+    -------
+
+    symbol: string
+        The isotopic symbol. The result will generally be returned as
+        something like 'He-4' or 'Au-197', but will return 'D' for
+        deuterium and 'T' for tritium.
+
+    Raises
+    ------
+
+    InvalidIsotopeError
+        If the argument is a valid particle but not a valid isotope.
+
+    InvalidParticleError
+        If the argument does not correspond to a valid particle
+        or contradictory information is provided.
+
+    TypeError
+        If the argument is not a string or integer.
+
+    Warns
+    -----
+
+    AtomicWarning
+        If redundant isotope information is provided.
+
+    See also
+    --------
+
+    atomic_symbol : returns atomic symbol instead of isotopic symbol
+
+    Notes
+    -----
+
+    This function returns the symbol of the element rather than the
+    symbol of an isotope.  For example, 'deuterium', 'T', or
+    'hydrogen-2' will yield 'H'; 'alpha' will yield 'He'; and
+    'iron-56' or 'Fe-56' will yield 'Fe'.
+
+    Examples
+    --------
+    >>> isotope_symbol('He', 4)
+    'He-4'
+    >>> isotope_symbol(79, 197)
+    'Au-197'
+    >>> isotope_symbol('hydrogen-2')
+    'D'
+    >>> isotope_symbol('carbon-13')
+    'C-13'
+    >>> isotope_symbol('alpha')
+    'He-4'
+
+    """
+
+    # TODO: Remove this functionality when particle_symbol comes online
+    if _is_neutron(argument, mass_numb):
+        return 'n'
+
+    if _is_special_particle(argument):
+        raise InvalidIsotopeError("The argument {argument} does not "
+                                  "correspond to a valid isotope in "
+                                  "isotope_symbol.")
+
+    try:
+        element = atomic_symbol(argument)
+    except InvalidParticleError:
+        raise InvalidParticleError(
+            f"The argument {argument} to isotope_symbol is not a valid "
+            f"particle.")
+    except InvalidElementError:
+        raise InvalidIsotopeError(f"The argument {argument} to isotope_symbol "
+                                  f"does not correspond to a valid element or "
+                                  f"isotope.")
+
+    # If the argument is already in our standard form for an isotope,
+    # return the argument.
+
+    if mass_numb is None and argument in _Isotopes.keys():
+        return argument
+
+    if isinstance(argument, str):
+        argument, Z = _extract_charge_state(argument)
+
+    if isinstance(argument, str) and argument.isdigit():
+        argument = int(argument)
+
+    if isinstance(mass_numb, str) and mass_numb.isdigit():
+        mass_numb = int(mass_numb)
+
+    # This routine allows several forms of input, and must be able to handle
+    # all of the exceptions that can arise with useful error messages.
+
+    if not isinstance(argument, (str, int)):
+        raise TypeError("The first argument in isotope_symbol must be either "
+                        "a string representing an element or isotope, or an "
+                        "integer representing the atomic number (or 0 for "
+                        " neutrons).")
+
+    if not (isinstance(mass_numb, int) or
+            mass_numb is None):  # coveralls: ignore
+        raise TypeError("The second argument in isotope_symbol must be an "
+                        "integer (or a string containing an integer) that "
+                        "represents the mass number of an isotope.")
+
+    if isinstance(argument, int):
+        if not 0 <= argument <= 118:
+            raise InvalidParticleError(
+                "Invalid atomic number in isotope_symbol")
+        if mass_numb is None:
+            raise InvalidIsotopeError("Insufficient information to determine "
+                                      "isotope in isotope_symbol.")
+
+    # Get mass number from argument, check for redundancies, and take
+    # care of special cases.
+
+    if isinstance(argument, str):
+        if argument.count('-') == 1:
+            dash_position = argument.find('-')
+            mass_numb_from_arg = argument[dash_position+1:].strip()
+            mass_numb_from_arg = int(mass_numb_from_arg)
+        elif argument in ['p', 'p+'] or \
+                argument.lower() in ['protium', 'proton']:
+            mass_numb_from_arg = 1
+        elif argument.lower() in ['d', 'deuterium', 'deuteron']:
+            mass_numb_from_arg = 2
+        elif argument.lower() in ['t', 'tritium', 'triton']:
+            mass_numb_from_arg = 3
+        elif _is_alpha(argument):
+            mass_numb_from_arg = 4
+        else:
+            mass_numb_from_arg = None
+
+        if mass_numb is None and mass_numb_from_arg is None:
+            raise InvalidIsotopeError(
+                "Insufficient information to determine the mass"
+                " number from the inputs to isotope_symbol.")
+
+        if mass_numb is not None and mass_numb_from_arg is not None:
+            if mass_numb == mass_numb_from_arg:
+                warnings.warn(
+                    "Redundant mass number information in isotope_symbol "
+                    f"from inputs: {argument}, {mass_numb}", AtomicWarning)
+            else:  # coveralls: ignore
+                raise InvalidParticleError(
+                    "Contradictory mass number information in isotope_symbol.")
+
+        if mass_numb_from_arg is not None:
+            mass_numb = mass_numb_from_arg
+
+    isotope = f"{element}-{mass_numb}"
+
+    if isotope == 'H-2':
+        isotope = 'D'
+    elif isotope == 'H-3':
+        isotope = 'T'
+
+    if _Elements[element]['atomic_number'] > mass_numb:
+        raise InvalidParticleError("The atomic number cannot exceed the mass "
+                                   "number in isotope_symbol.")
+
+    return isotope
+
+
+def element_name(argument: Union[str, int]) -> str:
+    r"""Returns the name of an element.
+
+    Parameters
+    ----------
+
+    argument : string or integer
+        A string representing an element, isotope, or ion or an
+        integer representing an atomic number
+
+    Returns
+    -------
+
+    name : string
+        The name of the element.
+
+    Raises
+    ------
+
+    InvalidElementError
+        If the argument is a valid particle but not a valid element.
+
+    InvalidParticleError
+        If the argument does not correspond to a valid particle.
+
+    TypeError
+        If the argument is not a string or integer.
+
+    See also
+    --------
+
+    atomic_symbol : returns the atomic symbol
+
+    isotope_symbol : returns the symbol of an isotope
+
+    Examples
+    --------
+
+    >>> element_name("H")
+    'hydrogen'
+    >>> element_name("T")
+    'hydrogen'
+    >>> element_name("alpha")
+    'helium'
+    >>> element_name(42)
+    'molybdenum'
+    >>> element_name("C-12")
+    'carbon'
+
+    """
+
+    try:
+        element = atomic_symbol(argument)
+        name = _Elements[element]["name"]
+    except InvalidElementError:
+        raise InvalidElementError("Invalid element in element_name.")
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid particle in element_name.")
+    except TypeError:
+        raise TypeError("Invalid input to element_name.")
+
+    return name
+
+
+def _extract_charge_state(argument: str) -> Tuple[str, int]:
+    r"""Splits strings containing element or isotope and charge state
+    information into a string without the charge state information and
+    the charge state as an integer (or None if no charge state
+    information is given).
+
+    Parameters
+    ----------
+
+    argument : string
+        String containing information for an element or isotope in any
+        of the allowed formats, followed by charge state information.
+
+    Returns
+    -------
+
+    argument : string
+        The original string with charge state information removed.
+
+    Z : integer
+        The charge state of an ion (e.g., this will return 1 if one
+        electron has been removed and -1 if one electron has been
+        gained)
+
+    Raises
+    ------
+
+    InvalidParticleError
+        If invalid charge information is included.
+
+    Notes
+    -----
+
+    If the argument is not a string, this function will return the
+    original argument and None.
+
+    This function does not check that the charge state is valid.
+
+    Examples
+    --------
+
+    >>> isotope, Z = _extract_charge_state('Fe-56+++')
+    >>> print(isotope)
+    Fe-56
+    >>> print(Z)
+    3
+    >>> _extract_charge_state('D +1')
+    ('D', 1)
+
+    """
+
+    if not isinstance(argument, str):
+        return argument, None
+
+    argument = _get_standard_symbol(argument)
+
+    if argument in ['n', 'antineutron'] or 'nu_' in argument:
+        return argument, 0
+    if argument in ['e-', 'mu-', 'tau-', 'p-']:
+        return argument, -1
+    elif argument in ['e+', 'mu+', 'tau+', 'p']:
+        return argument, 1
+
+    if argument.lower() == 'alpha':
+        return 'He-4', 1
+    elif 'alpha' in argument.lower():
+        raise InvalidParticleError("Invalid representation of an alpha"
+                                   " particle")
+
+    if argument.count(' ') == 1:  # For cases like 'Fe +2' and 'Fe-56 2+'
+
+        argument, ion_info = argument.split()
+
+        check1 = (ion_info.endswith(('-', '+')) ^
+                  ion_info.startswith(('-', '+')))
+
+        check2 = ((ion_info.count('-') == 1 and ion_info.count('+') == 0) or
+                  (ion_info.count('-') == 0 and ion_info.count('+') == 1))
+
+        if '-' in ion_info:
+            sign = -1
+            charge = ion_info.replace('-', '')
+        elif '+' in ion_info:
+            sign = +1
+            charge = ion_info.replace('+', '')
+
+        try:
+            charge_state = sign * int(charge)
+            check3 = True
+        except Exception:
+            check3 = False
+
+        if not (check1 and check2 and check3):
+            raise InvalidParticleError(
+                "The following input does not have valid charge: "
+                f"state information: {argument}{ion_info}")
+
+    elif argument.endswith(('-', '+')):  # For cases like 'Fe++' or 'Si-'
+
+        char = argument[-1]
+        match = re.match(r"["+char+"]*", argument[::-1])
+
+        charge_state = match.span()[1]
+
+        if char == '-':
+            charge_state = -charge_state
+
+        argument = argument[0:len(argument)-match.span()[1]]
+
+        if argument.endswith(('-', '+')):
+            raise InvalidParticleError("Invalid charge state information")
+
+    else:
+        charge_state = None
+
+    if charge_state is not None and charge_state < -3:
+        warnings.warn(f"Element {atomic_symbol(argument)} has a charge of "
+                      f"{charge_state} which is unlikely to occur in nature.",
+                      AtomicWarning)
+
+    return argument, charge_state
+
+
+def _is_neutron(argument: Any, mass_numb: int = None) -> bool:
+    r"""Returns True if the argument corresponds to a neutron, and
+    False otherwise."""
+
+    if argument == 0 and mass_numb == 1:
+        return True
+    elif isinstance(argument, str) and mass_numb is None:
+        if argument in ('n', 'n-1') or argument.lower() in ['neutron', 'n0']:
+            return True
+        else:
+            return False
+    else:
+        return False
+
+
+def _is_hydrogen(argument: Any,
+                 can_be_atomic_number: Optional[bool] = False) -> bool:
+    r"""Returns True if the argument corresponds to hydrogen, and False
+    otherwise."""
+
+    case_sensitive_aliases = ['p', 'p+', 'H', 'D', 'T']
+
+    case_insensitive_aliases = ['proton', 'protium', 'deuterium',
+                                'deuteron', 'triton', 'tritium', 'hydrogen']
+
+    for mass_numb in range(1, 8):
+        case_sensitive_aliases.append(f'H-{mass_numb}')
+        case_insensitive_aliases.append(f'hydrogen-{mass_numb}')
+
+    if isinstance(argument, str):
+
+        argument, Z = _extract_charge_state(argument)
+
+        if argument in case_sensitive_aliases:
+            is_hydrogen = True
+        else:
+            is_hydrogen = argument.lower() in case_insensitive_aliases
+
+        if is_hydrogen and Z is not None and Z > 1:
+            raise InvalidParticleError("Invalid charge state of hydrogen")
+
+    elif argument == 1 and can_be_atomic_number:
+        is_hydrogen = True
+    else:
+        is_hydrogen = False
+
+    return is_hydrogen
+
+
+def _is_electron(arg: Any) -> bool:
+    r"""Returns True if the argument corresponds to an electron, and False
+    otherwise."""
+
+    if not isinstance(arg, str):
+        return False
+
+    return arg in ['e', 'e-'] or arg.lower() == 'electron'
+
+
+def _is_positron(arg: Any) -> bool:
+    r"""Returns True if the argument corresponds to a positron, and False
+    otherwise."""
+
+    if not isinstance(arg, str):
+        return False
+
+    return arg == 'e+' or arg.lower() == 'positron'
+
+
+def _is_antiproton(arg: Any) -> bool:
+    r"""Returns True if the argument corresponds to an antiproton, and
+    False otherwise."""
+
+    if not isinstance(arg, str):
+        return False
+
+    return arg == 'p-' or arg.lower() == 'antiproton'
+
+
+def _is_antineutron(arg: Any) -> bool:
+    r"""Returns True if the argument corresponds to an antineutron, and
+    False otherwise."""
+
+    if not isinstance(arg, str):
+        return False
+
+    return arg.lower() == 'antineutron'
+
+
+def _is_proton(arg: Any, Z: int = None, mass_numb: int = None) -> bool:
+    r"""Returns True if the argument corresponds to a proton, and
+    False otherwise.  This function returns False for 'H-1' if no
+    charge state is given."""
+
+    argument, Z_from_arg = _extract_charge_state(arg)
+
+    if (Z is None) == (Z_from_arg is None):
+        return False
+    else:
+        if Z is None:
+            Z = Z_from_arg
+
+    try:
+        isotope = isotope_symbol(arg, mass_numb)
+    except Exception:
+        return False
+
+    return isotope == 'H-1' and Z == 1
+
+
+def _is_alpha(arg: Any) -> bool:
+    r"""Returns True if the argument corresponds to an alpha particle,
+    and False otherwise."""
+
+    if not isinstance(arg, str):
+        return False
+
+    if arg.lower() == 'alpha':
+        return True
+    elif 'alpha' in arg.lower():
+        raise InvalidParticleError(
+            f"{arg} is an invalid representation of an alpha particle")
+    else:
+        arg, Z = _extract_charge_state(arg)
+
+        if Z != 2 or arg[-2:] != '-4':
+            return False
+        else:
+
+            dash_position = arg.find('-')
+            arg = arg[:dash_position]
+
+            return arg.lower() == 'helium' or arg == 'He'
diff --git a/plasmapy/atomic/nuclear.py b/plasmapy/atomic/nuclear.py
index 01828dd2..8d7c51e6 100644
--- a/plasmapy/atomic/nuclear.py
+++ b/plasmapy/atomic/nuclear.py
@@ -3,9 +3,20 @@
 from astropy import units, constants
 import re
 from itertools import repeat
-from .atomic import (isotope_symbol, mass_number, isotope_mass, ion_mass,
-                     atomic_number, charge_state, _is_neutron, _is_electron,
-                     _is_positron, _is_antiproton, _is_antineutron)
+from .atomic import (mass_number,
+                     isotope_mass,
+                     ion_mass,
+                     atomic_number,
+                     charge_state)
+
+from .names import (isotope_symbol,
+                    _is_neutron,
+                    _is_proton,
+                    _is_electron,
+                    _is_positron,
+                    _is_antiproton,
+                    _is_antineutron)
+
 from ..utils import (InvalidElementError, InvalidIsotopeError)
 
 


Test patch:
diff --git a/plasmapy/atomic/tests/test_atomic.py b/plasmapy/atomic/tests/test_atomic.py
index 594e9362..e99b8054 100644
--- a/plasmapy/atomic/tests/test_atomic.py
+++ b/plasmapy/atomic/tests/test_atomic.py
@@ -3,11 +3,24 @@
 from itertools import product
 from astropy import units as u, constants as const
 
-from ..atomic import (atomic_symbol,
-                      isotope_symbol,
-                      atomic_number,
+from ..names import (atomic_symbol,
+                     isotope_symbol,
+                     element_name,
+
+                     _is_neutron,
+                     _is_hydrogen,
+                     _is_electron,
+                     _is_positron,
+                     _is_antiproton,
+                     _is_alpha,
+                     _is_proton,
+                     _is_antineutron,
+                     _extract_charge_state)
+
+from ..isotopes import _Isotopes
+
+from ..atomic import (atomic_number,
                       mass_number,
-                      element_name,
                       standard_atomic_weight,
                       isotope_mass,
                       ion_mass,
@@ -18,17 +31,10 @@
                       stable_isotopes,
                       isotopic_abundance,
                       charge_state,
-                      electric_charge,
-                      _Isotopes,
-                      _is_neutron,
-                      _is_hydrogen,
-                      _is_electron,
-                      _is_positron,
-                      _is_antiproton,
-                      _is_alpha,
-                      _extract_charge_state,
-                      _is_proton)
+                      electric_charge)
+
 from ..nuclear import (nuclear_binding_energy, nuclear_reaction_energy)
+
 from ...utils import (AtomicWarning,
                       InvalidElementError,
                       InvalidIsotopeError,
@@ -36,7 +42,6 @@
                       ChargeError,
                       AtomicError,
                       MissingAtomicDataError,
-                      MissingAtomicDataWarning,
                       InvalidParticleError)
 
 # (argument, expected)