Repository: PlasmaPy/PlasmaPy
Pull request #231 (2018-01-11 02:24:34)

Implement custom exceptions and warnings in atomic subpackage
Recently we have created and begun to implement custom PlasmaPy exceptions (#168, #170) including specifically for the atomic subpackage in #217.  However, #217 did not go through the arduous process of implementing these custom exceptions.  This pull request implements the custom exceptions and warnings and updates the corresponding tests.


Patch:
diff --git a/plasmapy/atomic/__init__.py b/plasmapy/atomic/__init__.py
index 8f417d89..883da534 100644
--- a/plasmapy/atomic/__init__.py
+++ b/plasmapy/atomic/__init__.py
@@ -1,5 +1,19 @@
 """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 .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 .elements import atomic_symbols, atomic_symbols_dict, Elements
-from .isotopes import Iso
diff --git a/plasmapy/atomic/atomic.py b/plasmapy/atomic/atomic.py
index 10f21122..4d7442ce 100644
--- a/plasmapy/atomic/atomic.py
+++ b/plasmapy/atomic/atomic.py
@@ -2,21 +2,40 @@
 
 import numpy as np
 import re
+import warnings
+from typing import (Union, Optional, Any, List, Tuple)
 from astropy import units as u, constants as const
 from astropy.units import Quantity
-import warnings
-from .elements import atomic_symbols, atomic_symbols_dict, Elements
-from .isotopes import Isotopes
-from .particles import _Particles, _get_standard_symbol
+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,
+                     MissingAtomicDataError,
+                     MissingAtomicDataWarning,
+                     InvalidParticleError,
+                     ChargeError)
 
-from typing import (Union, Optional, Any, List, Tuple)
 
 # 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.
 
-# TODO: Implement subpackage specific exceptions/warnings (e.g., AtomicError)
+# TODO: refactor the atomic subpackage
+#  - Create a Particle class
+#  - Create a decorator that:
+#     1. Takes the inputs of a function corresponding to a particle
+#     2. Checks to make sure the particle is valid
+#     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
@@ -29,33 +48,39 @@ def atomic_symbol(argument: Union[str, int]) -> str:
 
     Parameters
     ----------
+
     argument: string or integer
-        A string representing an element, isotope, or ion or an
-        integer representing an atomic number
+        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
     ------
-    TypeError:
-        If the argument is not a string or integer.
 
-    ValueError:
-        If the argument cannot be used to identify the element, the
-        isotope has not been discovered, or the charge state is
-        invalid.
+    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
@@ -70,6 +95,7 @@ def atomic_symbol(argument: Union[str, int]) -> str:
 
     Examples
     --------
+
     >>> atomic_symbol('helium')
     'He'
     >>> atomic_symbol(42)
@@ -89,12 +115,13 @@ def atomic_symbol(argument: Union[str, int]) -> str:
 
     """
 
-    if _is_neutron(argument):
-        raise ValueError("Neutrons do not have an atomic symbol")
-    elif _is_antiproton(argument):
-        raise ValueError("Antiprotons do not have an atomic symbol")
+    if _is_special_particle(argument):
+        raise InvalidElementError(f"{argument} is not a valid element.")
 
-    argument, Z = _extract_charge_state(argument)
+    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 "
@@ -108,10 +135,10 @@ def atomic_symbol(argument: Union[str, int]) -> str:
     if isinstance(argument, int):
 
         try:
-            element = atomic_symbols[argument]
-        except Exception:
-            raise ValueError(f"{argument} is an invalid atomic number in "
-                             "atomic_symbol")
+            element = _atomic_symbols[argument]
+        except KeyError:
+            raise InvalidParticleError(f"{argument} is an invalid atomic "
+                                       "number in atomic_symbol.")
 
     elif _is_hydrogen(argument):
         element = 'H'
@@ -123,31 +150,33 @@ def atomic_symbol(argument: Union[str, int]) -> str:
             dash_position = argument.find('-')
             mass_numb = argument[dash_position+1:]
             if not mass_numb.isdigit():
-                raise ValueError("Invalid isotope format in atomic_symbol")
+                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():
+        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 ValueError(f"{argument} is an invalid argument for "
-                             "atomic_symbol")
+            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 ValueError("The input in atomic_symbol corresponding "
-                                 f"to {isotope} is not a valid isotope.")
+            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 ValueError("Cannot have an ionization state greater than the "
-                         "atomic number.")
+            Z > _Elements[element]['atomic_number']:
+        raise InvalidParticleError("Cannot have an ionization state greater "
+                                   "than the atomic number.")
 
     return element
 
@@ -157,6 +186,7 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
 
     Parameters
     ----------
+
     argument: integer or string
         A string representing an element, isotope, or ion or an
         integer representing an atomic number
@@ -167,6 +197,7 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
 
     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
@@ -174,24 +205,31 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
 
     Raises
     ------
-    ValueError:
-        If insufficient or contradictory isotope information is
-        provided, the element cannot be determined from the first
-        argument, or the mass number exceeds the atomic number.
 
-    TypeError:
-        If isotope information cannot be found because one or both
-        inputs is of an inappropriate type.
+    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
+    -----
 
-    UserWarning:
+    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
@@ -212,14 +250,34 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
 
     """
 
+    # 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():
+    if mass_numb is None and argument in _Isotopes.keys():
         return argument
 
     if isinstance(argument, str):
-        argument, charge_state = _extract_charge_state(argument)
+        argument, Z = _extract_charge_state(argument)
 
     if isinstance(argument, str) and argument.isdigit():
         argument = int(argument)
@@ -242,18 +300,13 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
                         "integer (or a string containing an integer) that "
                         "represents the mass number of an isotope.")
 
-    if isinstance(argument, int) and mass_numb is None:
-        raise ValueError("Insufficient information to determine element and "
-                         "mass number in isotope_symbol.")
-
-    if _is_neutron(argument, mass_numb):
-        return 'n'
-
-    try:
-        element = atomic_symbol(argument)
-    except Exception:
-        raise ValueError(f"The first argument of isotope_symbol ({argument}) "
-                         "does not correspond to a valid element or 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.
@@ -276,17 +329,18 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
             mass_numb_from_arg = None
 
         if mass_numb is None and mass_numb_from_arg is None:
-            raise ValueError("Insufficient information to determine the mass "
-                             "number from the inputs to isotope_symbol.")
+            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}", UserWarning)
+                    f"from inputs: {argument}, {mass_numb}", AtomicWarning)
             else:  # coveralls: ignore
-                raise ValueError("Contradictory mass number information in "
-                                 "isotope_symbol.")
+                raise InvalidParticleError(
+                    "Contradictory mass number information in isotope_symbol.")
 
         if mass_numb_from_arg is not None:
             mass_numb = mass_numb_from_arg
@@ -299,8 +353,8 @@ def isotope_symbol(argument: Union[str, int], mass_numb: int = None) -> str:
         isotope = 'T'
 
     if atomic_number(element) > mass_numb:
-        raise ValueError("The atomic number cannot exceed the mass number in "
-                         "isotope_symbol.")
+        raise InvalidParticleError("The atomic number cannot exceed the mass "
+                                   "number in isotope_symbol.")
 
     return isotope
 
@@ -310,17 +364,32 @@ def atomic_number(argument: str) -> str:
 
     Parameters
     ----------
+
     argument: string
         A string representing an element, isotope, or ion.
 
     Returns
     -------
+
     atomic_number: integer
         An integer representing the atomic number of the element or
         isotope.
 
+    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.
+
     See also
     --------
+
     mass_number : returns the mass number (the total number of protons
         and neutrons) of an isotope.
 
@@ -339,11 +408,17 @@ def atomic_number(argument: str) -> str:
 
     try:
         element = atomic_symbol(argument)
-        atomic_numb = Elements[element]['atomic_number']
-    except ValueError:
-        raise ValueError("Unable to identify element in atomic_number")
+        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("Invalid type in atomic_number")
+        raise TypeError(f"The argument {argument} to atomic number is not a "
+                        f"string.") from None
 
     return atomic_numb
 
@@ -354,6 +429,7 @@ def is_isotope_stable(argument: Union[str, int],
 
     Parameters
     ----------
+
     argument: integer or string
         A string representing an isotope or an integer representing an
         atomic number
@@ -363,16 +439,29 @@ def is_isotope_stable(argument: Union[str, int],
 
     Returns
     -------
+
     is_stable: boolean
         True if the isotope is stable, False if it is unstable.
 
     Raises
     ------
-    ValueError:
-        If isotope cannot be determined or is not known.
+
+    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")
@@ -382,15 +471,15 @@ def is_isotope_stable(argument: Union[str, int],
 
     try:
         isotope = isotope_symbol(argument, mass_numb)
-    except ValueError:
-        raise ValueError("Invalid isotope in is_isotope_stable")
+        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("Invalid input to is_isotope_stable")
-
-    try:
-        is_stable = Isotopes[isotope]['is_stable']
-    except Exception:  # coveralls: ignore
-        ValueError("No data on stability of " + isotope)
+        raise TypeError("The argument to is_isotope_stable must be a string.")
 
     return is_stable
 
@@ -401,6 +490,7 @@ def half_life(argument: Union[int, str], mass_numb: int = None) -> Quantity:
 
     Parameters
     ----------
+
     argument: integer or string
         A string representing an isotope or an integer representing an
         atomic number
@@ -410,26 +500,38 @@ def half_life(argument: Union[int, str], mass_numb: int = None) -> Quantity:
 
     Returns
     -------
+
     half_life_sec: astropy Quantity
         The half-life in units of seconds.
 
     Raises:
     -------
-    ValueError:
+
+    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.
+    TypeError
+        The argument is not an integer or string or the mass number is
+        not an integer.
 
-    UserWarning:
+    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')
@@ -440,22 +542,26 @@ def half_life(argument: Union[int, str], mass_numb: int = None) -> Quantity:
     """
 
     try:
+
         isotope = isotope_symbol(argument, mass_numb)
-    except ValueError:
-        raise ValueError("Cannot determine isotope information from these " +
-                         f"inputs to half_life: {argument}, {mass_numb}")
-    except TypeError:
-        raise TypeError("Incorrect argument type for half_life")
 
-    try:
-        if Isotopes[isotope]['is_stable']:
+        if _Isotopes[isotope]['is_stable']:
             half_life_sec = np.inf * u.s
         else:
-            half_life_sec = Isotopes[isotope]['half_life']
-    except Exception:
-        half_life_sec = None
-        warnings.warn(f"The half-life for isotope {isotope} is not available; "
-                      "returning None.", UserWarning)
+            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
 
@@ -466,29 +572,38 @@ def mass_number(isotope: str) -> int:
 
     Parameters
     ----------
+
     isotope : string
         A string representing an isotope or a neutron.
 
     Returns
     -------
+
     mass_number : integer
        The total number of protons plus neutrons in a nuclide.
 
     Raises
     ------
-    ValueError
-        If the mass number cannot be found.
+
+    InvalidParticleError
+        If the argument does not correspond to a valid particle.
+
+    InvalidIsotopeError
+        If the argument does not correspond to a valid isotope.
 
     TypeError
-        The first argument is not a string.
+        The argument is not a string.
+
 
     See also
     --------
+
     atomic_number : returns the number of protons in an isotope or
         element
 
     Examples
     --------
+
     >>> mass_number("H-1")
     1
     >>> mass_number("Pb-208")
@@ -497,10 +612,6 @@ def mass_number(isotope: str) -> int:
     3
     >>> mass_number("n")
     1
-    >>> mass_number("N")
-    Traceback (most recent call last):
-      ...
-    ValueError: Mass number not able to be found from input N
     >>> mass_number("alpha")
     4
 
@@ -508,12 +619,15 @@ def mass_number(isotope: str) -> int:
 
     try:
         isotope = isotope_symbol(isotope)
-        mass_numb = Isotopes[isotope]["mass_number"]
+        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("Incorrect type for mass_number input.")
-    except ValueError:
-        raise ValueError("Mass number not able to be found from input "
-                         f"{isotope}")
+        raise TypeError("The argument to mass_number is not a string.")
 
     return mass_numb
 
@@ -523,23 +637,39 @@ def element_name(argument: Union[str, int]) -> str:
 
     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")
@@ -555,11 +685,13 @@ def element_name(argument: Union[str, int]) -> str:
 
     try:
         element = atomic_symbol(argument)
-        name = Elements[element]["name"]
-    except ValueError:
-        raise ValueError("Unable to identify element in element_name")
+        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")
+        raise TypeError("Invalid input to element_name.")
 
     return name
 
@@ -571,6 +703,7 @@ def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
 
     Parameters
     ----------
+
     argument: string or integer
         A string representing an element or an integer representing an
         atomic number
@@ -584,13 +717,19 @@ def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
 
     Raises
     ------
-    ValueError:
-        If the argument cannot be used to identify an element; the
-        argument represents an isotope, ion, or neutron; or no
-        standard atomic weight is provided for an element.
+
+    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,
@@ -599,6 +738,7 @@ def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
 
     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.
@@ -615,6 +755,7 @@ def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
 
     Examples
     --------
+
     >>> from astropy import units
     >>> standard_atomic_weight("H")
     <Quantity 1.008 u>
@@ -627,37 +768,37 @@ def standard_atomic_weight(argument: Union[str, int]) -> Quantity:
     <Quantity 207.2 u>
     >>> standard_atomic_weight("lead")
     <Quantity 207.2 u>
-    >>> standard_atomic_weight(118) is None
-    Traceback (most recent call last):
-      ...
-    ValueError: No standard atomic weight is available for Og
 
     """
 
-    argument, charge_state = _extract_charge_state(argument)
-
-    if charge_state is not None and charge_state != 0:
-        raise ValueError("Use ion_mass to get masses of charged particles.")
-
     try:
-        isotope = isotope_symbol(argument)
-    except Exception:
-        isotope = ''
+        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.")
 
-    if _is_neutron(isotope):
-        raise ValueError("Use isotope_mass('n') or plasmapy.constants.m_n "
-                         "instead of standard_atomic_weight to get neutron "
-                         "mass")
-    elif '-' in isotope or isotope in ['D', 'T']:
-        raise ValueError("Use isotope_mass to get masses of isotopes")
+    try:
+        charge_state(argument)
+    except ChargeError:
+        pass
+    else:
+        raise AtomicError("Use ion_mass to get masses of ions.")
 
-    element = atomic_symbol(argument)
+    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 Exception:
-        raise ValueError("No standard atomic weight is available for " +
-                         element)
+        atomic_weight = _Elements[element]['atomic_mass']
+    except KeyError:
+        raise MissingAtomicDataError(
+            f"No standard atomic weight is available for {element}.")
 
     return atomic_weight
 
@@ -668,6 +809,7 @@ def isotope_mass(argument: Union[str, int],
 
     Parameters
     ----------
+
     argument : string or integer
         A string representing an element, isotope, or ion or an
         integer representing an atomic number
@@ -677,16 +819,29 @@ def isotope_mass(argument: Union[str, int],
 
     Returns
     -------
+
     isotope_mass : Quantity
         The atomic mass of a neutral atom of an isotope.
 
     Raises
     ------
-    ValueError:
-        Contradictory or insufficient isotope information is provided.
+
+    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
 
@@ -695,10 +850,12 @@ def isotope_mass(argument: Union[str, int],
 
     Notes
     -----
+
     The masses of rare isotopes may be unavailable.
 
     Examples
     --------
+
     >>> from astropy import units as u
     >>> isotope_mass("H-1")
     <Quantity 1.00782503 u>
@@ -714,14 +871,16 @@ def isotope_mass(argument: Union[str, int],
     argument, charge_state = _extract_charge_state(argument)
 
     if charge_state is not None and charge_state != 0:
-        raise ValueError("Use ion_mass instead of isotope_mass for masses of "
-                         "charged particles")
+        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 ValueError:
-        raise ValueError("Unable to identify isotope in isotope_mass")
+        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")
 
@@ -737,6 +896,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
 
     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
@@ -751,21 +911,23 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
 
     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.
 
-    ValueError
+    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.
 
-    UserWarning
+    AtomicWarning
         If a mass was inputted and it is outside of the range of known
         isotopes or electrons/positrons.
 
@@ -774,6 +936,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
 
     See also
     --------
+
     standard_atomic_mass : returns the conventional atomic mass of an
         element based on terrestrial values and assuming the atom is
         neutral.
@@ -783,6 +946,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
 
     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
@@ -805,6 +969,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
 
     Examples
     --------
+
     >>> print(ion_mass('p').si.value)
     1.672621898e-27
     >>> ion_mass('H+')  # assumes terrestrial abundance of D
@@ -846,7 +1011,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
             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.", UserWarning)
+                "electrons/ions.", AtomicWarning)
             return m_i
 
     if _is_electron(argument) or _is_positron(argument):
@@ -854,7 +1019,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
     elif _is_proton(argument, Z, mass_numb) or _is_antiproton(argument):
         return const.m_p
     elif _is_neutron(argument, mass_numb):
-        raise ValueError("Use isotope_mass or m_n to get mass of neutron")
+        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)
@@ -864,7 +1029,8 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = 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 ValueError("Inconsistent charge state information in ion_mass")
+        raise InvalidIonError("Inconsistent charge state information in"
+                              "ion_mass.")
     elif Z is None and Z_from_arg is not None:
         Z = Z_from_arg
 
@@ -882,8 +1048,8 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
                         "representing the mass number of an isotope.")
 
     if atomic_number(argument) < Z:
-        raise ValueError("The ionization state cannot exceed the "
-                         "atomic number in ion_mass")
+        raise InvalidParticleError("The ionization state cannot exceed the "
+                                   "atomic number in ion_mass")
 
     try:
         isotope = isotope_symbol(argument, mass_numb)
@@ -915,7 +1081,7 @@ def ion_mass(argument: Union[str, int, Quantity], Z: int = None,
             if isinstance(mass_numb, int):
                 errormessage += f" with mass number {mass_numb}"
 
-            raise ValueError(errormessage)
+            raise
 
     m_i = (atomic_mass - Z * const.m_e).to(u.kg)
 
@@ -928,12 +1094,14 @@ def known_isotopes(argument: Union[str, int] = None) -> List[str]:
 
     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
@@ -944,15 +1112,24 @@ def known_isotopes(argument: Union[str, int] = None) -> List[str]:
 
     Raises
     ------
-    ValueError
-        The argument cannot be used to determine an element.
+
+    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
 
@@ -975,7 +1152,7 @@ def known_isotopes(argument: Union[str, int] = None) -> List[str]:
     def known_isotopes_for_element(argument):
         element = atomic_symbol(argument)
         isotopes = []
-        for isotope in Isotopes.keys():
+        for isotope in _Isotopes.keys():
             if element + '-' in isotope and isotope[0:len(element)] == element:
                 isotopes.append(isotope)
         if element == 'H':
@@ -990,9 +1167,11 @@ def known_isotopes_for_element(argument):
         try:
             element = atomic_symbol(argument)
             isotopes_list = known_isotopes_for_element(element)
-        except Exception:
-            raise ValueError("known_isotopes is unable to get isotopes from "
-                             f"an input of: {argument}")
+        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):
@@ -1009,6 +1188,7 @@ def common_isotopes(argument: Union[str, int] = None,
 
     Parameters
     ----------
+
     argument: integer or string, optional
         A string or integer representing an atomic number or element,
         or a string represnting an isotope.
@@ -1018,6 +1198,7 @@ def common_isotopes(argument: Union[str, int] = None,
 
     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
@@ -1030,17 +1211,26 @@ def common_isotopes(argument: Union[str, int] = None,
 
     Raises
     ------
-    ValueError
-        The argument cannot be used to determine an element.
+
+    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
 
@@ -1051,6 +1241,7 @@ def common_isotopes(argument: Union[str, int] = None,
 
     Examples
     --------
+
     >>> common_isotopes('H')
     ['H-1', 'D']
     >>> common_isotopes(44)
@@ -1072,8 +1263,8 @@ def common_isotopes_for_element(argument: Union[str, int],
 
         isotopes = known_isotopes(argument)
         CommonIsotopes = [isotope for isotope in isotopes if
-                          'isotopic_abundance' in Isotopes[isotope].keys()]
-        isotopic_abundances = [Isotopes[isotope]['isotopic_abundance']
+                          '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))]
@@ -1091,9 +1282,12 @@ def common_isotopes_for_element(argument: Union[str, int],
             element = atomic_symbol(argument)
             isotopes_list = \
                 common_isotopes_for_element(element, most_common_only)
-        except Exception:
-            raise ValueError("common_isotopes is unable to get isotopes from "
-                             f"an input of: {argument}")
+        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 = []
@@ -1111,6 +1305,7 @@ def stable_isotopes(argument: Union[str, int] = None,
 
     Parameters
     ----------
+
     argument: integer or string
         A string or integer representing an atomic number or element,
         or a string represnting an isotope.
@@ -1121,13 +1316,27 @@ def stable_isotopes(argument: Union[str, int] = None,
 
     Returns
     -------
+
     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.
 
+    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
     -----
+
     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,
@@ -1137,6 +1346,7 @@ def stable_isotopes(argument: Union[str, int] = None,
 
     See also
     --------
+
     known_isotopes : returns a list of isotopes that have been
         discovered
 
@@ -1145,6 +1355,7 @@ def stable_isotopes(argument: Union[str, int] = None,
 
     Examples
     --------
+
     >>> stable_isotopes('H')
     ['H-1', 'D']
     >>> stable_isotopes(44)
@@ -1167,7 +1378,7 @@ 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]
+                          _Isotopes[isotope]['is_stable'] == stable_only]
         return StableIsotopes
 
     if argument is not None:
@@ -1175,9 +1386,12 @@ def stable_isotopes_for_element(argument: Union[str, int],
             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 ValueError("stable_isotopes is unable to get isotopes from "
-                             f"an input of: {argument}")
+            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):
@@ -1193,6 +1407,7 @@ def isotopic_abundance(argument: Union[str, int],
 
     Parameters
     ----------
+
     argument: string or integer
         A string representing an element or isotope, or an integer
         representing the atomic number of an element.
@@ -1203,16 +1418,27 @@ def isotopic_abundance(argument: Union[str, int],
 
     Returns
     -------
+
     iso_comp: float
         The relative isotopic abundance in the terrestrial environment
 
     Raises
     ------
-    ValueError
-        Invalid isotope input, or the input corresponded to neutrons.
+
+    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.
+
 
     Notes
     -----
+
     Isotopic composition data are most readily available for the
     terrestrial environment, so this function may not be wholly
     appropriate for space and astrophysical applications.
@@ -1222,6 +1448,7 @@ def isotopic_abundance(argument: Union[str, int],
 
     Examples
     --------
+
     >>> isotopic_abundance('Pb-208')
     0.524
     >>> isotopic_abundance('hydrogen', 1)
@@ -1231,18 +1458,18 @@ def isotopic_abundance(argument: Union[str, int],
 
     """
 
+    if _is_neutron(argument):
+        raise InvalidIsotopeError(
+            "Neutrons do not have an isotopic abundance.")
+
     try:
         isotope = isotope_symbol(argument, mass_numb)
-    except Exception:
-        raise ValueError("Invalid isotope in isotopic_abundance.")
-
-    if isotope == 'n':
-        raise ValueError("Neutrons do not have an isotopic abundance.")
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid particle in isotopic_abundance.")
+    except InvalidIsotopeError:
+        raise InvalidIsotopeError("Invalid isotope in isotopic_abundance.")
 
-    try:
-        iso_comp = Isotopes[isotope]['isotopic_abundance']
-    except Exception:
-        iso_comp = 0.0
+    iso_comp = _Isotopes[isotope].get('isotopic_abundance', 0.0)
 
     return iso_comp
 
@@ -1252,26 +1479,33 @@ def charge_state(particle: str) -> int:
 
     Parameters
     ----------
+
     particle : string
         String representing a particle.
 
     Returns
     -------
+
     Z : integer
         The charge state, or None if it is not available.
 
     Raises
     ------
-    ValueError:
-        If the charge state or isotope information is invalid, or the
-        charge state exceeds the atomic number.
 
-    UserWarning:
+    InvalidParticleError
+        If the argument does not correspond to a valid particle
+        or contradictory information is provided.
+
+    ChargeError
+        If charge information for the particle is not available.
+
+    AtomicWarning
         If the input represents an ion with a charge state that is
         below -3.
 
     Notes
     -----
+
     This function supports two formats for the charge state
     information.
 
@@ -1288,6 +1522,7 @@ def charge_state(particle: str) -> int:
 
     Examples
     --------
+
     >>> charge_state('Fe-56 2+')
     2
     >>> charge_state('He -2')
@@ -1309,21 +1544,22 @@ def charge_state(particle: str) -> int:
     particle, Z = _extract_charge_state(particle)
 
     try:
-        atomic_numb = atomic_number(particle)
-    except Exception:
-        raise ValueError("Invalid element or isotope information in "
-                         "charge_state")
+        atomic_symbol(particle)
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid particle in charge_state")
 
-    if Z is not None and Z > atomic_numb:
-        raise ValueError("The charge state cannot be greater than the atomic "
-                         "number.")
+    if Z is None:
+        raise ChargeError(f"Unable to find charge of {particle}.")
 
-    if Z is not None and (Z < -atomic_numb-1 or Z < -3):
-        warnings.warn(f"Element {atomic_symbol(particle)} has a charge of {Z}"
-                      " which is unlikely to occur in nature.", UserWarning)
+    atomic_numb = atomic_number(particle)
 
-    if Z is None:
-        raise ValueError(f"Unable to find charge of {particle}")
+    if Z > atomic_numb:
+        raise InvalidParticleError("The integer charge cannot be greater than "
+                                   "the atomic number in charge_state.")
+
+    if Z < -atomic_numb - 1 or Z < -3:
+        warnings.warn(f"Element {atomic_symbol(particle)} has a charge of {Z}"
+                      " which is unlikely to occur in nature.", AtomicWarning)
 
     return Z
 
@@ -1334,27 +1570,34 @@ def electric_charge(particle: str) -> Quantity:
 
     Parameters
     ----------
+
     particle : string
         String representing an element or isotope followed by charge
         state information.
 
     Returns
     -------
+
     charge: Quantity
         The electric charge in coulombs.
 
     Raises
     ------
-    ValueError:
-        If the charge state or isotope information is invalid, or the
-        charge state exceeds the atomic number.
 
-    UserWarning:
+    InvalidParticleError
+        If the argument does not correspond to a valid particle
+        or contradictory information is provided.
+
+    ChargeError
+        If charge information for the particle is not available.
+
+    AtomicWarning
         If the input represents an ion with a charge state that is
         below -3.
 
     Notes
     -----
+
     This function supports two formats for the charge state
     information.
 
@@ -1371,6 +1614,7 @@ def electric_charge(particle: str) -> Quantity:
 
     Examples
     --------
+
     >>> electric_charge('p')
     <Quantity 1.60217662e-19 C>
     >>> electric_charge('e')
@@ -1380,9 +1624,15 @@ def electric_charge(particle: str) -> Quantity:
 
     try:
         charge = charge_state(particle) * const.e.to('C')
-        return charge
-    except Exception:  # coveralls: ignore
-        raise ValueError(f"{particle} is an invalid input to electric_charge")
+    except InvalidParticleError:
+        raise InvalidParticleError("Invalid particle in electric_charge.")
+    except ChargeError:
+        raise ChargeError("Charge information is not being provided to "
+                          "electric_charge.")
+    except TypeError:
+        raise TypeError("Invalid type in electric_charge.")
+
+    return charge
 
 
 def _extract_charge_state(argument: str) -> Tuple[str, int]:
@@ -1393,12 +1643,14 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
 
     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.
 
@@ -1407,8 +1659,15 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
         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.
 
@@ -1416,6 +1675,7 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
 
     Examples
     --------
+
     >>> isotope, Z = _extract_charge_state('Fe-56+++')
     >>> print(isotope)
     Fe-56
@@ -1429,17 +1689,20 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
     if not isinstance(argument, str):
         return argument, None
 
-    if argument in ['n', 'neutron', 'n-1']:
+    argument = _get_standard_symbol(argument)
+
+    if argument in ['n', 'antineutron'] or 'nu_' in argument:
         return argument, 0
-    elif argument in ['p', 'p+'] or argument.lower() in \
-            ['proton', 'deuteron', 'triton']:
-        return argument, 1
-    elif argument.lower() == 'alpha':
-        return argument, 2
-    elif argument == 'e+' or argument.lower() == 'positron':
-        return argument, 1
-    elif _is_antiproton(argument):
+    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+'
 
@@ -1459,14 +1722,15 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
             charge = ion_info.replace('+', '')
 
         try:
-            charge_state = sign*int(charge)
+            charge_state = sign * int(charge)
             check3 = True
         except Exception:
             check3 = False
 
         if not (check1 and check2 and check3):
-            raise ValueError("The following input does not have valid charge "
-                             f"state information: {argument}{ion_info}")
+            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-'
 
@@ -1481,7 +1745,7 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
         argument = argument[0:len(argument)-match.span()[1]]
 
         if argument.endswith(('-', '+')):
-            raise ValueError("Invalid charge state information")
+            raise InvalidParticleError("Invalid charge state information")
 
     else:
         charge_state = None
@@ -1489,7 +1753,7 @@ def _extract_charge_state(argument: str) -> Tuple[str, int]:
     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.",
-                      UserWarning)
+                      AtomicWarning)
 
     return argument, charge_state
 
@@ -1533,7 +1797,7 @@ def _is_hydrogen(argument: Any,
             is_hydrogen = argument.lower() in case_insensitive_aliases
 
         if is_hydrogen and Z is not None and Z > 1:
-            raise ValueError("Invalid charge state of hydrogen")
+            raise InvalidParticleError("Invalid charge state of hydrogen")
 
     elif argument == 1 and can_be_atomic_number:
         is_hydrogen = True
@@ -1611,6 +1875,9 @@ def _is_alpha(arg: Any) -> bool:
 
     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)
 
diff --git a/plasmapy/atomic/elements.py b/plasmapy/atomic/elements.py
index 5add30ca..7d563809 100644
--- a/plasmapy/atomic/elements.py
+++ b/plasmapy/atomic/elements.py
@@ -1,75 +1,77 @@
-"""Structures containing atomic data"""
+"""Dictionaries containing atomic data"""
 
 from astropy import units as u, constants as const
 
-atomic_symbols = {1: "H", 2: "He", 3: "Li", 4: "Be", 5: "B", 6: "C", 7: "N",
-                  8: "O", 9: "F", 10: "Ne", 11: "Na", 12: "Mg", 13: "Al",
-                  14: "Si", 15: "P", 16: "S", 17: "Cl", 18: "Ar", 19: "K",
-                  20: "Ca", 21: "Sc", 22: "Ti", 23: "V", 24: "Cr", 25: "Mn",
-                  26: "Fe", 27: "Co", 28: "Ni", 29: "Cu", 30: "Zn", 31: "Ga",
-                  32: "Ge", 33: "As", 34: "Se", 35: "Br", 36: "Kr", 37: "Rb",
-                  38: "Sr", 39: "Y", 40: "Zr", 41: "Nb", 42: "Mo", 43: "Tc",
-                  44: "Ru", 45: "Rh", 46: "Pd", 47: "Ag", 48: "Cd", 49: "In",
-                  50: "Sn", 51: "Sb", 52: "Te", 53: "I", 54: "Xe", 55: "Cs",
-                  56: "Ba", 57: "La", 58: "Ce", 59: "Pr", 60: "Nd", 61: "Pm",
-                  62: "Sm", 63: "Eu", 64: "Gd", 65: "Tb", 66: "Dy", 67: "Ho",
-                  68: "Er", 69: "Tm", 70: "Yb", 71: "Lu", 72: "Hf", 73: "Ta",
-                  74: "W", 75: "Re", 76: "Os", 77: "Ir", 78: "Pt", 79: "Au",
-                  80: "Hg", 81: "Tl", 82: "Pb", 83: "Bi", 84: "Po", 85: "At",
-                  86: "Rn", 87: "Fr", 88: "Ra", 89: "Ac", 90: "Th", 91: "Pa",
-                  92: "U", 93: "Np", 94: "Pu", 95: "Am", 96: "Cm", 97: "Bk",
-                  98: "Cf", 99: "Es", 100: "Fm", 101: "Md", 102: "No",
-                  103: "Lr", 104: "Rf", 105: "Db", 106: "Sg", 107: "Bh",
-                  108: "Hs", 109: "Mt", 110: "Ds", 111: "Rg", 112: "Cn",
-                  113: "Nh", 114: "Fl", 115: "Mc", 116: "Lv", 117: "Ts",
-                  118: "Og"}
+_atomic_symbols = {1: "H", 2: "He", 3: "Li", 4: "Be", 5: "B", 6: "C", 7: "N",
+                   8: "O", 9: "F", 10: "Ne", 11: "Na", 12: "Mg", 13: "Al",
+                   14: "Si", 15: "P", 16: "S", 17: "Cl", 18: "Ar", 19: "K",
+                   20: "Ca", 21: "Sc", 22: "Ti", 23: "V", 24: "Cr", 25: "Mn",
+                   26: "Fe", 27: "Co", 28: "Ni", 29: "Cu", 30: "Zn", 31: "Ga",
+                   32: "Ge", 33: "As", 34: "Se", 35: "Br", 36: "Kr", 37: "Rb",
+                   38: "Sr", 39: "Y", 40: "Zr", 41: "Nb", 42: "Mo", 43: "Tc",
+                   44: "Ru", 45: "Rh", 46: "Pd", 47: "Ag", 48: "Cd", 49: "In",
+                   50: "Sn", 51: "Sb", 52: "Te", 53: "I", 54: "Xe", 55: "Cs",
+                   56: "Ba", 57: "La", 58: "Ce", 59: "Pr", 60: "Nd", 61: "Pm",
+                   62: "Sm", 63: "Eu", 64: "Gd", 65: "Tb", 66: "Dy", 67: "Ho",
+                   68: "Er", 69: "Tm", 70: "Yb", 71: "Lu", 72: "Hf", 73: "Ta",
+                   74: "W", 75: "Re", 76: "Os", 77: "Ir", 78: "Pt", 79: "Au",
+                   80: "Hg", 81: "Tl", 82: "Pb", 83: "Bi", 84: "Po", 85: "At",
+                   86: "Rn", 87: "Fr", 88: "Ra", 89: "Ac", 90: "Th", 91: "Pa",
+                   92: "U", 93: "Np", 94: "Pu", 95: "Am", 96: "Cm", 97: "Bk",
+                   98: "Cf", 99: "Es", 100: "Fm", 101: "Md", 102: "No",
+                   103: "Lr", 104: "Rf", 105: "Db", 106: "Sg", 107: "Bh",
+                   108: "Hs", 109: "Mt", 110: "Ds", 111: "Rg", 112: "Cn",
+                   113: "Nh", 114: "Fl", 115: "Mc", 116: "Lv", 117: "Ts",
+                   118: "Og"}
 
 
-atomic_symbols_dict = {"hydrogen": "H", "helium": "He", "lithium": "Li",
-                       "beryllium": "Be", "boron": "B", "carbon": "C",
-                       "nitrogen": "N", "oxygen": "O", "fluorine": "F",
-                       "neon": "Ne", "sodium": "Na", "magnesium": "Mg",
-                       "aluminium": "Al", "silicon": "Si", "phosphorus": "P",
-                       "sulfur": "S", "chlorine": "Cl", "argon": "Ar",
-                       "potassium": "K", "calcium": "Ca", "scandium": "Sc",
-                       "titanium": "Ti", "vanadium": "V", "chromium": "Cr",
-                       "manganese": "Mn", "iron": "Fe", "cobalt": "Co",
-                       "nickel": "Ni", "copper": "Cu", "zinc": "Zn",
-                       "gallium": "Ga", "germanium": "Ge", "arsenic": "As",
-                       "selenium": "Se", "bromine": "Br", "krypton": "Kr",
-                       "rubidium": "Rb", "strontium": "Sr", "yttrium": "Y",
-                       "zirconium": "Zr", "niobium": "Nb", "molybdenum": "Mo",
-                       "technetium": "Tc", "ruthenium": "Ru", "rhodium": "Rh",
-                       "palladium": "Pd", "silver": "Ag", "cadmium": "Cd",
-                       "indium": "In", "tin": "Sn", "antimony": "Sb",
-                       "tellurium": "Te", "iodine": "I", "xenon": "Xe",
-                       "caesium": "Cs", "barium": "Ba", "lanthanum": "La",
-                       "cerium": "Ce", "praseodymium": "Pr", "neodymium": "Nd",
-                       "promethium": "Pm", "samarium": "Sm", "europium": "Eu",
-                       "gadolinium": "Gd", "terbium": "Tb", "dysprosium": "Dy",
-                       "holmium": "Ho", "erbium": "Er", "thulium": "Tm",
-                       "ytterbium": "Yb", "lutetium": "Lu", "hafnium": "Hf",
-                       "tantalum": "Ta", "tungsten": "W", "rhenium": "Re",
-                       "osmium": "Os", "iridium": "Ir", "platinum": "Pt",
-                       "gold": "Au", "mercury": "Hg", "thallium": "Tl",
-                       "lead": "Pb", "bismuth": "Bi", "polonium": "Po",
-                       "astatine": "At", "radon": "Rn", "francium": "Fr",
-                       "radium": "Ra", "actinium": "Ac", "thorium": "Th",
-                       "protactinium": "Pa", "uranium": "U", "neptunium": "Np",
-                       "plutonium": "Pu", "americium": "Am", "curium": "Cm",
-                       "berkelium": "Bk", "californium": "Cf",
-                       "einsteinium": "Es", "fermium": "Fm",
-                       "mendelevium": "Md", "nobelium": "No",
-                       "lawrencium": "Lr", "rutherfordium": "Rf",
-                       "dubnium": "Db", "seaborgium": "Sg", "bohrium": "Bh",
-                       "hassium": "Hs", "meitnerium": "Mt",
-                       "darmstadtium": "Ds", "roentgenium": "Rg",
-                       "copernicium": "Cn", "nihonium": "Nh",
-                       "flerovium": "Fl", "moscovium": "Mc",
-                       "livermorium": "Lv", "tennessine": "Ts",
-                       "oganesson": "Og"}
+_atomic_symbols_dict = {'hydrogen': "H", 'helium': "He", 'lithium': "Li",
+                        'beryllium': "Be", 'boron': "B", 'carbon': "C",
+                        'nitrogen': "N", 'oxygen': "O", 'fluorine': "F",
+                        'neon': "Ne", 'sodium': "Na", 'magnesium': "Mg",
+                        'aluminium': "Al", 'silicon': "Si", 'phosphorus': "P",
+                        'sulfur': "S", 'chlorine': "Cl", 'argon': "Ar",
+                        'potassium': "K", 'calcium': "Ca", 'scandium': "Sc",
+                        'titanium': "Ti", 'vanadium': "V", 'chromium': "Cr",
+                        'manganese': "Mn", 'iron': "Fe", 'cobalt': "Co",
+                        'nickel': "Ni", 'copper': "Cu", 'zinc': "Zn",
+                        'gallium': "Ga", 'germanium': "Ge", 'arsenic': "As",
+                        'selenium': "Se", 'bromine': "Br", 'krypton': "Kr",
+                        'rubidium': "Rb", 'strontium': "Sr", 'yttrium': "Y",
+                        'zirconium': "Zr", 'niobium': "Nb", 'molybdenum': "Mo",
+                        'technetium': "Tc", 'ruthenium': "Ru", 'rhodium': "Rh",
+                        'palladium': "Pd", 'silver': "Ag", 'cadmium': "Cd",
+                        'indium': "In", 'tin': "Sn", 'antimony': "Sb",
+                        'tellurium': "Te", 'iodine': "I", 'xenon': "Xe",
+                        'caesium': "Cs", 'barium': "Ba", 'lanthanum': "La",
+                        'cerium': "Ce", 'praseodymium': "Pr",
+                        'neodymium': "Nd", 'promethium': "Pm",
+                        'samarium': "Sm", 'europium': "Eu", 'gadolinium': "Gd",
+                        'terbium': "Tb", 'dysprosium': "Dy", 'holmium': "Ho",
+                        'erbium': "Er", 'thulium': "Tm", 'ytterbium': "Yb",
+                        'lutetium': "Lu", 'hafnium': "Hf", 'tantalum': "Ta",
+                        'tungsten': "W", 'rhenium': "Re", 'osmium': "Os",
+                        'iridium': "Ir", 'platinum': "Pt", 'gold': "Au",
+                        'mercury': "Hg", 'thallium': "Tl", 'lead': "Pb",
+                        'bismuth': "Bi", 'polonium': "Po", 'astatine': "At",
+                        'radon': "Rn", 'francium': "Fr", 'radium': "Ra",
+                        'actinium': "Ac", 'thorium': "Th",
+                        'protactinium': "Pa", 'uranium': "U",
+                        'neptunium': "Np", 'plutonium': "Pu",
+                        'americium': "Am", 'curium': "Cm", 'berkelium': "Bk",
+                        'californium': "Cf", 'einsteinium': "Es",
+                        'fermium': "Fm", 'mendelevium': "Md", 'nobelium': "No",
+                        'lawrencium': "Lr", 'rutherfordium': "Rf",
+                        'dubnium': "Db", 'seaborgium': "Sg", 'bohrium': "Bh",
+                        'hassium': "Hs", 'meitnerium': "Mt",
+                        'darmstadtium': "Ds", 'roentgenium': "Rg",
+                        'copernicium': "Cn", 'nihonium': "Nh",
+                        'flerovium': "Fl", 'moscovium': "Mc",
+                        'livermorium': "Lv", 'tennessine': "Ts",
+                        'oganesson': "Og"}
 
-Elements = {
+
+_Elements = {
     "H": {"atomic_number": 1, "symbol": "H",
           "atomic_mass": 1.008*u.u, "name": "hydrogen"},
     "He": {"atomic_number": 2, "symbol": "He",
diff --git a/plasmapy/atomic/isotopes.py b/plasmapy/atomic/isotopes.py
index ea40990e..d0504238 100644
--- a/plasmapy/atomic/isotopes.py
+++ b/plasmapy/atomic/isotopes.py
@@ -26,7 +26,7 @@ def Iso(symbol, name, atomic_number, mass_number, relative_atomic_mass,
 # The atomic mass is given in units of u, and half-lives are in seconds.
 # The half-life data is presently incomplete.
 
-Isotopes = {
+_Isotopes = {
     'n': Iso('n', 'neutron', 0, 1, 1.00866491588, False, half_life=881.5),
     'H-1': Iso('H-1', 'hydrogen-1', 1, 1, 1.00782503223, True,
                isotopic_abundance=0.999885),
diff --git a/plasmapy/atomic/nuclear.py b/plasmapy/atomic/nuclear.py
index f203fec4..01828dd2 100644
--- a/plasmapy/atomic/nuclear.py
+++ b/plasmapy/atomic/nuclear.py
@@ -6,6 +6,7 @@
 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 ..utils import (InvalidElementError, InvalidIsotopeError)
 
 
 def nuclear_binding_energy(argument, mass_numb=None):
@@ -13,6 +14,7 @@ def nuclear_binding_energy(argument, mass_numb=None):
 
     Parameters
     ----------
+
     argument: string or integer
         A string representing an element or isotope, or an integer
         representing the atomic number of an element.
@@ -24,16 +26,19 @@ def nuclear_binding_energy(argument, mass_numb=None):
 
     Returns
     -------
+
     binding_energy: Quantity
         The binding energy of the nucleus in units of Joules.
 
     See also
     --------
+
     nuclear_reaction_energy : Returns the change in binding energy
         during nuclear fusion or fission reactions.
 
     Examples
     --------
+
     >>> from astropy import units as u
     >>> nuclear_binding_energy('Fe-56').to(u.MeV)
     <Quantity 492.25957876 MeV>
@@ -77,6 +82,7 @@ def nuclear_reaction_energy(*args, **kwargs):
 
     Parameters
     ----------
+
     reaction: string (optional, positional argument only)
         A string representing the reaction, like "D + T --> alpha + n"
         or "Be-8 --> 2*He-4"
@@ -92,6 +98,7 @@ def nuclear_reaction_energy(*args, **kwargs):
 
     Returns
     -------
+
     energy: Quantity
         The difference between the mass energy of the reactants and
         the mass energy of the products in a nuclear reaction.  This
@@ -101,6 +108,7 @@ def nuclear_reaction_energy(*args, **kwargs):
 
     Raises
     ------
+
     ValueError:
         If the reaction is not valid, there is insufficient
         information to determine an isotope, the baryon number is
@@ -112,10 +120,12 @@ def nuclear_reaction_energy(*args, **kwargs):
 
     See also
     --------
+
     nuclear_binding_energy : finds the binding energy of an isotope
 
     Notes
     -----
+
     This function requires either a string containing the nuclear
     reaction, or reactants and products as two keyword-only lists
     containing strings representing the isotopes and other particles
@@ -125,6 +135,7 @@ def nuclear_reaction_energy(*args, **kwargs):
 
     Examples
     --------
+
     >>> from astropy import units as u
     >>> nuclear_reaction_energy("D + T --> alpha + n")
     <Quantity 2.81812097e-12 J>
@@ -202,9 +213,11 @@ def _baryon_number(particles):
         for particle in particles:
             try:
                 baryon_number += mass_number(particle)
-            except ValueError:
+            except Exception:
                 if _is_antiproton(particle) or _is_antineutron(particle):
                     baryon_number -= 1
+                elif _is_neutron(particle):
+                    baryon_number += 1
 
         return baryon_number
 
@@ -217,7 +230,7 @@ def _total_charge(particles):
         for particle in particles:
             try:
                 total_charge += atomic_number(particle)
-            except ValueError:
+            except InvalidElementError:
                 total_charge += charge_state(particle)
 
         return total_charge
@@ -235,6 +248,8 @@ def _mass_energy(particles):
                 total_mass += constants.m_n
             elif _is_antiproton(particle):
                 total_mass += constants.m_p
+            elif _is_neutron(particle):
+                total_mass += constants.m_n
             else:
                 atomic_numb = atomic_number(particle)
                 total_mass += ion_mass(particle, Z=atomic_numb)
diff --git a/plasmapy/atomic/particles.py b/plasmapy/atomic/particles.py
index b9ff11f9..7e845b99 100644
--- a/plasmapy/atomic/particles.py
+++ b/plasmapy/atomic/particles.py
@@ -159,8 +159,8 @@ def _create_alias_dicts(Particles: dict) -> (typing.Dict[str, str],
         (['p+'], 'p'),
         (['n-1'], 'n'),
         (['H-2'], 'D'),
-        (['H-2+', 'H-2 1+', 'H-2 +1'], 'D 1+'),
-        (['H-3+', 'H-3 1+', 'H-3 +1'], 'T 1+'),
+        (['H-2+', 'H-2 1+', 'H-2 +1', 'D+'], 'D 1+'),
+        (['H-3+', 'H-3 1+', 'H-3 +1', 'T+'], 'T 1+'),
     ]
 
     case_insensitive_aliases_for_a_symbol = [
@@ -171,7 +171,7 @@ def _create_alias_dicts(Particles: dict) -> (typing.Dict[str, str],
         (['protium'], 'H-1'),
         (['protium+', 'protium 1+', 'protium +1'], 'p'),
         (['deuterium', 'hydrogen-2'], 'D'),
-        (['deuteron', 'deuterium+', 'deuterium 1+', 'deuterium +1', 'D+'],
+        (['deuteron', 'deuterium+', 'deuterium 1+', 'deuterium +1'],
          'D 1+'),
         (['tritium', 'hydrogen-3'], 'T'),
         (['triton', 'tritium+', 'tritium 1+', 'tritium +1'], 'T 1+'),
@@ -221,6 +221,22 @@ def _get_standard_symbol(alias: typing.Union[str, int]) -> str:
         return alias
 
 
+def _is_special_particle(alias: typing.Union[str, int]) -> bool:
+    r"""Returns true if a particle is a special particle, and False
+    otherwise."""
+
+    special_particles = [
+        'n', 'antineutron', 'p-',
+        'e-', 'e+', 'nu_e', 'anti_nu_e',
+        'mu-', 'mu+', 'nu_mu', 'anti_nu_mu',
+        'tau-', 'tau+', 'nu_tau', 'anti_nu_tau',
+    ]
+
+    symbol = _get_standard_symbol(alias)
+
+    return symbol in special_particles
+
+
 if __name__ == "__main__":  # coveralls: ignore
     from pprint import pprint
     print("Case insensitive aliases:")
diff --git a/plasmapy/physics/parameters.py b/plasmapy/physics/parameters.py
index 7563dd62..4ef53161 100644
--- a/plasmapy/physics/parameters.py
+++ b/plasmapy/physics/parameters.py
@@ -13,7 +13,7 @@
 # bit more than @quantity_input as it can allow
 import plasmapy.utils as utils
 from plasmapy.utils.checks import _check_quantity
-from plasmapy.utils.exceptions import PhysicsError  # , PhysicsWarning
+from plasmapy.utils.exceptions import PhysicsError, AtomicError  # , PhysicsWarning
 
 
 r"""
@@ -180,14 +180,14 @@ def Alfven_speed(B, density, ion="p", z_mean=None):
                 #               PhysicsWarning)
                 try:
                     Z = atomic.charge_state(ion)
-                except ValueError:
+                except AtomicError:
                     Z = 1
             else:
                 # using average ionization provided by user
                 Z = z_mean
             # ensuring positive value of Z
             Z = np.abs(Z)
-        except Exception:
+        except AtomicError:
             raise ValueError("Invalid ion in Alfven_speed.")
         rho = density * m_i + Z * density * m_e
 
@@ -328,12 +328,12 @@ def ion_sound_speed(*ignore,
             #               PhysicsWarning)
             try:
                 Z = atomic.charge_state(ion)
-            except ValueError:
+            except AtomicError:
                 Z = 1
         else:
             # using average ionization provided by user
             Z = z_mean
-    except Exception:
+    except AtomicError:
         raise ValueError("Invalid ion in ion_sound_speed.")
 
     if not isinstance(gamma_e, (float, int)):
@@ -445,7 +445,7 @@ def thermal_speed(T, particle="e", method="most_probable"):
 
     try:
         m = atomic.ion_mass(particle)
-    except Exception:
+    except AtomicError:
         raise ValueError("Unable to find {particle} mass in thermal_speed")
 
     # different methods, as per https://en.wikipedia.org/wiki/Thermal_velocity
@@ -649,7 +649,7 @@ def gyrofrequency(B, particle='e', signed=False, z_mean=None):
             #               PhysicsWarning)
             try:
                 Z = atomic.charge_state(particle)
-            except ValueError:
+            except AtomicError:
                 Z = 1
         else:
             # using user provided average ionization
@@ -813,7 +813,7 @@ def plasma_frequency(n, particle='e', z_mean=None):
         a macroscopic description is valid. If this quantity is not
         given then the atomic charge state (integer) of the ion
         is used. This is effectively an average plasma frequency for the
-        plasma where multiple charge states are present. 
+        plasma where multiple charge states are present.
 
     Returns
     -------
@@ -867,7 +867,7 @@ def plasma_frequency(n, particle='e', z_mean=None):
             #               PhysicsWarning)
             try:
                 Z = atomic.charge_state(particle)
-            except ValueError:
+            except Exception:
                 Z = 1
         else:
             # using user provided average ionization
@@ -1078,7 +1078,7 @@ def inertial_length(n, particle='e'):
 
     try:
         Z = atomic.charge_state(particle)
-    except Exception:
+    except AtomicError:
         raise ValueError(f"Invalid particle {particle} in inertial_length.")
     if Z:
         Z = abs(Z)
diff --git a/plasmapy/utils/__init__.py b/plasmapy/utils/__init__.py
index 1b9b8fc0..36109614 100644
--- a/plasmapy/utils/__init__.py
+++ b/plasmapy/utils/__init__.py
@@ -1,4 +1,22 @@
-from .checks import check_quantity, check_relativistic, _check_quantity, _check_relativistic
-from .exceptions import (PlasmaPyError, PhysicsError, RelativityError, AtomicError, MissingAtomicDataError, NoChargeInfoError, IonError, IsotopeError, ElementError, ParticleError,
-                        PlasmaPyWarning, PhysicsWarning, RelativityWarning, AtomicWarning, MissingAtomicDataWarning)
+from .checks import (check_quantity,
+                     check_relativistic,
+                     _check_quantity,
+                     _check_relativistic)
+
+from .exceptions import (PlasmaPyError,
+                         PhysicsError,
+                         RelativityError,
+                         AtomicError,
+                         MissingAtomicDataError,
+                         ChargeError,
+                         InvalidIonError,
+                         InvalidIsotopeError,
+                         InvalidElementError,
+                         InvalidParticleError,
+                         PlasmaPyWarning,
+                         PhysicsWarning,
+                         RelativityWarning,
+                         AtomicWarning,
+                         MissingAtomicDataWarning)
+
 from .import_helpers import check_versions
diff --git a/plasmapy/utils/exceptions.py b/plasmapy/utils/exceptions.py
index 32dd3e56..aa584872 100644
--- a/plasmapy/utils/exceptions.py
+++ b/plasmapy/utils/exceptions.py
@@ -8,60 +8,70 @@
 # ----------
 
 class PlasmaPyError(Exception):
-    """
+    r"""
     Base class of PlasmaPy custom errors.
 
-    All custom exceptions raised by PlasmaPy should inherit from this class
-    and be defined in this module.
+    All custom exceptions raised by PlasmaPy should inherit from this
+    class and be defined in this module.
 
-    Custom exceptions can inherit from other exception types too. Thus, if code
-    already knows how to handle a ValueError, it won't need any specific
-    modification.
+    Custom exceptions can inherit from other exception types too.
+    Thus, if code already knows how to handle a ValueError, it won't
+    need any specific modification.
     """
     pass
 
 
 class PhysicsError(PlasmaPyError, ValueError):
-    """Error for use of a physics value outside PlasmaPy theoretical bounds"""
+    r"""Error for use of a physics value outside PlasmaPy theoretical
+    bounds"""
     pass
 
 
 class RelativityError(PhysicsError):
-    """Error for use of a speed greater than or equal to the speed of light"""
+    r"""Error for use of a speed greater than or equal to the speed of
+    light"""
     pass
 
 
 class AtomicError(PlasmaPyError):
-    """Error for use by an atomic subpackage"""
+    r"""An exception for errors occurring in the atomic subpackage."""
     pass
 
 
 class MissingAtomicDataError(AtomicError):
-    """Error for use when atomic data is missing."""
+    r"""An exception for when atomic data is missing."""
     pass
 
 
-class NoChargeInfoError(AtomicError):
-    """Error for use when charge information is needed but missing."""
+class ChargeError(AtomicError):
+    r"""An exception for when charge information is incorrect or
+    missing."""
+    pass
+
+
+class UnexpectedParticleError(AtomicError):
+    r"""An exception for when a particle is not of the expected
+    category."""
+    pass
 
 
-class IonError(NoChargeInfoError):
-    """Error for use when an ion is invalid."""
+class InvalidIonError(UnexpectedParticleError):
+    r"""An exception for when an argument is not an ion."""
     pass
 
 
-class IsotopeError(AtomicError):
-    """Error for use when an isotope is invalid."""
+class InvalidIsotopeError(UnexpectedParticleError):
+    r"""An exception for when an argument is not an isotope."""
     pass
 
 
-class ElementError(IsotopeError, IonError):
-    """Error for use when an element is invalid."""
+class InvalidElementError(UnexpectedParticleError):
+    r"""An exception for when an argument is not an element."""
     pass
 
 
-class ParticleError(ElementError):
-    """Error for use when a particle is invalid."""
+class InvalidParticleError(AtomicError):
+    r"""An exception for when a particle is invalid."""
     pass
 
 
@@ -70,7 +80,7 @@ class ParticleError(ElementError):
 # ----------
 
 class PlasmaPyWarning(Warning):
-    """Base class of PlasmaPy custom warnings.
+    r"""Base class of PlasmaPy custom warnings.
 
     All PlasmaPy custom warnings should inherit from this class and be defined
     in this module.
@@ -82,20 +92,20 @@ class PlasmaPyWarning(Warning):
 
 
 class PhysicsWarning(PlasmaPyWarning):
-    """Warning for using a mildly worrisome physics value"""
+    r"""Warning for using a mildly worrisome physics value"""
     pass
 
 
 class RelativityWarning(PhysicsWarning):
-    """Warning for use of a speed quantity approaching the speed of light"""
+    r"""Warning for use of a speed quantity approaching the speed of light"""
     pass
 
 
 class AtomicWarning(PlasmaPyWarning):
-    """Warnings for use in the atomic subpackage."""
+    r"""Warnings for use in the atomic subpackage."""
     pass
 
 
 class MissingAtomicDataWarning(AtomicWarning):
-    """Warning for use when atomic data is missing."""
+    r"""Warning for use when atomic data is missing."""
     pass


Test patch:
diff --git a/plasmapy/atomic/tests/test_atomic.py b/plasmapy/atomic/tests/test_atomic.py
index 86b16cbc..594e9362 100644
--- a/plasmapy/atomic/tests/test_atomic.py
+++ b/plasmapy/atomic/tests/test_atomic.py
@@ -19,7 +19,7 @@
                       isotopic_abundance,
                       charge_state,
                       electric_charge,
-                      Isotopes,
+                      _Isotopes,
                       _is_neutron,
                       _is_hydrogen,
                       _is_electron,
@@ -29,6 +29,15 @@
                       _extract_charge_state,
                       _is_proton)
 from ..nuclear import (nuclear_binding_energy, nuclear_reaction_energy)
+from ...utils import (AtomicWarning,
+                      InvalidElementError,
+                      InvalidIsotopeError,
+                      InvalidIonError,
+                      ChargeError,
+                      AtomicError,
+                      MissingAtomicDataError,
+                      MissingAtomicDataWarning,
+                      InvalidParticleError)
 
 # (argument, expected)
 atomic_symbol_table = [
@@ -81,24 +90,26 @@ def test_atomic_symbol(argument, expected):
 
 # (argument, expected_error)
 atomic_symbol_error_table = [
-    ('H-0', ValueError),
+    ('H-0', InvalidParticleError),
     (3.14159, TypeError),
-    ('Og-294b', ValueError),
-    ('H-934361079326356530741942970523610389', ValueError),
-    ('Fe 2+4', ValueError),
-    ('Fe+24', ValueError),
-    ('Fe +59', ValueError),
-    ('C++++++++++++++++', ValueError),
-    ('C-++++', ValueError),
-    ('neutron', ValueError),
-    ('n', ValueError),
-    ('n-1', ValueError),
-    ('h', ValueError),
-    ('d', ValueError),
-    ('he', ValueError),
-    ('au', ValueError),
-    ('p-', ValueError),
-    ('antiproton', ValueError)]
+    ('Og-294b', InvalidParticleError),
+    ('H-934361079326356530741942970523610389', InvalidParticleError),
+    ('Fe 2+4', InvalidParticleError),
+    ('Fe+24', InvalidParticleError),
+    ('Fe +59', InvalidParticleError),
+    ('C++++++++++++++++', InvalidParticleError),
+    ('C-++++', InvalidParticleError),
+    ('neutron', InvalidElementError),
+    ('n', InvalidElementError),
+    ('n-1', InvalidElementError),
+    ('h', InvalidParticleError),
+    ('d', InvalidParticleError),
+    ('he', InvalidParticleError),
+    ('au', InvalidParticleError),
+    ('p-', InvalidElementError),
+    (0, InvalidParticleError),
+    (119, InvalidParticleError),
+    ('antiproton', InvalidElementError)]
 
 
 @pytest.mark.parametrize(
@@ -158,30 +169,29 @@ def test_isotope_symbol(arguments, expected):
 
 # (argument, kwargs, expected_error)
 isotope_symbol_error_table = [
-    ('Md-260', {"mass_numb": 261}, ValueError),
-    ('protium', {"mass_numb": 2}, ValueError),
-    ('alpha', {"mass_numb": 3}, ValueError),
-    ('O-18', {"mass_numb": 19}, ValueError),
-    ('lead-209', {"mass_numb": 511}, ValueError),
-    ('He-1', {}, ValueError),
-    (24, {"mass_numb": 23}, ValueError),
-    ('H', {"mass_numb": 0}, ValueError),
-    ('H-1', {"mass_numb": 2}, ValueError),
-    ('P', {}, ValueError),
-    (1, {}, ValueError),
-    (4, {}, ValueError),
-    ('hydrogen-444444', {}, ValueError),
+    ('Md-260', {"mass_numb": 261}, InvalidParticleError),
+    ('protium', {"mass_numb": 2}, InvalidParticleError),
+    ('alpha', {"mass_numb": 3}, InvalidParticleError),
+    ('O-18', {"mass_numb": 19}, InvalidParticleError),
+    ('lead-209', {"mass_numb": 511}, InvalidParticleError),
+    ('He-1', {}, InvalidParticleError),
+    (24, {"mass_numb": 23}, InvalidParticleError),
+    ('H', {"mass_numb": 0}, InvalidParticleError),
+    ('H-1', {"mass_numb": 2}, InvalidParticleError),
+    ('P', {}, InvalidIsotopeError),
+    (1, {}, InvalidIsotopeError),
+    (4, {}, InvalidIsotopeError),
+    ('hydrogen-444444', {}, InvalidParticleError),
     ('Fe', {"mass_numb": 2.1}, TypeError),
     ('He', {"mass_numb": 'c'}, TypeError),
-    ('He-3', {"mass_numb": 4}, ValueError),
-    ('alpha', {"mass_numb": 3}, ValueError),
-    ('D', {"mass_numb": 3}, ValueError),
-    ('T', {"mass_numb": 2}, ValueError),
-    ('Fe', {"mass_numb": None}, ValueError),
-    ('d', {}, ValueError),
-    ('h-3', {}, ValueError),
-    ('h', {}, ValueError),
-    ('d+', {}, ValueError),
+    ('He-3', {"mass_numb": 4}, InvalidParticleError),
+    ('D', {"mass_numb": 3}, InvalidParticleError),
+    ('T', {"mass_numb": 2}, InvalidParticleError),
+    ('Fe', {"mass_numb": None}, InvalidIsotopeError),
+    ('d', {}, InvalidParticleError),
+    ('h-3', {}, InvalidParticleError),
+    ('h', {}, InvalidParticleError),
+    ('d+', {}, InvalidParticleError),
 ]
 
 
@@ -197,13 +207,13 @@ def test_isotope_symbol_error(argument, kwargs, expected_error):
 
 # (argument, kwargs, expected_warning)
 isotope_symbol_warning_table = [
-    ('H-1', {"mass_numb": 1}, UserWarning),
-    ('H-2', {"mass_numb": 2}, UserWarning),
-    ('T', {"mass_numb": 3}, UserWarning),
-    ('Li-6', {"mass_numb": 6}, UserWarning),
-    ('lithium-6', {"mass_numb": 6}, UserWarning),
-    ('alpha', {"mass_numb": 4}, UserWarning),
-    ('p', {"mass_numb": 1}, UserWarning)]
+    ('H-1', {"mass_numb": 1}, AtomicWarning),
+    ('H-2', {"mass_numb": 2}, AtomicWarning),
+    ('T', {"mass_numb": 3}, AtomicWarning),
+    ('Li-6', {"mass_numb": 6}, AtomicWarning),
+    ('lithium-6', {"mass_numb": 6}, AtomicWarning),
+    ('alpha', {"mass_numb": 4}, AtomicWarning),
+    ('p', {"mass_numb": 1}, AtomicWarning)]
 
 
 @pytest.mark.parametrize(
@@ -248,16 +258,16 @@ def test_atomic_number(argument, expected):
 
 # (argument, expected_error)
 atomic_number_error_table = [
-    ('H-3934', ValueError),
-    ('C-12b', ValueError),
+    ('H-3934', InvalidParticleError),
+    ('C-12b', InvalidParticleError),
     (-1.5, TypeError),
-    ('n', ValueError),
-    ('n-1', ValueError),
-    ('neutron', ValueError),
-    ('Neutron', ValueError),
-    ('d', ValueError),
-    ('t', ValueError),
-    ('s-36', ValueError)]
+    ('n', InvalidElementError),
+    ('n-1', InvalidElementError),
+    ('neutron', InvalidElementError),
+    ('Neutron', InvalidElementError),
+    ('d', InvalidParticleError),
+    ('t', InvalidParticleError),
+    ('s-36', InvalidParticleError)]
 
 
 @pytest.mark.parametrize(
@@ -301,11 +311,11 @@ def test_mass_number(isotope, expected):
 
 # (argument, expected_error)
 mass_number_error_table = [
-    ('H-359', ValueError),
-    ('C-12b', ValueError),
+    ('H-359', InvalidParticleError),
+    ('C-12b', InvalidParticleError),
     (-1.5, Exception),
-    ('N-13+-+-', ValueError),
-    ('h-3', ValueError)]
+    ('N-13+-+-', InvalidParticleError),
+    ('h-3', InvalidParticleError)]
 
 
 @pytest.mark.parametrize(
@@ -354,17 +364,20 @@ def test_element_name(argument, expected):
 
 # (argument, expected_error)
 element_name_error_table = [
-    ('vegan cupcakes', ValueError),
-    ('C-13-14-15-51698024', ValueError),
+    ('vegancupcakes', InvalidParticleError),
+    ('C-+-', InvalidParticleError),
     (1.24, TypeError),
-    ('n', ValueError),
-    ('neutron', ValueError),
-    (0, ValueError),
-    ('H++', ValueError),
-    ('t', ValueError),
-    ('pb', ValueError),
-    ('d', ValueError),
-    ('h-3', ValueError)]
+    ('n', InvalidElementError),
+    ('neutron', InvalidElementError),
+    (0, InvalidParticleError),
+    ('H++', InvalidParticleError),
+    ('t', InvalidParticleError),
+    ('pb', InvalidParticleError),
+    ('d', InvalidParticleError),
+    ('h-3', InvalidParticleError),
+    ('Pb-9', InvalidParticleError),
+    ('H 2+', InvalidParticleError),
+]
 
 
 @pytest.mark.parametrize("argument, expected_error", element_name_error_table)
@@ -405,19 +418,19 @@ def test_standard_atomic_weight(argument, expected):
 
 # (argument, expected_error)
 standard_atomic_weight_error_table = [
-    ('H-1', ValueError),
-    ("help i'm trapped in a unit test", ValueError),
+    ('H-1', AtomicError),
+    ("help i'm trapped in a unit test", InvalidParticleError),
     (1.1, TypeError),
-    ('n', ValueError),
-    ('p', ValueError),
-    ('alpha', ValueError),
-    ('deuteron', ValueError),
-    ('tritium', ValueError),
-    ('Au+', ValueError),
-    ('Fe -2', ValueError),
-    ('Og 2+', ValueError),
-    ('h', ValueError),
-    ('fe', ValueError)]
+    ('n', InvalidElementError),
+    ('p', AtomicError),
+    ('alpha', AtomicError),
+    ('deuteron', AtomicError),
+    ('tritium', AtomicError),
+    ('Au+', AtomicError),
+    ('Fe -2', AtomicError),
+    ('Og 2+', AtomicError),
+    ('h', InvalidParticleError),
+    ('fe', InvalidParticleError)]
 
 
 @pytest.mark.parametrize("argument, expected_error",
@@ -471,17 +484,17 @@ def test_isotope_mass(arg1, arg2):
 
 # (argument, expected_error)
 isotope_mass_error_table = [
-    ("H", ValueError),
+    ("H", InvalidIsotopeError),
     (1.1, TypeError),
-    ('alpha', ValueError),
-    ('He-4 2+', ValueError),
-    ('he-4', ValueError),
-    ('Fe 2+', ValueError),
-    ('Fe -2', ValueError),
-    ('deuteron', ValueError),
-    ('triton', ValueError),
-    ('H-1 +1', ValueError),
-    ('H-1+', ValueError)]
+    ('alpha', AtomicError),
+    ('He-4 2+', AtomicError),
+    ('he-4', InvalidParticleError),
+    ('Fe 2+', AtomicError),
+    ('Fe -2', AtomicError),
+    ('deuteron', AtomicError),
+    ('triton', AtomicError),
+    ('H-1 +1', AtomicError),
+    ('H-1+', AtomicError)]
 
 
 @pytest.mark.parametrize("argument, expected_error", isotope_mass_error_table)
@@ -580,19 +593,19 @@ def test_ion_mass_equivalent_args(arg1, kwargs1, arg2, kwargs2, expected):
 
 # (argument, kwargs, expected_error)
 ion_mass_error_table = [
-    ('0g 1+', {}, ValueError),  # since it has no standard atomic weight
+    ('Og 1+', {}, MissingAtomicDataError),
     ('Fe-56', {"Z": 1.4}, TypeError),
-    ('n', {}, ValueError),
-    ('H-1 +1', {"Z": 0}, ValueError),
+    ('n', {}, InvalidIonError),
+    ('H-1 +1', {"Z": 0}, AtomicError),
     (26, {"Z": 1, "mass_numb": 'a'}, TypeError),
-    (26, {"Z": 27, "mass_numb": 56}, ValueError),
-    ('Og', {"Z": 1}, ValueError),
-    ('Og', {"mass_numb": 296, "Z": 1}, ValueError),
-    ('n', {}, ValueError),
-    ('He 1+', {"mass_numb": 99}, ValueError),
+    (26, {"Z": 27, "mass_numb": 56}, InvalidParticleError),
+    ('Og', {"Z": 1}, MissingAtomicDataError),
+    ('Og', {"mass_numb": 696, "Z": 1}, InvalidParticleError),
+    ('n', {}, InvalidIonError),
+    ('He 1+', {"mass_numb": 99}, InvalidParticleError),
     (1 * u.m, {}, u.UnitConversionError),
-    ('Og', {"Z": 1}, ValueError),
-    ('fe-56 1+', {}, ValueError)]
+    ('Og', {"Z": 1}, MissingAtomicDataError),
+    ('fe-56 1+', {}, InvalidParticleError)]
 
 
 @pytest.mark.parametrize("argument, kwargs, expected_error",
@@ -607,8 +620,8 @@ def test_ion_mass_error(argument, kwargs, expected_error):
 
 # (argument, kwargs, expected_warning)
 ion_mass_warning_table = [
-    (1.6e-27 * u.kg, {}, UserWarning),
-    (8e-25 * u.kg, {}, UserWarning)]
+    (1.6e-27 * u.kg, {}, AtomicWarning),
+    (8e-25 * u.kg, {}, AtomicWarning)]
 
 
 @pytest.mark.parametrize("argument, kwargs, expected_warning",
@@ -678,11 +691,11 @@ def test_is_isotope_stable_false(argument):
 
 # (argument, expected_error)
 is_isotope_stable_error_table = [
-    (('hydrogen-444444',), ValueError),
-    (('hydrogen', 0), ValueError),
-    (('',), ValueError),
-    (('pb-209',), ValueError),
-    (('h',), ValueError)]
+    (('hydrogen-444444',), InvalidParticleError),
+    (('hydrogen', 0), InvalidParticleError),
+    (('',), InvalidParticleError),
+    (('pb-209',), InvalidParticleError),
+    (('h',), InvalidParticleError)]
 
 
 @pytest.mark.parametrize("argument, expected_error",
@@ -731,11 +744,11 @@ def test_half_life():
 def test_half_life_unstable_isotopes():
     """Test that half_life returns None and issues a warning for all isotopes
     that do not yet have half-life data."""
-    for isotope in Isotopes.keys():
-        if 'half_life' not in Isotopes[isotope].keys() and \
-                not Isotopes[isotope].keys():
-            with pytest.warns(UserWarning, message=(
-                    f"No UserWarning issued for {isotope}")):
+    for isotope in _Isotopes.keys():
+        if 'half_life' not in _Isotopes[isotope].keys() and \
+                not _Isotopes[isotope].keys():
+            with pytest.warns(AtomicWarning, message=(
+                    f"No AtomicWarning issued for {isotope}")):
                 assert half_life(isotope) is None
 
 
@@ -749,14 +762,14 @@ def test_half_life_u_220():
 
     isotope_without_half_life_data = "U-220"
 
-    with pytest.warns(UserWarning):
+    with pytest.warns(AtomicWarning):
 
         try:
             half_life_isotope = half_life(isotope_without_half_life_data)
         except Exception:
-            raise ValueError(f"half_life is raising an exception instead of "
-                             f"issuing a UserWarning for an isotope without "
-                             f"half-life data")
+            raise Exception(f"half_life is raising an exception instead of "
+                            f"issuing a AtomicWarning for an isotope without "
+                            f"half-life data")
 
         assert half_life_isotope is None, \
             (f"half_life should return None for an isotope without half-life "
@@ -792,21 +805,53 @@ def test_atomic_TypeErrors(func, argument):
         func(argument)
 
 
-atomic_ValueErrors_funcs_table = [
-    atomic_symbol, isotope_symbol, atomic_number,
-    is_isotope_stable, half_life, mass_number,
-    element_name, standard_atomic_weight]
-atomic_ValueError_bad_arguments = [-1, 119, 'grumblemuffins', 'Oj']
+atomic_ParticleErrors_funcs_table = [
+    atomic_symbol,
+    isotope_symbol,
+    atomic_number,
+    is_isotope_stable,
+    half_life,
+    mass_number,
+    element_name,
+    standard_atomic_weight,
+    ion_mass,
+    known_isotopes,
+    stable_isotopes,
+    common_isotopes,
+    isotopic_abundance,
+    charge_state,
+    electric_charge,
+]
+
+atomic_ParticleError_bad_arguments = [
+    -1,
+    119,
+    'grumblemuffins',
+    'H-0',
+    'Og-294b',
+    'H-934361079326356530741942970523610389',
+    'Fe 2+4',
+    'Fe+24',
+    'Fe +59',
+    'C++++++++++++++++',
+    'C-++++',
+    'h',
+    'd',
+    'he',
+    'au',
+    'alpha 1+',
+    'alpha-4',
+]
 
 
 @pytest.mark.parametrize(
     "func, argument",
-    product(atomic_ValueErrors_funcs_table,
-            atomic_ValueError_bad_arguments))
-def test_atomic_ValueErrors(func, argument):
-    """Test that atomic functions raise ValueErrors when incorrect
+    product(atomic_ParticleErrors_funcs_table,
+            atomic_ParticleError_bad_arguments))
+def test_atomic_ParticleErrors(func, argument):
+    """Test that atomic functions raise ElementErrors when incorrect
     arguments are provided."""
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidParticleError):
         func(argument)
 
 
@@ -857,9 +902,9 @@ def test_known_common_stable_isotopes_len():
     "func", [common_isotopes, stable_isotopes, known_isotopes])
 def test_known_common_stable_isotopes_error(func):
     """Test that known_isotopes, common_isotopes, and stable_isotopes
-    raise ValueErrors for neutrons."""
-    with pytest.raises(ValueError, message=(
-            f"{func} is not raising a ValueError for neutrons.")):
+    raise ElementErrors for neutrons."""
+    with pytest.raises(InvalidElementError, message=(
+            f"{func} is not raising a ElementError for neutrons.")):
         func('n')
 
 
@@ -872,10 +917,11 @@ def test_isotopic_abundance():
     assert isotopic_abundance('Li-8') == 0.0, 'Li-8'
     assert isotopic_abundance('Og', 294) == 0.0
 
-    with pytest.raises(ValueError, message="No exception raised for neutrons"):
+    with pytest.raises(InvalidIsotopeError, message="No exception raised for "
+                                                    "neutrons"):
         isotopic_abundance('neutron')
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidParticleError):
         isotopic_abundance('Og-2')
 
 
@@ -935,13 +981,13 @@ def test_charge_state(argument, expected):
 
 # (argument, expected_error)
 charge_state_error_table = [
-    ('fads', ValueError),
-    ('H++', ValueError),
-    ('h+', ValueError),
-    ('fe 1+', ValueError),
-    ('d+', ValueError),
-    ('Fe 29+', ValueError),
-    ('H-1', ValueError),
+    ('fads', InvalidParticleError),
+    ('H++', InvalidParticleError),
+    ('h+', InvalidParticleError),
+    ('fe 1+', InvalidParticleError),
+    ('d+', InvalidParticleError),
+    ('Fe 29+', InvalidParticleError),
+    ('H-1', ChargeError),
 ]
 
 
@@ -955,9 +1001,9 @@ def test_charge_state_error(argument, expected_error):
 
 # (argument, expected_warning)
 charge_state_warning_table = [
-    ('H---', UserWarning),
-    ('Fe -26', UserWarning),
-    ('Og 10-', UserWarning)]
+    ('H---', AtomicWarning),
+    ('Fe -26', AtomicWarning),
+    ('Og 10-', AtomicWarning)]
 
 
 @pytest.mark.parametrize("argument, expected_warning",
@@ -981,10 +1027,9 @@ def test_electric_charge():
 
 # (argument, expected_error)
 electric_charge_error_table = [
-    ('badinput', ValueError),
-    (' ', ValueError),
-    ('h+', ValueError),
-    ('Au 81+', ValueError)]
+    ('badinput', InvalidParticleError),
+    ('h+', InvalidParticleError),
+    ('Au 81+', InvalidParticleError)]
 
 
 @pytest.mark.parametrize("argument, expected_error",
@@ -999,8 +1044,8 @@ def test_electric_charge_error(argument, expected_error):
 
 # (argument, expected_warning)
 electric_charge_warning_table = [
-    ('Au 81-', UserWarning),
-    ('H---', UserWarning)]
+    ('Au 81-', AtomicWarning),
+    ('H---', AtomicWarning)]
 
 
 @pytest.mark.parametrize("argument, expected_warning",
@@ -1067,8 +1112,8 @@ def test_is_hydrogen(test_input, can_be_atom_numb, expected):
 
 
 @pytest.mark.parametrize("test_input,kwargs,expected_error",
-                         [('H 2+', {}, ValueError),
-                          ('D++', {}, ValueError)])
+                         [('H 2+', {}, InvalidParticleError),
+                          ('D++', {}, InvalidParticleError)])
 def test_is_hydrogen_errors(test_input, kwargs, expected_error):
     """Test that _is_hydrogen raises the expected exceptions."""
     with pytest.raises(expected_error):
@@ -1198,7 +1243,7 @@ def test_is_alpha(test_input, expected):
                          [('H', 'H', None),
                           ('H+', 'H', 1),
                           ('D 1+', 'D', 1),
-                          ('alpha', 'alpha', 2),
+                          ('alpha', 'He-4', 2),
                           ('Fe', 'Fe', None),
                           ('Titanium', 'Titanium', None),
                           ('N-7+++', 'N-7', 3),
@@ -1216,9 +1261,9 @@ def test_extract_charge_state(test_input, expected_newarg, expected_Z):
 
 
 @pytest.mark.parametrize("test_input,expected_error",
-                         [('H-1-+-+', ValueError),
-                          ('H ++', ValueError),
-                          ('Fe +21+', ValueError)])
+                         [('H-1-+-+', InvalidParticleError),
+                          ('H ++', InvalidParticleError),
+                          ('Fe +21+', InvalidParticleError)])
 def test_extract_charge_state_errors(test_input, expected_error):
     """Test that _extract_charge_state raises the expected exceptions."""
     with pytest.raises(expected_error):
@@ -1226,9 +1271,9 @@ def test_extract_charge_state_errors(test_input, expected_error):
 
 
 @pytest.mark.parametrize("test_input,expected_warning",
-                         [('H-1----', UserWarning),
-                          ('Fe -4', UserWarning),
-                          ('lead 4-', UserWarning)])
+                         [('H-1----', AtomicWarning),
+                          ('Fe -4', AtomicWarning),
+                          ('lead 4-', AtomicWarning)])
 def test_extract_charge_state_warnings(test_input, expected_warning):
     """Test that _extract_charge_state issues the expected warnings."""
     with pytest.warns(expected_warning):
diff --git a/plasmapy/atomic/tests/test_nuclear.py b/plasmapy/atomic/tests/test_nuclear.py
index 627eeca2..b1cb9c9f 100644
--- a/plasmapy/atomic/tests/test_nuclear.py
+++ b/plasmapy/atomic/tests/test_nuclear.py
@@ -2,7 +2,7 @@
 from astropy import units as u, constants as const
 import numpy as np
 from ..nuclear import (nuclear_binding_energy, nuclear_reaction_energy)
-
+from ...utils import (InvalidElementError, InvalidIsotopeError)
 import pytest
 
 
@@ -22,7 +22,7 @@ def test_nuclear_binding_energy_D_T():
 
 # (argument, expected_error)
 nuclear_binding_energy_table = [
-    ("H", ValueError),
+    ("H", InvalidIsotopeError),
     (1.1, TypeError)]
 
 
diff --git a/plasmapy/utils/tests/test_exceptions.py b/plasmapy/utils/tests/test_exceptions.py
index fcc89faa..88c1bf08 100644
--- a/plasmapy/utils/tests/test_exceptions.py
+++ b/plasmapy/utils/tests/test_exceptions.py
@@ -9,11 +9,11 @@
     RelativityError,
     AtomicError,
     MissingAtomicDataError,
-    NoChargeInfoError,
-    IonError,
-    IsotopeError,
-    ElementError,
-    ParticleError,
+    ChargeError,
+    InvalidIonError,
+    InvalidIsotopeError,
+    InvalidElementError,
+    InvalidParticleError,
 ]