Repository: PlasmaPy/PlasmaPy

Issue:
Apparent bug in ion contribution to Thomson scattering spectrum
We received a bug report over email.  

> I believe there may be a bug in the computation of the ion contribution for the Thomson scattering in diagnostics/thomson.py.  Specifically, the contribution from the species' charge ([line 247](https://github.com/PlasmaPy/PlasmaPy/blob/main/src/plasmapy/diagnostics/thomson.py#L247)) appears to be slightly incorrect. Rather than `ion_z​` it should be `ion_z**2 / zbar​` according to [Sheffield](https://www.sciencedirect.com/book/9780123748775/plasma-scattering-of-electromagnetic-radiation) (Section 5.1, Eq. 5.1.2 and following on page 104 - see screenshot below)
![unnamed](https://github.com/PlasmaPy/PlasmaPy/assets/8931994/a8ac4e2b-ef4f-4439-b07c-8192553f002c)



@pheuer — could you look into this?


Discussion:
github-actions[bot]: Thank you for submitting a pull request (PR) to PlasmaPy! ✨ The future of the project depends on contributors like you, so we deeply appreciate it! 🌱

  Our [**contributor guide**](https://docs.plasmapy.org/en/latest/contributing/index.html) has information on:

   - [Getting ready to contribute](https://docs.plasmapy.org/en/latest/contributing/getting_ready.html#getting-ready-to-contribute) and the [code contribution workflow](https://docs.plasmapy.org/en/latest/contributing/workflow.html#code-contribution-workflow)
   - Using [`astropy.units`](https://docs.plasmapy.org/en/latest/notebooks/getting_started/units.html#Using-Astropy-Units) and [`plasmapy.particles`](https://docs.plasmapy.org/en/latest/notebooks/getting_started/particles.html#Using-PlasmaPy-Particles)
   - [Coding tips and guidelines](https://docs.plasmapy.org/en/latest/contributing/coding_guide.html#coding-guide)
   - [Writing documentation](https://docs.plasmapy.org/en/latest/contributing/doc_guide.html#writing-documentation) (in [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#restructuredtext-primer) with [numpydoc style docstrings](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard)).
   - [Writing tests](https://docs.plasmapy.org/en/latest/contributing/testing_guide.html#testing-guide) using [pytest](https://docs.pytest.org)
   - [Adding a changelog entry](https://docs.plasmapy.org/en/latest/contributing/changelog_guide.html#adding-a-changelog-entry) (needed except for minor changes)

  > [!IMPORTANT]
  > PlasmaPy recently switched to an [`src`](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/) layout. The source code previously in `plasmapy/` is now in [`src/plasmapy/`](https://github.com/PlasmaPy/PlasmaPy/tree/main/src/plasmapy). Tests are now in [`tests/`](https://github.com/PlasmaPy/PlasmaPy/tree/main/tests). If you have previously done an editable installation, it will likely need to be re-done (i.e., with `pip install -e .[tests,docs]` in the top-level directory of the repository). The former `plasmapy/` directory will need to be deleted manually in old clones because `git` does not track directories.

  The bottom of this page shows several checks that are run for every PR. Don't worry if something broke! We break stuff all the time. 😺 Click on "Details" to learn why a check didn't pass. Please also feel free to ask for help. We do that all the time as well. 🌸 You can find us in our [**chat room**](https://docs.plasmapy.org/en/latest/contributing/coding_guide.html) or weekly [**community meeting**](https://www.plasmapy.org/meetings/weekly) & [**office hours**](https://www.plasmapy.org/meetings/office_hours). Here are some tips:

   - Try fixing **CI / Python 3.12** test failures first.
   - Most **pre-commit.ci - pr** failures can be automagically fixed by commenting `pre-commit.ci autofix` below, followed by a `git pull` to bring the changes back to your computer. Please also see our [pre-commit troubleshooting guide](https://docs.plasmapy.org/en/stable/contributing/pre-commit.html#troubleshooting-pre-commit-failures).
   - If **pre-commit.ci - pr** says that a function is too long or complex, try breaking up that function into multiple short functions that each do one thing. See also these tips on [**writing clean scientific software**](https://doi.org/10.5281/zenodo.3922956).
   - If the **CI / Documentation** check ends with a cryptic error message, check out our [documentation troubleshooting guide](https://docs.plasmapy.org/en/latest/contributing/doc_guide.html#troubleshooting).
   - For a documentation preview, click on *Details* next to **docs/readthedocs.org:plasmapy**.

  If this PR is marked as ready for review, someone should stop by to provide a code review and offer suggestions soon. ✅ If you don't get a review within a few days, please feel free to send us a reminder.

  Please also use SI units within PlasmaPy, except when there is strong justification otherwise or in some examples.

  We thank you once again!
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/PlasmaPy/PlasmaPy/pull/2699?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PlasmaPy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.25%. Comparing base [(`534b8e9`)](https://app.codecov.io/gh/PlasmaPy/PlasmaPy/commit/534b8e960050488db4c9e2977bc4142388afc153?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PlasmaPy) to head [(`e80a00b`)](https://app.codecov.io/gh/PlasmaPy/PlasmaPy/commit/e80a00b89679b65fc02cb5fc39aa292668177d83?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PlasmaPy).


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #2699   +/-   ##
=======================================
  Coverage   95.25%   95.25%           
=======================================
  Files         104      104           
  Lines        9435     9435           
  Branches     2159     2159           
=======================================
  Hits         8987     8987           
  Misses        272      272           
  Partials      176      176           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PlasmaPy/PlasmaPy/pull/2699?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PlasmaPy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PlasmaPy).

pheuer: @namurphy Thanks for the suggestions! 

One last thing before we merge - is the person who pointed out the error already listed in the citation file? If not, would they like to be? That certainly counts as a contribution in my book! 

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c7ab73ab..64cdd595 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -78,6 +78,12 @@ repos:
     additional_dependencies:
     - tomli
 
+- repo: https://github.com/crate-ci/typos
+  rev: v1.21.0
+  hooks:
+  - id: typos
+    name: typos (add false positives to _typos.toml)
+
 - repo: https://github.com/astral-sh/ruff-pre-commit
   rev: v0.4.3
   hooks:
@@ -85,7 +91,7 @@ repos:
     name: ruff (see https://docs.astral.sh/ruff/rules)
     args: [--fix]
   - id: ruff-format
-    name: autoformat source code with ruff formatter
+    name: autoformat source code with ruff-format
 
 - repo: https://github.com/asottile/blacken-docs
   rev: 1.16.0
@@ -110,6 +116,3 @@ repos:
   - id: nbqa-ruff
     name: ruff for notebooks (see https://docs.astral.sh/ruff/rules)
     args: [--fix, '--select=A,ARG,B,BLE,C,C4,E,F,FLY,I,INT,ISC,PERF,PIE,PLC,PLE,PYI,Q003,RET,RSE,SIM,TID,TRY,UP,W', '--ignore=B007,B008,B015,B018,E402,E501,E741,SIM114']
-    additional_dependencies:
-    # remove ruff dependency after 0.3.5 is released
-    - ruff !=0.3.3, !=0.3.4
diff --git a/CODEOWNERS b/CODEOWNERS
index 457a2a29..f94e8e53 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -4,42 +4,45 @@
 # Order is important; the last matching pattern takes the most
 # precedence.
 
+# Tip: IDEs like PyCharm may have features or extensions that validate
+# CODEOWNERS files and check that paths that are listed match any files.
+
 * @PlasmaPy/plasmapy-reviewers
 
 # subpackages
 
-plasmapy/analysis/ @rocco8773
-plasmapy/diagnostics/ @rocco8773
-plasmapy/diagnostics/charged_particle_radiography/ @pheuer
-plasmapy/dispersion/ @ejohnson-96
-plasmapy/particles/ @namurphy
-plasmapy/simulation/ @namurphy
-plasmapy/tests/ @namurphy
-plasmapy/utils/decorators/ @rocco8773
-plasmapy/utils/pytest_helpers/ @namurphy
+*pytest_helpers/ @namurphy
+analysis/ @rocco8773
+diagnostics/ @rocco8773
+**/diagnostics/charged_particle_radiography/ @pheuer
+dispersion/ @ejohnson-96
+**/utils/decorators/ @rocco8773
 
 # subpackage files (with wildcards to include tests)
 
-plasmapy/analysis/**/*nullpoint.py @namurphy
-plasmapy/diagnostics/**/*thomson.py @pheuer
-plasmapy/formulary/**/*relativity.py @namurphy
+**/analysis/*nullpoint.py @namurphy
+**/diagnostics/*thomson.py @pheuer
+**/formulary/*relativity.py @namurphy
 
 # documentation
 
 .readthedocs.yml @namurphy
+docs/_cff_to_rst.py @namurphy
+docs/_global_substitutions.py @namurphy
 docs/conf.py @namurphy
 docs/contributing/ @namurphy
 docs/plasmapy_sphinx/ @rocco8773
 
 # project files
 
-plasmapy/__init__.py @namurphy
+ci_requirements/ @namurphy
+mypy.ini @namurphy
 pyproject.toml @namurphy
-requirements.txt @namurphy
-setup.py @namurphy
+type_stubs/ @namurphy
 
 # tests, linters, CI
 
-.github @namurphy
+.github/ @namurphy
 .pre-commit-config.yaml @namurphy
-tox.ini @namurphy
+codecov.yml @namurphy
+noxfile.py @namurphy
diff --git a/_typos.toml b/_typos.toml
new file mode 100644
index 00000000..7e55ebc4
--- /dev/null
+++ b/_typos.toml
@@ -0,0 +1,11 @@
+[default]
+
+[default.extend-words]
+aas = "aas"
+arange = "arange"
+ba = "ba"
+dne = "dne"
+explin = "explin"
+hax = "hax"
+iy = "iy"
+nd = "nd"
diff --git a/changelog/2699.bugfix.rst b/changelog/2699.bugfix.rst
new file mode 100644
index 00000000..c5bf5c50
--- /dev/null
+++ b/changelog/2699.bugfix.rst
@@ -0,0 +1,5 @@
+Changed the :math:`z` dependence of the ion contribution to the optical Thomson scattering
+spectral density function in `~plasmapy.diagnostics.thomson.spectral_density_lite` from ``z``
+to ``z^2 / zbar`` to match Eq. 5.1.2 and following equations in :cite:t:`sheffield:2011`.
+The result is a small change in the ion acoustic wave spectrum for plasmas
+with multiple ion species.
diff --git a/changelog/2700.internal.rst b/changelog/2700.internal.rst
new file mode 100644
index 00000000..a94168c4
--- /dev/null
+++ b/changelog/2700.internal.rst
@@ -0,0 +1,1 @@
+Added ``typos`` as a |pre-commit| hook.
diff --git a/changelog/2702.breaking.rst b/changelog/2702.breaking.rst
new file mode 100644
index 00000000..ece9255c
--- /dev/null
+++ b/changelog/2702.breaking.rst
@@ -0,0 +1,3 @@
+Added a ``__str__`` method to the |CustomParticle|
+class that returns the symbol of the particle if provided, and
+otherwise falls back to using ``__repr__``.
diff --git a/ci_requirements/all-3.12.txt b/ci_requirements/all-3.12.txt
index 45d8465e..669627bb 100644
--- a/ci_requirements/all-3.12.txt
+++ b/ci_requirements/all-3.12.txt
@@ -2,7 +2,7 @@
 #    nox -s requirements
 alabaster==0.7.16
     # via sphinx
-anyio==4.3.0
+anyio==4.4.0
     # via jupyter-server
 argcomplete==3.3.0
     # via nox
@@ -12,11 +12,11 @@ argon2-cffi-bindings==21.2.0
     # via argon2-cffi
 arrow==1.3.0
     # via isoduration
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==6.1.0
     # via plasmapy (pyproject.toml)
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
     # via astropy
 asttokens==2.4.1
     # via stack-data
@@ -59,7 +59,7 @@ comm==0.2.2
     #   ipywidgets
 contourpy==1.2.1
     # via matplotlib
-coverage==7.5.1
+coverage==7.5.2
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
@@ -92,7 +92,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.1
     # via matplotlib
 fqdn==1.5.1
     # via jsonschema
@@ -100,7 +100,7 @@ future==1.0.0
     # via uncertainties
 h5py==3.11.0
     # via plasmapy (pyproject.toml)
-hypothesis==6.102.4
+hypothesis==6.102.6
     # via plasmapy (pyproject.toml)
 identify==2.5.36
     # via pre-commit
@@ -147,7 +147,7 @@ jsonschema==4.22.0
     #   nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==8.6.1
+jupyter-client==8.6.2
     # via
     #   ipykernel
     #   jupyter-server
@@ -172,7 +172,7 @@ jupyter-server-terminals==0.5.3
     # via jupyter-server
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
     # via voila
 jupyterlab-widgets==3.0.10
     # via ipywidgets
@@ -198,7 +198,7 @@ mistune==3.0.2
     # via nbconvert
 mpmath==1.3.0
     # via plasmapy (pyproject.toml)
-nbclient==0.7.4
+nbclient==0.10.0
     # via
     #   nbconvert
     #   voila
@@ -365,7 +365,7 @@ referencing==0.35.1
     #   jsonschema
     #   jsonschema-specifications
     #   jupyter-events
-requests==2.31.0
+requests==2.32.2
     # via
     #   plasmapy (pyproject.toml)
     #   jupyterlab-server
@@ -382,13 +382,13 @@ rpds-py==0.18.1
     # via
     #   jsonschema
     #   referencing
-scipy==1.13.0
+scipy==1.13.1
     # via
     #   plasmapy (pyproject.toml)
     #   lmfit
 send2trash==1.8.3
     # via jupyter-server
-setuptools==69.5.1
+setuptools==70.0.0
     # via
     #   plasmapy (pyproject.toml)
     #   nodeenv
@@ -440,7 +440,7 @@ sphinx-hoverxref==1.3.0
     # via plasmapy (pyproject.toml)
 sphinx-issues==4.1.0
     # via plasmapy (pyproject.toml)
-sphinx-notfound-page==1.0.0
+sphinx-notfound-page==1.0.2
     # via plasmapy (pyproject.toml)
 sphinx-reredirects==0.1.3
     # via plasmapy (pyproject.toml)
@@ -526,14 +526,14 @@ uri-template==1.3.0
     # via jsonschema
 urllib3==2.2.1
     # via requests
-uv==0.1.44
+uv==0.2.4
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.5.6
+voila==0.5.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
diff --git a/ci_requirements/docs-3.12.txt b/ci_requirements/docs-3.12.txt
index fd7ff1b9..2647e587 100644
--- a/ci_requirements/docs-3.12.txt
+++ b/ci_requirements/docs-3.12.txt
@@ -2,7 +2,7 @@
 #    nox -s requirements
 alabaster==0.7.16
     # via sphinx
-anyio==4.3.0
+anyio==4.4.0
     # via jupyter-server
 argcomplete==3.3.0
     # via nox
@@ -12,11 +12,11 @@ argon2-cffi-bindings==21.2.0
     # via argon2-cffi
 arrow==1.3.0
     # via isoduration
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==6.1.0
     # via plasmapy (pyproject.toml)
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
     # via astropy
 asttokens==2.4.1
     # via stack-data
@@ -85,7 +85,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.1
     # via matplotlib
 fqdn==1.5.1
     # via jsonschema
@@ -134,7 +134,7 @@ jsonschema==4.22.0
     #   nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==8.6.1
+jupyter-client==8.6.2
     # via
     #   ipykernel
     #   jupyter-server
@@ -159,7 +159,7 @@ jupyter-server-terminals==0.5.3
     # via jupyter-server
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
     # via voila
 jupyterlab-widgets==3.0.10
     # via ipywidgets
@@ -185,7 +185,7 @@ mistune==3.0.2
     # via nbconvert
 mpmath==1.3.0
     # via plasmapy (pyproject.toml)
-nbclient==0.7.4
+nbclient==0.10.0
     # via
     #   nbconvert
     #   voila
@@ -320,7 +320,7 @@ referencing==0.35.1
     #   jsonschema
     #   jsonschema-specifications
     #   jupyter-events
-requests==2.31.0
+requests==2.32.2
     # via
     #   plasmapy (pyproject.toml)
     #   jupyterlab-server
@@ -337,13 +337,13 @@ rpds-py==0.18.1
     # via
     #   jsonschema
     #   referencing
-scipy==1.13.0
+scipy==1.13.1
     # via
     #   plasmapy (pyproject.toml)
     #   lmfit
 send2trash==1.8.3
     # via jupyter-server
-setuptools==69.5.1
+setuptools==70.0.0
     # via plasmapy (pyproject.toml)
 six==1.16.0
     # via
@@ -391,7 +391,7 @@ sphinx-hoverxref==1.3.0
     # via plasmapy (pyproject.toml)
 sphinx-issues==4.1.0
     # via plasmapy (pyproject.toml)
-sphinx-notfound-page==1.0.0
+sphinx-notfound-page==1.0.2
     # via plasmapy (pyproject.toml)
 sphinx-reredirects==0.1.3
     # via plasmapy (pyproject.toml)
@@ -475,13 +475,13 @@ uri-template==1.3.0
     # via jsonschema
 urllib3==2.2.1
     # via requests
-uv==0.1.44
+uv==0.2.4
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   tox
-voila==0.5.6
+voila==0.5.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
diff --git a/ci_requirements/requirements_docs_py312.txt b/ci_requirements/requirements_docs_py312.txt
index 64374baa..710dedb4 100644
--- a/ci_requirements/requirements_docs_py312.txt
+++ b/ci_requirements/requirements_docs_py312.txt
@@ -1,12 +1,12 @@
 alabaster==0.7.16
-anyio==4.3.0
+anyio==4.4.0
 argcomplete==3.3.0
 argon2-cffi==23.1.0
 argon2-cffi-bindings==21.2.0
 arrow==1.3.0
-asteval==0.9.32
+asteval==0.9.33
 astropy==6.1.0
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
 asttokens==2.4.1
 attrs==23.2.0
 babel==2.15.0
@@ -32,7 +32,7 @@ docutils==0.20.1
 executing==2.0.1
 fastjsonschema==2.19.1
 filelock==3.14.0
-fonttools==4.51.0
+fonttools==4.52.1
 fqdn==1.5.1
 future==1.0.0
 h5py==3.11.0
@@ -49,13 +49,13 @@ json5==0.9.25
 jsonpointer==2.4
 jsonschema==4.22.0
 jsonschema-specifications==2023.12.1
-jupyter-client==8.6.1
+jupyter-client==8.6.2
 jupyter-core==5.7.2
 jupyter-events==0.10.0
 jupyter-server==2.14.0
 jupyter-server-terminals==0.5.3
 jupyterlab-pygments==0.3.0
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
 jupyterlab-widgets==3.0.10
 kiwisolver==1.4.5
 latexcodec==3.0.0
@@ -66,7 +66,7 @@ matplotlib==3.9.0
 matplotlib-inline==0.1.7
 mistune==3.0.2
 mpmath==1.3.0
-nbclient==0.7.4
+nbclient==0.10.0
 nbconvert==7.16.4
 nbformat==5.10.4
 nbsphinx==0.9.4
@@ -102,13 +102,13 @@ pytz==2024.1
 pyyaml==6.0.1
 pyzmq==26.0.3
 referencing==0.35.1
-requests==2.31.0
+requests==2.32.2
 rfc3339-validator==0.1.4
 rfc3986-validator==0.1.1
 rpds-py==0.18.1
-scipy==1.13.0
+scipy==1.13.1
 send2trash==1.8.3
-setuptools==69.5.1
+setuptools==70.0.0
 six==1.16.0
 sniffio==1.3.1
 snowballstemmer==2.2.0
@@ -121,7 +121,7 @@ sphinx-copybutton==0.5.2
 sphinx-gallery==0.16.0
 sphinx-hoverxref==1.3.0
 sphinx-issues==4.1.0
-sphinx-notfound-page==1.0.0
+sphinx-notfound-page==1.0.2
 sphinx-reredirects==0.1.3
 sphinx-rtd-theme==2.0.0
 sphinx-tabs==3.4.5
@@ -150,9 +150,9 @@ uncertainties==3.1.7
 unidecode==1.3.8
 uri-template==1.3.0
 urllib3==2.2.1
-uv==0.1.44
+uv==0.2.4
 virtualenv==20.26.2
-voila==0.5.6
+voila==0.5.7
 wcwidth==0.2.13
 webcolors==1.13
 webencodings==0.5.1
diff --git a/docs/contributing/coding_guide.rst b/docs/contributing/coding_guide.rst
index fdcd3495..a99ddf65 100644
--- a/docs/contributing/coding_guide.rst
+++ b/docs/contributing/coding_guide.rst
@@ -890,7 +890,7 @@ of |Python|, |NumPy|, |Astropy|, and other dependencies.
 * PlasmaPy should support the minor versions of Python initially
   released 42 months prior to a release. Because :pep:`602` establishes
   that Python releases will generally occur in October of each year,
-  the minimum required version fo Python should be increased for the
+  the minimum required version of Python should be increased for the
   first release of PlasmaPy after April each year.
 
 * PlasmaPy should support minor versions of its dependencies that were
diff --git a/docs/notebooks/plasma/grids_cartesian.ipynb b/docs/notebooks/plasma/grids_cartesian.ipynb
index 4760fcf8..7f04f96f 100644
--- a/docs/notebooks/plasma/grids_cartesian.ipynb
+++ b/docs/notebooks/plasma/grids_cartesian.ipynb
@@ -292,7 +292,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Grid objects contain several interpolator methods to evaluate quantites at positions between the grid vertices. These interpolators use the fact that all of the quantities are defined on the same grid to perform faster interpolations using a nearest-neighbor scheme. When an interpolation at a position is requested, the grid indices closest to that position are calculated. Then, the quantity arrays are evaluated at the interpolated indices. Using this method, many quantities can be interpolated at the same positions in almost the same amount of time as is required to interpolate one quantity. \n",
+    "Grid objects contain several interpolator methods to evaluate quantities at positions between the grid vertices. These interpolators use the fact that all of the quantities are defined on the same grid to perform faster interpolations using a nearest-neighbor scheme. When an interpolation at a position is requested, the grid indices closest to that position are calculated. Then, the quantity arrays are evaluated at the interpolated indices. Using this method, many quantities can be interpolated at the same positions in almost the same amount of time as is required to interpolate one quantity. \n",
     "\n",
     "Positions are provided to the interpolator as a `u.Quantity` array of shape [N,3] where N is the number of positions and [i,:] represents the x,y,z coordinates of the ith position:"
    ]
diff --git a/pyproject.toml b/pyproject.toml
index 35677bd4..42e71218 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -55,8 +55,9 @@ dynamic = [
 dependencies = [
   "astropy >= 5.1",
   "h5py >= 3.7.0",
-  "ipykernel >= 6.8.0",
-  "ipywidgets >= 7.7.0",
+  # Minimum version of ipykernel should not exceed what's used by Google Colab
+  "ipykernel >= 5.5.6",
+  "ipywidgets >= 6.0.0",
   "lmfit >= 1.1.0",
   "matplotlib >= 3.5.1",
   "mpmath >= 1.2.1",
@@ -68,7 +69,8 @@ dependencies = [
   "scipy >= 1.8.0",
   "setuptools >= 61.2.0",
   "tqdm >= 4.60.0",
-  "voila >= 0.4.0",
+  # Use loose requirements for voila because it impacts ipykernel version
+  "voila >= 0.2.7",
   "wrapt >= 1.14.0",
   "xarray >= 2022.3.0",
 ]
diff --git a/src/plasmapy/diagnostics/thomson.py b/src/plasmapy/diagnostics/thomson.py
index 38e4eba4..7aff4afe 100644
--- a/src/plasmapy/diagnostics/thomson.py
+++ b/src/plasmapy/diagnostics/thomson.py
@@ -244,7 +244,8 @@ def spectral_density_lite(
         icontr[m, :] = ifract[m] * (
             2
             * np.sqrt(np.pi)
-            * ion_z[m]
+            * ion_z[m] ** 2
+            / zbar
             / k
             / vT_i[m]
             * np.power(np.abs(np.sum(chiE, axis=0) / epsilon), 2)
@@ -403,14 +404,14 @@ def spectral_density(  # noqa: C901, PLR0912, PLR0915
         S(k,ω) = \sum_e \frac{2π}{k}
         \bigg |1 - \frac{χ_e}{ε} \bigg |^2
         f_{e0,e} \bigg (\frac{ω}{k} \bigg ) +
-        \sum_i \frac{2π Z_i}{k}
+        \sum_i \frac{2π}{k} \frac{Z_i^2}{\bar Z}
         \bigg |\frac{χ_e}{ε} \bigg |^2 f_{i0,i}
         \bigg ( \frac{ω}{k} \bigg )
 
     where :math:`χ_e` is the electron population susceptibility of the
     plasma and :math:`ε = 1 + ∑_e χ_e + ∑_i χ_i` is the total plasma
     dielectric function (with :math:`χ_i` being the ion population of
-    the susceptibility), :math:`Z_i` is the charge of each ion,
+    the susceptibility),
     :math:`k` is the scattering wavenumber, :math:`ω` is the scattering
     frequency, and :math:`f_{e0,e}` and :math:`f_{i0,i}` are the
     electron and ion velocity distribution functions, respectively. In
@@ -434,6 +435,11 @@ def spectral_density(  # noqa: C901, PLR0912, PLR0915
     .. math::
         \sum_e F_e = 1
 
+    and
+
+    .. math::
+        \bar Z = \sum_k F_k Z_k
+
     The plasma is assumed to be quasineutral, and therefore the number
     density of the i\ :sup:`th` ion population is
 
diff --git a/src/plasmapy/particles/particle_class.py b/src/plasmapy/particles/particle_class.py
index bf30dc76..a8919555 100644
--- a/src/plasmapy/particles/particle_class.py
+++ b/src/plasmapy/particles/particle_class.py
@@ -2283,6 +2283,30 @@ def __repr__(self) -> str:
             else f"CustomParticle(mass={self.mass}, charge={self.charge}, symbol={self.symbol})"
         )
 
+    def __str__(self) -> str:
+        """
+        Return the particle's symbol if provided, otherwise the `repr`.
+
+        Examples
+        --------
+        >>> custom_particle = CustomParticle(
+        ...     mass=4.21e-25 * u.kg,
+        ...     charge=1.6e-19 * u.C,
+        ...     symbol="I2+",
+        ... )
+        >>> str(custom_particle)
+        'I2+'
+
+        Without a symbol, the |repr| is returned.
+
+        >>> mass = 1.2e-26 * u.kg
+        >>> charge = 9.2e-19 * u.C
+        >>> custom_particle = CustomParticle(mass=mass, charge=charge)
+        >>> repr(custom_particle)
+        'CustomParticle(mass=1.2e-26 kg, charge=9.2e-19 C)'
+        """
+        return self.symbol
+
     @property
     def json_dict(self) -> dict[str, Any]:
         """


Test patch:
diff --git a/ci_requirements/requirements_tests_lowest_direct_py310.txt b/ci_requirements/requirements_tests_lowest_direct_py310.txt
index a3ebd9a8..0ab89362 100644
--- a/ci_requirements/requirements_tests_lowest_direct_py310.txt
+++ b/ci_requirements/requirements_tests_lowest_direct_py310.txt
@@ -7,11 +7,10 @@ argcomplete==3.3.0
 argon2-cffi==23.1.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 argon2-cffi-bindings==21.2.0
     # via argon2-cffi
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==5.1
     # via plasmapy (pyproject.toml)
@@ -44,7 +43,7 @@ colorama==0.4.6
     # via tox
 colorlog==6.8.2
     # via nox
-coverage==7.5.1
+coverage==7.5.2
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
@@ -74,7 +73,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.1
     # via matplotlib
 future==1.0.0
     # via uncertainties
@@ -113,7 +112,6 @@ jinja2==3.1.4
     # via
     #   jupyter-server
     #   jupyterlab-server
-    #   nbclassic
     #   nbconvert
     #   notebook
 json5==0.9.25
@@ -128,7 +126,6 @@ jupyter-client==7.4.1
     # via
     #   ipykernel
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   notebook
     #   voila
@@ -136,7 +133,6 @@ jupyter-core==5.7.2
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   nbformat
@@ -145,7 +141,6 @@ jupyter-core==5.7.2
 jupyter-server==1.24.0
     # via
     #   jupyterlab-server
-    #   nbclassic
     #   notebook-shim
     #   voila
 jupyterlab-pygments==0.3.0
@@ -174,7 +169,7 @@ mistune==3.0.2
     # via nbconvert
 mpmath==1.2.1
     # via plasmapy (pyproject.toml)
-nbclassic==1.0.0
+nbclassic==1.1.0
     # via notebook
 nbclient==0.7.4
     # via
@@ -183,14 +178,12 @@ nbclient==0.7.4
 nbconvert==7.16.4
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   voila
 nbformat==5.10.4
     # via
     #   ipywidgets
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   notebook
@@ -264,7 +257,6 @@ pre-commit==3.6.0
 prometheus-client==0.20.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 prompt-toolkit==3.0.43
     # via ipython
@@ -323,7 +315,6 @@ pyzmq==26.0.3
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
 referencing==0.35.1
     # via
@@ -344,7 +335,6 @@ scipy==1.8.0
 send2trash==1.8.3
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 setuptools==61.2.0
     # via
@@ -366,7 +356,6 @@ stack-data==0.6.3
 terminado==0.18.1
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 tinycss2==1.3.0
     # via nbconvert
@@ -383,7 +372,6 @@ tornado==6.4
     #   ipykernel
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   terminado
 tox==4.14.0
@@ -403,19 +391,18 @@ traitlets==5.14.3
     #   jupyter-core
     #   jupyter-server
     #   matplotlib-inline
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
     #   voila
-typing-extensions==4.11.0
+typing-extensions==4.12.0
     # via ipython
 uncertainties==3.1.7
     # via lmfit
 urllib3==1.26.18
     # via requests
-uv==0.1.44
+uv==0.2.4
     # via tox-uv
 virtualenv==20.26.2
     # via
diff --git a/ci_requirements/requirements_tests_py310.txt b/ci_requirements/requirements_tests_py310.txt
index 62ee8666..546a6085 100644
--- a/ci_requirements/requirements_tests_py310.txt
+++ b/ci_requirements/requirements_tests_py310.txt
@@ -1,11 +1,11 @@
-anyio==4.3.0
+anyio==4.4.0
 argcomplete==3.3.0
 argon2-cffi==23.1.0
 argon2-cffi-bindings==21.2.0
 arrow==1.3.0
-asteval==0.9.32
+asteval==0.9.33
 astropy==6.1.0
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
 asttokens==2.4.1
 attrs==23.2.0
 babel==2.15.0
@@ -21,7 +21,7 @@ colorama==0.4.6
 colorlog==6.8.2
 comm==0.2.2
 contourpy==1.2.1
-coverage==7.5.1
+coverage==7.5.2
 cycler==0.12.1
 debugpy==1.8.1
 decorator==5.1.1
@@ -33,11 +33,11 @@ execnet==2.1.1
 executing==2.0.1
 fastjsonschema==2.19.1
 filelock==3.14.0
-fonttools==4.51.0
+fonttools==4.52.1
 fqdn==1.5.1
 future==1.0.0
 h5py==3.11.0
-hypothesis==6.102.4
+hypothesis==6.102.6
 identify==2.5.36
 idna==3.7
 iniconfig==2.0.0
@@ -51,13 +51,13 @@ json5==0.9.25
 jsonpointer==2.4
 jsonschema==4.22.0
 jsonschema-specifications==2023.12.1
-jupyter-client==8.6.1
+jupyter-client==8.6.2
 jupyter-core==5.7.2
 jupyter-events==0.10.0
 jupyter-server==2.14.0
 jupyter-server-terminals==0.5.3
 jupyterlab-pygments==0.3.0
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
 jupyterlab-widgets==3.0.10
 kiwisolver==1.4.5
 llvmlite==0.42.0
@@ -67,7 +67,7 @@ matplotlib==3.9.0
 matplotlib-inline==0.1.7
 mistune==3.0.2
 mpmath==1.3.0
-nbclient==0.7.4
+nbclient==0.10.0
 nbconvert==7.16.4
 nbformat==5.10.4
 nest-asyncio==1.6.0
@@ -108,13 +108,13 @@ pytz==2024.1
 pyyaml==6.0.1
 pyzmq==26.0.3
 referencing==0.35.1
-requests==2.31.0
+requests==2.32.2
 rfc3339-validator==0.1.4
 rfc3986-validator==0.1.1
 rpds-py==0.18.1
-scipy==1.13.0
+scipy==1.13.1
 send2trash==1.8.3
-setuptools==69.5.1
+setuptools==70.0.0
 six==1.16.0
 sniffio==1.3.1
 sortedcontainers==2.4.0
@@ -129,14 +129,14 @@ tox-uv==1.8.2
 tqdm==4.66.4
 traitlets==5.14.3
 types-python-dateutil==2.9.0.20240316
-typing-extensions==4.11.0
+typing-extensions==4.12.0
 tzdata==2024.1
 uncertainties==3.1.7
 uri-template==1.3.0
 urllib3==2.2.1
-uv==0.1.44
+uv==0.2.4
 virtualenv==20.26.2
-voila==0.5.6
+voila==0.5.7
 wcwidth==0.2.13
 webcolors==1.13
 webencodings==0.5.1
diff --git a/ci_requirements/requirements_tests_py311.txt b/ci_requirements/requirements_tests_py311.txt
index 9aae35d3..6de681f2 100644
--- a/ci_requirements/requirements_tests_py311.txt
+++ b/ci_requirements/requirements_tests_py311.txt
@@ -1,11 +1,11 @@
-anyio==4.3.0
+anyio==4.4.0
 argcomplete==3.3.0
 argon2-cffi==23.1.0
 argon2-cffi-bindings==21.2.0
 arrow==1.3.0
-asteval==0.9.32
+asteval==0.9.33
 astropy==6.1.0
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
 asttokens==2.4.1
 attrs==23.2.0
 babel==2.15.0
@@ -21,7 +21,7 @@ colorama==0.4.6
 colorlog==6.8.2
 comm==0.2.2
 contourpy==1.2.1
-coverage==7.5.1
+coverage==7.5.2
 cycler==0.12.1
 debugpy==1.8.1
 decorator==5.1.1
@@ -32,11 +32,11 @@ execnet==2.1.1
 executing==2.0.1
 fastjsonschema==2.19.1
 filelock==3.14.0
-fonttools==4.51.0
+fonttools==4.52.1
 fqdn==1.5.1
 future==1.0.0
 h5py==3.11.0
-hypothesis==6.102.4
+hypothesis==6.102.6
 identify==2.5.36
 idna==3.7
 iniconfig==2.0.0
@@ -50,13 +50,13 @@ json5==0.9.25
 jsonpointer==2.4
 jsonschema==4.22.0
 jsonschema-specifications==2023.12.1
-jupyter-client==8.6.1
+jupyter-client==8.6.2
 jupyter-core==5.7.2
 jupyter-events==0.10.0
 jupyter-server==2.14.0
 jupyter-server-terminals==0.5.3
 jupyterlab-pygments==0.3.0
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
 jupyterlab-widgets==3.0.10
 kiwisolver==1.4.5
 llvmlite==0.42.0
@@ -66,7 +66,7 @@ matplotlib==3.9.0
 matplotlib-inline==0.1.7
 mistune==3.0.2
 mpmath==1.3.0
-nbclient==0.7.4
+nbclient==0.10.0
 nbconvert==7.16.4
 nbformat==5.10.4
 nest-asyncio==1.6.0
@@ -107,13 +107,13 @@ pytz==2024.1
 pyyaml==6.0.1
 pyzmq==26.0.3
 referencing==0.35.1
-requests==2.31.0
+requests==2.32.2
 rfc3339-validator==0.1.4
 rfc3986-validator==0.1.1
 rpds-py==0.18.1
-scipy==1.13.0
+scipy==1.13.1
 send2trash==1.8.3
-setuptools==69.5.1
+setuptools==70.0.0
 six==1.16.0
 sniffio==1.3.1
 sortedcontainers==2.4.0
@@ -128,14 +128,14 @@ tox-uv==1.8.2
 tqdm==4.66.4
 traitlets==5.14.3
 types-python-dateutil==2.9.0.20240316
-typing-extensions==4.11.0
+typing-extensions==4.12.0
 tzdata==2024.1
 uncertainties==3.1.7
 uri-template==1.3.0
 urllib3==2.2.1
-uv==0.1.44
+uv==0.2.4
 virtualenv==20.26.2
-voila==0.5.6
+voila==0.5.7
 wcwidth==0.2.13
 webcolors==1.13
 webencodings==0.5.1
diff --git a/ci_requirements/requirements_tests_py312.txt b/ci_requirements/requirements_tests_py312.txt
index f1f25558..188364a5 100644
--- a/ci_requirements/requirements_tests_py312.txt
+++ b/ci_requirements/requirements_tests_py312.txt
@@ -1,11 +1,11 @@
-anyio==4.3.0
+anyio==4.4.0
 argcomplete==3.3.0
 argon2-cffi==23.1.0
 argon2-cffi-bindings==21.2.0
 arrow==1.3.0
-asteval==0.9.32
+asteval==0.9.33
 astropy==6.1.0
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
 asttokens==2.4.1
 attrs==23.2.0
 babel==2.15.0
@@ -21,7 +21,7 @@ colorama==0.4.6
 colorlog==6.8.2
 comm==0.2.2
 contourpy==1.2.1
-coverage==7.5.1
+coverage==7.5.2
 cycler==0.12.1
 debugpy==1.8.1
 decorator==5.1.1
@@ -32,11 +32,11 @@ execnet==2.1.1
 executing==2.0.1
 fastjsonschema==2.19.1
 filelock==3.14.0
-fonttools==4.51.0
+fonttools==4.52.1
 fqdn==1.5.1
 future==1.0.0
 h5py==3.11.0
-hypothesis==6.102.4
+hypothesis==6.102.6
 identify==2.5.36
 idna==3.7
 iniconfig==2.0.0
@@ -50,13 +50,13 @@ json5==0.9.25
 jsonpointer==2.4
 jsonschema==4.22.0
 jsonschema-specifications==2023.12.1
-jupyter-client==8.6.1
+jupyter-client==8.6.2
 jupyter-core==5.7.2
 jupyter-events==0.10.0
 jupyter-server==2.14.0
 jupyter-server-terminals==0.5.3
 jupyterlab-pygments==0.3.0
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
 jupyterlab-widgets==3.0.10
 kiwisolver==1.4.5
 llvmlite==0.42.0
@@ -66,7 +66,7 @@ matplotlib==3.9.0
 matplotlib-inline==0.1.7
 mistune==3.0.2
 mpmath==1.3.0
-nbclient==0.7.4
+nbclient==0.10.0
 nbconvert==7.16.4
 nbformat==5.10.4
 nest-asyncio==1.6.0
@@ -107,13 +107,13 @@ pytz==2024.1
 pyyaml==6.0.1
 pyzmq==26.0.3
 referencing==0.35.1
-requests==2.31.0
+requests==2.32.2
 rfc3339-validator==0.1.4
 rfc3986-validator==0.1.1
 rpds-py==0.18.1
-scipy==1.13.0
+scipy==1.13.1
 send2trash==1.8.3
-setuptools==69.5.1
+setuptools==70.0.0
 six==1.16.0
 sniffio==1.3.1
 sortedcontainers==2.4.0
@@ -132,9 +132,9 @@ tzdata==2024.1
 uncertainties==3.1.7
 uri-template==1.3.0
 urllib3==2.2.1
-uv==0.1.44
+uv==0.2.4
 virtualenv==20.26.2
-voila==0.5.6
+voila==0.5.7
 wcwidth==0.2.13
 webcolors==1.13
 webencodings==0.5.1
diff --git a/ci_requirements/tests-3.10-lowest-direct.txt b/ci_requirements/tests-3.10-lowest-direct.txt
index 4fe7f38b..3652647b 100644
--- a/ci_requirements/tests-3.10-lowest-direct.txt
+++ b/ci_requirements/tests-3.10-lowest-direct.txt
@@ -7,11 +7,10 @@ argcomplete==3.3.0
 argon2-cffi==23.1.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 argon2-cffi-bindings==21.2.0
     # via argon2-cffi
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==5.1
     # via plasmapy (pyproject.toml)
@@ -22,8 +21,6 @@ attrs==23.2.0
     #   hypothesis
     #   jsonschema
     #   referencing
-babel==2.15.0
-    # via jupyterlab-server
 beautifulsoup4==4.12.3
     # via nbconvert
 bleach==6.1.0
@@ -44,12 +41,10 @@ colorama==0.4.6
     # via tox
 colorlog==6.8.2
     # via nox
-coverage==7.5.1
+coverage==7.5.3
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
-debugpy==1.8.1
-    # via ipykernel
 decorator==5.1.1
     # via ipython
 defusedxml==0.7.1
@@ -57,7 +52,7 @@ defusedxml==0.7.1
 distlib==0.3.8
     # via virtualenv
 entrypoints==0.4
-    # via jupyter-client
+    # via nbconvert
 exceptiongroup==1.2.1
     # via
     #   anyio
@@ -74,7 +69,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.4
     # via matplotlib
 future==1.0.0
     # via uncertainties
@@ -90,7 +85,7 @@ idna==3.7
     #   requests
 iniconfig==2.0.0
     # via pytest
-ipykernel==6.8.0
+ipykernel==5.5.6
     # via
     #   plasmapy (pyproject.toml)
     #   ipywidgets
@@ -102,33 +97,26 @@ ipython==8.24.0
     #   ipywidgets
 ipython-genutils==0.2.0
     # via
-    #   ipywidgets
+    #   ipykernel
     #   nbclassic
     #   notebook
-ipywidgets==7.7.0
+ipywidgets==6.0.0
     # via plasmapy (pyproject.toml)
 jedi==0.19.1
     # via ipython
 jinja2==3.1.4
     # via
     #   jupyter-server
-    #   jupyterlab-server
-    #   nbclassic
     #   nbconvert
     #   notebook
-json5==0.9.25
-    # via jupyterlab-server
 jsonschema==4.22.0
-    # via
-    #   jupyterlab-server
-    #   nbformat
+    # via nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==7.4.1
+jupyter-client==6.1.12
     # via
     #   ipykernel
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   notebook
     #   voila
@@ -136,30 +124,23 @@ jupyter-core==5.7.2
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
-    #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
-    #   voila
 jupyter-server==1.24.0
     # via
-    #   jupyterlab-server
-    #   nbclassic
     #   notebook-shim
     #   voila
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.24.0
-    # via voila
-jupyterlab-widgets==3.0.10
-    # via ipywidgets
 kiwisolver==1.4.5
     # via matplotlib
 llvmlite==0.40.1
     # via numba
 lmfit==1.1.0
     # via plasmapy (pyproject.toml)
+lxml==5.2.2
+    # via nbconvert
 markupsafe==2.1.5
     # via
     #   jinja2
@@ -167,40 +148,35 @@ markupsafe==2.1.5
 matplotlib==3.5.1
     # via plasmapy (pyproject.toml)
 matplotlib-inline==0.1.7
-    # via
-    #   ipykernel
-    #   ipython
-mistune==3.0.2
+    # via ipython
+mistune==0.8.4
     # via nbconvert
 mpmath==1.2.1
     # via plasmapy (pyproject.toml)
-nbclassic==1.0.0
+nbclassic==1.1.0
     # via notebook
-nbclient==0.7.4
+nbclient==0.5.13
     # via
     #   nbconvert
     #   voila
-nbconvert==7.16.4
+nbconvert==6.5.4
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   voila
 nbformat==5.10.4
     # via
     #   ipywidgets
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   notebook
 nest-asyncio==1.6.0
     # via
-    #   ipykernel
-    #   jupyter-client
     #   nbclassic
+    #   nbclient
     #   notebook
-nodeenv==1.8.0
+nodeenv==1.9.0
     # via pre-commit
 notebook==6.5.7
     # via widgetsnbextension
@@ -227,7 +203,6 @@ packaging==23.2
     #   plasmapy (pyproject.toml)
     #   astropy
     #   jupyter-server
-    #   jupyterlab-server
     #   matplotlib
     #   nbconvert
     #   nox
@@ -264,9 +239,8 @@ pre-commit==3.6.0
 prometheus-client==0.20.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
-prompt-toolkit==3.0.43
+prompt-toolkit==3.0.45
     # via ipython
 ptyprocess==0.7.0
     # via
@@ -323,16 +297,13 @@ pyzmq==26.0.3
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
 referencing==0.35.1
     # via
     #   jsonschema
     #   jsonschema-specifications
 requests==2.28.0
-    # via
-    #   plasmapy (pyproject.toml)
-    #   jupyterlab-server
+    # via plasmapy (pyproject.toml)
 rpds-py==0.18.1
     # via
     #   jsonschema
@@ -344,12 +315,9 @@ scipy==1.8.0
 send2trash==1.8.3
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 setuptools==61.2.0
-    # via
-    #   plasmapy (pyproject.toml)
-    #   nodeenv
+    # via plasmapy (pyproject.toml)
 six==1.16.0
     # via
     #   asttokens
@@ -366,7 +334,6 @@ stack-data==0.6.3
 terminado==0.18.1
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 tinycss2==1.3.0
     # via nbconvert
@@ -383,7 +350,6 @@ tornado==6.4
     #   ipykernel
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   terminado
 tox==4.14.0
@@ -403,26 +369,24 @@ traitlets==5.14.3
     #   jupyter-core
     #   jupyter-server
     #   matplotlib-inline
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
-    #   voila
-typing-extensions==4.11.0
+typing-extensions==4.12.0
     # via ipython
 uncertainties==3.1.7
     # via lmfit
 urllib3==1.26.18
     # via requests
-uv==0.1.44
+uv==0.2.5
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.4.0
+voila==0.2.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
@@ -432,9 +396,7 @@ webencodings==0.5.1
     #   tinycss2
 websocket-client==1.8.0
     # via jupyter-server
-websockets==12.0
-    # via voila
-widgetsnbextension==3.6.6
+widgetsnbextension==2.0.1
     # via ipywidgets
 wrapt==1.14.0
     # via plasmapy (pyproject.toml)
diff --git a/ci_requirements/tests-3.10.txt b/ci_requirements/tests-3.10.txt
index 7051e628..e7801339 100644
--- a/ci_requirements/tests-3.10.txt
+++ b/ci_requirements/tests-3.10.txt
@@ -1,6 +1,6 @@
 # This file was autogenerated by uv via the following command:
 #    nox -s requirements
-anyio==4.3.0
+anyio==4.4.0
     # via jupyter-server
 argcomplete==3.3.0
     # via nox
@@ -10,11 +10,11 @@ argon2-cffi-bindings==21.2.0
     # via argon2-cffi
 arrow==1.3.0
     # via isoduration
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==6.1.0
     # via plasmapy (pyproject.toml)
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
     # via astropy
 asttokens==2.4.1
     # via stack-data
@@ -51,7 +51,7 @@ comm==0.2.2
     #   ipywidgets
 contourpy==1.2.1
     # via matplotlib
-coverage==7.5.1
+coverage==7.5.2
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
@@ -81,7 +81,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.1
     # via matplotlib
 fqdn==1.5.1
     # via jsonschema
@@ -89,7 +89,7 @@ future==1.0.0
     # via uncertainties
 h5py==3.11.0
     # via plasmapy (pyproject.toml)
-hypothesis==6.102.4
+hypothesis==6.102.6
     # via plasmapy (pyproject.toml)
 identify==2.5.36
     # via pre-commit
@@ -128,7 +128,7 @@ jsonschema==4.22.0
     #   nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==8.6.1
+jupyter-client==8.6.2
     # via
     #   ipykernel
     #   jupyter-server
@@ -153,7 +153,7 @@ jupyter-server-terminals==0.5.3
     # via jupyter-server
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
     # via voila
 jupyterlab-widgets==3.0.10
     # via ipywidgets
@@ -177,7 +177,7 @@ mistune==3.0.2
     # via nbconvert
 mpmath==1.3.0
     # via plasmapy (pyproject.toml)
-nbclient==0.7.4
+nbclient==0.10.0
     # via
     #   nbconvert
     #   voila
@@ -324,7 +324,7 @@ referencing==0.35.1
     #   jsonschema
     #   jsonschema-specifications
     #   jupyter-events
-requests==2.31.0
+requests==2.32.2
     # via
     #   plasmapy (pyproject.toml)
     #   jupyterlab-server
@@ -340,13 +340,13 @@ rpds-py==0.18.1
     # via
     #   jsonschema
     #   referencing
-scipy==1.13.0
+scipy==1.13.1
     # via
     #   plasmapy (pyproject.toml)
     #   lmfit
 send2trash==1.8.3
     # via jupyter-server
-setuptools==69.5.1
+setuptools==70.0.0
     # via
     #   plasmapy (pyproject.toml)
     #   nodeenv
@@ -409,7 +409,7 @@ traitlets==5.14.3
     #   voila
 types-python-dateutil==2.9.0.20240316
     # via arrow
-typing-extensions==4.11.0
+typing-extensions==4.12.0
     # via
     #   anyio
     #   ipython
@@ -421,14 +421,14 @@ uri-template==1.3.0
     # via jsonschema
 urllib3==2.2.1
     # via requests
-uv==0.1.44
+uv==0.2.4
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.5.6
+voila==0.5.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
diff --git a/ci_requirements/tests-3.11-lowest-direct.txt b/ci_requirements/tests-3.11-lowest-direct.txt
index 114eaf93..fc39876b 100644
--- a/ci_requirements/tests-3.11-lowest-direct.txt
+++ b/ci_requirements/tests-3.11-lowest-direct.txt
@@ -7,11 +7,10 @@ argcomplete==3.3.0
 argon2-cffi==23.1.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 argon2-cffi-bindings==21.2.0
     # via argon2-cffi
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==5.1
     # via plasmapy (pyproject.toml)
@@ -22,8 +21,6 @@ attrs==23.2.0
     #   hypothesis
     #   jsonschema
     #   referencing
-babel==2.15.0
-    # via jupyterlab-server
 beautifulsoup4==4.12.3
     # via nbconvert
 bleach==6.1.0
@@ -44,12 +41,10 @@ colorama==0.4.6
     # via tox
 colorlog==6.8.2
     # via nox
-coverage==7.5.1
+coverage==7.5.3
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
-debugpy==1.8.1
-    # via ipykernel
 decorator==5.1.1
     # via ipython
 defusedxml==0.7.1
@@ -57,7 +52,7 @@ defusedxml==0.7.1
 distlib==0.3.8
     # via virtualenv
 entrypoints==0.4
-    # via jupyter-client
+    # via nbconvert
 execnet==2.1.1
     # via pytest-xdist
 executing==2.0.1
@@ -68,7 +63,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.4
     # via matplotlib
 future==1.0.0
     # via uncertainties
@@ -84,7 +79,7 @@ idna==3.7
     #   requests
 iniconfig==2.0.0
     # via pytest
-ipykernel==6.8.0
+ipykernel==5.5.6
     # via
     #   plasmapy (pyproject.toml)
     #   ipywidgets
@@ -96,33 +91,26 @@ ipython==8.24.0
     #   ipywidgets
 ipython-genutils==0.2.0
     # via
-    #   ipywidgets
+    #   ipykernel
     #   nbclassic
     #   notebook
-ipywidgets==7.7.0
+ipywidgets==6.0.0
     # via plasmapy (pyproject.toml)
 jedi==0.19.1
     # via ipython
 jinja2==3.1.4
     # via
     #   jupyter-server
-    #   jupyterlab-server
-    #   nbclassic
     #   nbconvert
     #   notebook
-json5==0.9.25
-    # via jupyterlab-server
 jsonschema==4.22.0
-    # via
-    #   jupyterlab-server
-    #   nbformat
+    # via nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==7.4.1
+jupyter-client==6.1.12
     # via
     #   ipykernel
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   notebook
     #   voila
@@ -130,30 +118,23 @@ jupyter-core==5.7.2
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
-    #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
-    #   voila
 jupyter-server==1.24.0
     # via
-    #   jupyterlab-server
-    #   nbclassic
     #   notebook-shim
     #   voila
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.24.0
-    # via voila
-jupyterlab-widgets==3.0.10
-    # via ipywidgets
 kiwisolver==1.4.5
     # via matplotlib
 llvmlite==0.40.1
     # via numba
 lmfit==1.1.0
     # via plasmapy (pyproject.toml)
+lxml==5.2.2
+    # via nbconvert
 markupsafe==2.1.5
     # via
     #   jinja2
@@ -161,40 +142,35 @@ markupsafe==2.1.5
 matplotlib==3.5.1
     # via plasmapy (pyproject.toml)
 matplotlib-inline==0.1.7
-    # via
-    #   ipykernel
-    #   ipython
-mistune==3.0.2
+    # via ipython
+mistune==0.8.4
     # via nbconvert
 mpmath==1.2.1
     # via plasmapy (pyproject.toml)
-nbclassic==1.0.0
+nbclassic==1.1.0
     # via notebook
-nbclient==0.7.4
+nbclient==0.5.13
     # via
     #   nbconvert
     #   voila
-nbconvert==7.16.4
+nbconvert==6.5.4
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   voila
 nbformat==5.10.4
     # via
     #   ipywidgets
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   notebook
 nest-asyncio==1.6.0
     # via
-    #   ipykernel
-    #   jupyter-client
     #   nbclassic
+    #   nbclient
     #   notebook
-nodeenv==1.8.0
+nodeenv==1.9.0
     # via pre-commit
 notebook==6.5.7
     # via widgetsnbextension
@@ -221,7 +197,6 @@ packaging==23.2
     #   plasmapy (pyproject.toml)
     #   astropy
     #   jupyter-server
-    #   jupyterlab-server
     #   matplotlib
     #   nbconvert
     #   nox
@@ -258,9 +233,8 @@ pre-commit==3.6.0
 prometheus-client==0.20.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
-prompt-toolkit==3.0.43
+prompt-toolkit==3.0.45
     # via ipython
 ptyprocess==0.7.0
     # via
@@ -317,33 +291,27 @@ pyzmq==26.0.3
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
 referencing==0.35.1
     # via
     #   jsonschema
     #   jsonschema-specifications
 requests==2.28.0
-    # via
-    #   plasmapy (pyproject.toml)
-    #   jupyterlab-server
+    # via plasmapy (pyproject.toml)
 rpds-py==0.18.1
     # via
     #   jsonschema
     #   referencing
-scipy==1.9.2
+scipy==1.9.0
     # via
     #   plasmapy (pyproject.toml)
     #   lmfit
 send2trash==1.8.3
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 setuptools==61.2.0
-    # via
-    #   plasmapy (pyproject.toml)
-    #   nodeenv
+    # via plasmapy (pyproject.toml)
 six==1.16.0
     # via
     #   asttokens
@@ -360,7 +328,6 @@ stack-data==0.6.3
 terminado==0.18.1
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 tinycss2==1.3.0
     # via nbconvert
@@ -371,7 +338,6 @@ tornado==6.4
     #   ipykernel
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   terminado
 tox==4.14.0
@@ -391,26 +357,24 @@ traitlets==5.14.3
     #   jupyter-core
     #   jupyter-server
     #   matplotlib-inline
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
-    #   voila
-typing-extensions==4.11.0
+typing-extensions==4.12.0
     # via ipython
 uncertainties==3.1.7
     # via lmfit
 urllib3==1.26.18
     # via requests
-uv==0.1.44
+uv==0.2.5
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.4.0
+voila==0.2.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
@@ -420,9 +384,7 @@ webencodings==0.5.1
     #   tinycss2
 websocket-client==1.8.0
     # via jupyter-server
-websockets==12.0
-    # via voila
-widgetsnbextension==3.6.6
+widgetsnbextension==2.0.1
     # via ipywidgets
 wrapt==1.14.0
     # via plasmapy (pyproject.toml)
diff --git a/ci_requirements/tests-3.11.txt b/ci_requirements/tests-3.11.txt
index c5656757..32702dc1 100644
--- a/ci_requirements/tests-3.11.txt
+++ b/ci_requirements/tests-3.11.txt
@@ -1,6 +1,6 @@
 # This file was autogenerated by uv via the following command:
 #    nox -s requirements
-anyio==4.3.0
+anyio==4.4.0
     # via jupyter-server
 argcomplete==3.3.0
     # via nox
@@ -10,11 +10,11 @@ argon2-cffi-bindings==21.2.0
     # via argon2-cffi
 arrow==1.3.0
     # via isoduration
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==6.1.0
     # via plasmapy (pyproject.toml)
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
     # via astropy
 asttokens==2.4.1
     # via stack-data
@@ -51,7 +51,7 @@ comm==0.2.2
     #   ipywidgets
 contourpy==1.2.1
     # via matplotlib
-coverage==7.5.1
+coverage==7.5.2
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
@@ -75,7 +75,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.1
     # via matplotlib
 fqdn==1.5.1
     # via jsonschema
@@ -83,7 +83,7 @@ future==1.0.0
     # via uncertainties
 h5py==3.11.0
     # via plasmapy (pyproject.toml)
-hypothesis==6.102.4
+hypothesis==6.102.6
     # via plasmapy (pyproject.toml)
 identify==2.5.36
     # via pre-commit
@@ -122,7 +122,7 @@ jsonschema==4.22.0
     #   nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==8.6.1
+jupyter-client==8.6.2
     # via
     #   ipykernel
     #   jupyter-server
@@ -147,7 +147,7 @@ jupyter-server-terminals==0.5.3
     # via jupyter-server
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
     # via voila
 jupyterlab-widgets==3.0.10
     # via ipywidgets
@@ -171,7 +171,7 @@ mistune==3.0.2
     # via nbconvert
 mpmath==1.3.0
     # via plasmapy (pyproject.toml)
-nbclient==0.7.4
+nbclient==0.10.0
     # via
     #   nbconvert
     #   voila
@@ -318,7 +318,7 @@ referencing==0.35.1
     #   jsonschema
     #   jsonschema-specifications
     #   jupyter-events
-requests==2.31.0
+requests==2.32.2
     # via
     #   plasmapy (pyproject.toml)
     #   jupyterlab-server
@@ -334,13 +334,13 @@ rpds-py==0.18.1
     # via
     #   jsonschema
     #   referencing
-scipy==1.13.0
+scipy==1.13.1
     # via
     #   plasmapy (pyproject.toml)
     #   lmfit
 send2trash==1.8.3
     # via jupyter-server
-setuptools==69.5.1
+setuptools==70.0.0
     # via
     #   plasmapy (pyproject.toml)
     #   nodeenv
@@ -397,7 +397,7 @@ traitlets==5.14.3
     #   voila
 types-python-dateutil==2.9.0.20240316
     # via arrow
-typing-extensions==4.11.0
+typing-extensions==4.12.0
     # via ipython
 tzdata==2024.1
     # via pandas
@@ -407,14 +407,14 @@ uri-template==1.3.0
     # via jsonschema
 urllib3==2.2.1
     # via requests
-uv==0.1.44
+uv==0.2.4
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.5.6
+voila==0.5.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
diff --git a/ci_requirements/tests-3.12-lowest-direct.txt b/ci_requirements/tests-3.12-lowest-direct.txt
index 83ca263b..b72d0cc4 100644
--- a/ci_requirements/tests-3.12-lowest-direct.txt
+++ b/ci_requirements/tests-3.12-lowest-direct.txt
@@ -7,11 +7,10 @@ argcomplete==3.3.0
 argon2-cffi==23.1.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 argon2-cffi-bindings==21.2.0
     # via argon2-cffi
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==5.1
     # via plasmapy (pyproject.toml)
@@ -22,8 +21,6 @@ attrs==23.2.0
     #   hypothesis
     #   jsonschema
     #   referencing
-babel==2.15.0
-    # via jupyterlab-server
 beautifulsoup4==4.12.3
     # via nbconvert
 bleach==6.1.0
@@ -44,12 +41,10 @@ colorama==0.4.6
     # via tox
 colorlog==6.8.2
     # via nox
-coverage==7.5.1
+coverage==7.5.3
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
-debugpy==1.8.1
-    # via ipykernel
 decorator==5.1.1
     # via ipython
 defusedxml==0.7.1
@@ -57,7 +52,7 @@ defusedxml==0.7.1
 distlib==0.3.8
     # via virtualenv
 entrypoints==0.4
-    # via jupyter-client
+    # via nbconvert
 execnet==2.1.1
     # via pytest-xdist
 executing==2.0.1
@@ -68,7 +63,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.4
     # via matplotlib
 future==1.0.0
     # via uncertainties
@@ -84,7 +79,7 @@ idna==3.7
     #   requests
 iniconfig==2.0.0
     # via pytest
-ipykernel==6.8.0
+ipykernel==5.5.6
     # via
     #   plasmapy (pyproject.toml)
     #   ipywidgets
@@ -96,33 +91,26 @@ ipython==8.24.0
     #   ipywidgets
 ipython-genutils==0.2.0
     # via
-    #   ipywidgets
+    #   ipykernel
     #   nbclassic
     #   notebook
-ipywidgets==7.7.0
+ipywidgets==6.0.0
     # via plasmapy (pyproject.toml)
 jedi==0.19.1
     # via ipython
 jinja2==3.1.4
     # via
     #   jupyter-server
-    #   jupyterlab-server
-    #   nbclassic
     #   nbconvert
     #   notebook
-json5==0.9.25
-    # via jupyterlab-server
 jsonschema==4.22.0
-    # via
-    #   jupyterlab-server
-    #   nbformat
+    # via nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==7.4.1
+jupyter-client==6.1.12
     # via
     #   ipykernel
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   notebook
     #   voila
@@ -130,30 +118,23 @@ jupyter-core==5.7.2
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
-    #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
-    #   voila
 jupyter-server==1.24.0
     # via
-    #   jupyterlab-server
-    #   nbclassic
     #   notebook-shim
     #   voila
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.24.0
-    # via voila
-jupyterlab-widgets==3.0.10
-    # via ipywidgets
 kiwisolver==1.4.5
     # via matplotlib
 llvmlite==0.40.1
     # via numba
 lmfit==1.1.0
     # via plasmapy (pyproject.toml)
+lxml==5.2.2
+    # via nbconvert
 markupsafe==2.1.5
     # via
     #   jinja2
@@ -161,40 +142,35 @@ markupsafe==2.1.5
 matplotlib==3.5.1
     # via plasmapy (pyproject.toml)
 matplotlib-inline==0.1.7
-    # via
-    #   ipykernel
-    #   ipython
-mistune==3.0.2
+    # via ipython
+mistune==0.8.4
     # via nbconvert
 mpmath==1.2.1
     # via plasmapy (pyproject.toml)
-nbclassic==1.0.0
+nbclassic==1.1.0
     # via notebook
-nbclient==0.7.4
+nbclient==0.5.13
     # via
     #   nbconvert
     #   voila
-nbconvert==7.16.4
+nbconvert==6.5.4
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   voila
 nbformat==5.10.4
     # via
     #   ipywidgets
     #   jupyter-server
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   notebook
 nest-asyncio==1.6.0
     # via
-    #   ipykernel
-    #   jupyter-client
     #   nbclassic
+    #   nbclient
     #   notebook
-nodeenv==1.8.0
+nodeenv==1.9.0
     # via pre-commit
 notebook==6.5.7
     # via widgetsnbextension
@@ -221,7 +197,6 @@ packaging==23.2
     #   plasmapy (pyproject.toml)
     #   astropy
     #   jupyter-server
-    #   jupyterlab-server
     #   matplotlib
     #   nbconvert
     #   nox
@@ -258,9 +233,8 @@ pre-commit==3.6.0
 prometheus-client==0.20.0
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
-prompt-toolkit==3.0.43
+prompt-toolkit==3.0.45
     # via ipython
 ptyprocess==0.7.0
     # via
@@ -317,16 +291,13 @@ pyzmq==26.0.3
     # via
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
 referencing==0.35.1
     # via
     #   jsonschema
     #   jsonschema-specifications
 requests==2.28.0
-    # via
-    #   plasmapy (pyproject.toml)
-    #   jupyterlab-server
+    # via plasmapy (pyproject.toml)
 rpds-py==0.18.1
     # via
     #   jsonschema
@@ -338,12 +309,9 @@ scipy==1.9.2
 send2trash==1.8.3
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 setuptools==61.2.0
-    # via
-    #   plasmapy (pyproject.toml)
-    #   nodeenv
+    # via plasmapy (pyproject.toml)
 six==1.16.0
     # via
     #   asttokens
@@ -360,7 +328,6 @@ stack-data==0.6.3
 terminado==0.18.1
     # via
     #   jupyter-server
-    #   nbclassic
     #   notebook
 tinycss2==1.3.0
     # via nbconvert
@@ -371,7 +338,6 @@ tornado==6.4
     #   ipykernel
     #   jupyter-client
     #   jupyter-server
-    #   nbclassic
     #   notebook
     #   terminado
 tox==4.14.0
@@ -391,24 +357,22 @@ traitlets==5.14.3
     #   jupyter-core
     #   jupyter-server
     #   matplotlib-inline
-    #   nbclassic
     #   nbclient
     #   nbconvert
     #   nbformat
     #   notebook
-    #   voila
 uncertainties==3.1.7
     # via lmfit
 urllib3==1.26.18
     # via requests
-uv==0.1.44
+uv==0.2.5
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.4.0
+voila==0.2.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
@@ -418,9 +382,7 @@ webencodings==0.5.1
     #   tinycss2
 websocket-client==1.8.0
     # via jupyter-server
-websockets==12.0
-    # via voila
-widgetsnbextension==3.6.6
+widgetsnbextension==2.0.1
     # via ipywidgets
 wrapt==1.14.0
     # via plasmapy (pyproject.toml)
diff --git a/ci_requirements/tests-3.12.txt b/ci_requirements/tests-3.12.txt
index d5aef14b..bccfa9cf 100644
--- a/ci_requirements/tests-3.12.txt
+++ b/ci_requirements/tests-3.12.txt
@@ -1,6 +1,6 @@
 # This file was autogenerated by uv via the following command:
 #    nox -s requirements
-anyio==4.3.0
+anyio==4.4.0
     # via jupyter-server
 argcomplete==3.3.0
     # via nox
@@ -10,11 +10,11 @@ argon2-cffi-bindings==21.2.0
     # via argon2-cffi
 arrow==1.3.0
     # via isoduration
-asteval==0.9.32
+asteval==0.9.33
     # via lmfit
 astropy==6.1.0
     # via plasmapy (pyproject.toml)
-astropy-iers-data==0.2024.5.20.0.29.40
+astropy-iers-data==0.2024.5.27.0.30.8
     # via astropy
 asttokens==2.4.1
     # via stack-data
@@ -51,7 +51,7 @@ comm==0.2.2
     #   ipywidgets
 contourpy==1.2.1
     # via matplotlib
-coverage==7.5.1
+coverage==7.5.2
     # via pytest-cov
 cycler==0.12.1
     # via matplotlib
@@ -75,7 +75,7 @@ filelock==3.14.0
     # via
     #   tox
     #   virtualenv
-fonttools==4.51.0
+fonttools==4.52.1
     # via matplotlib
 fqdn==1.5.1
     # via jsonschema
@@ -83,7 +83,7 @@ future==1.0.0
     # via uncertainties
 h5py==3.11.0
     # via plasmapy (pyproject.toml)
-hypothesis==6.102.4
+hypothesis==6.102.6
     # via plasmapy (pyproject.toml)
 identify==2.5.36
     # via pre-commit
@@ -122,7 +122,7 @@ jsonschema==4.22.0
     #   nbformat
 jsonschema-specifications==2023.12.1
     # via jsonschema
-jupyter-client==8.6.1
+jupyter-client==8.6.2
     # via
     #   ipykernel
     #   jupyter-server
@@ -147,7 +147,7 @@ jupyter-server-terminals==0.5.3
     # via jupyter-server
 jupyterlab-pygments==0.3.0
     # via nbconvert
-jupyterlab-server==2.27.1
+jupyterlab-server==2.27.2
     # via voila
 jupyterlab-widgets==3.0.10
     # via ipywidgets
@@ -171,7 +171,7 @@ mistune==3.0.2
     # via nbconvert
 mpmath==1.3.0
     # via plasmapy (pyproject.toml)
-nbclient==0.7.4
+nbclient==0.10.0
     # via
     #   nbconvert
     #   voila
@@ -318,7 +318,7 @@ referencing==0.35.1
     #   jsonschema
     #   jsonschema-specifications
     #   jupyter-events
-requests==2.31.0
+requests==2.32.2
     # via
     #   plasmapy (pyproject.toml)
     #   jupyterlab-server
@@ -334,13 +334,13 @@ rpds-py==0.18.1
     # via
     #   jsonschema
     #   referencing
-scipy==1.13.0
+scipy==1.13.1
     # via
     #   plasmapy (pyproject.toml)
     #   lmfit
 send2trash==1.8.3
     # via jupyter-server
-setuptools==69.5.1
+setuptools==70.0.0
     # via
     #   plasmapy (pyproject.toml)
     #   nodeenv
@@ -405,14 +405,14 @@ uri-template==1.3.0
     # via jsonschema
 urllib3==2.2.1
     # via requests
-uv==0.1.44
+uv==0.2.4
     # via tox-uv
 virtualenv==20.26.2
     # via
     #   nox
     #   pre-commit
     #   tox
-voila==0.5.6
+voila==0.5.7
     # via plasmapy (pyproject.toml)
 wcwidth==0.2.13
     # via prompt-toolkit
diff --git a/tests/diagnostics/test_thomson.py b/tests/diagnostics/test_thomson.py
index a785c4e3..6a901634 100644
--- a/tests/diagnostics/test_thomson.py
+++ b/tests/diagnostics/test_thomson.py
@@ -364,14 +364,14 @@ def multiple_species_collective_args():
         "n": 5e17 * u.cm**-3,
         "T_e": 10 * u.eV,
     }
-    kwargs["T_i"] = np.array([5, 5]) * u.eV
+    kwargs["T_i"] = np.array([25, 25]) * u.eV
     kwargs["ions"] = [Particle("p+"), Particle("C-12 5+")]
     kwargs["probe_vec"] = np.array([1, 0, 0])
     kwargs["scatter_vec"] = np.array([0, 1, 0])
     kwargs["efract"] = np.array([1.0])
     kwargs["ifract"] = np.array([0.7, 0.3])
-    kwargs["electron_vel"] = np.array([[300, 0, 0]]) * u.km / u.s
-    kwargs["ion_vel"] = np.array([[-500, 0, 0], [0, 500, 0]]) * u.km / u.s
+    kwargs["electron_vel"] = np.array([[0, 0, 0]]) * u.km / u.s
+    kwargs["ion_vel"] = np.array([[-100, 0, 0], [0, 100, 0]]) * u.km / u.s
 
     return kwargs
 
@@ -418,26 +418,27 @@ def test_multiple_species_collective_spectrum(
 
     # Compute the width and max of the spectrum, and the wavelength
     # of the max (sensitive to ion vel)
-    width = width_at_value(wavelength.value, Skw.value, 0.2e-11)
-    max_skw = np.max(Skw.value)
+    max_skw = np.nanmax(Skw.value)
+    width = width_at_value(wavelength.value, Skw.value, 2e-12)
+
     max_wavelength = wavelength.value[np.argmax(Skw.value)]
 
     # Check width
-    assert np.isclose(width, 0.14, 1e-2), (
+    assert np.isclose(width, 0.17, 1e-2), (
         f"Multiple ion species case spectrum width is {width} instead of "
-        "expected 0.14"
+        "expected 0.17"
     )
 
     # Check max value
-    assert np.isclose(max_skw, 2.4e-11, 1e-11), (
+    assert np.isclose(max_skw, 6e-12, 1e-11), (
         f"Multiple ion species case spectrum max is {max_skw} instead of "
-        "expected 2.4e-11"
+        "expected 6e-12"
     )
 
     # Check max peak location
-    assert np.isclose(max_wavelength, 530.799, 1e-2), (
+    assert np.isclose(max_wavelength, 532, 1e-2), (
         "Multiple ion species case spectrum peak wavelength is "
-        f"{max_wavelength} instead of expected 530.79"
+        f"{max_wavelength} instead of expected 532"
     )
 
 
diff --git a/tests/particles/test_particle_class.py b/tests/particles/test_particle_class.py
index bf91d365..490b0c4a 100644
--- a/tests/particles/test_particle_class.py
+++ b/tests/particles/test_particle_class.py
@@ -1102,6 +1102,25 @@ def test_customized_particle_repr(cls, kwargs, expected_repr) -> None:
         )
 
 
+custom_particle_str_table = [
+    (
+        {"mass": 5.12 * u.kg, "charge": 6.2 * u.C},
+        "CustomParticle(mass=5.12 kg, charge=6.2 C)",
+    ),
+    (
+        {"mass": 5.12 * u.kg, "charge": 6.2 * u.C, "symbol": "I2"},
+        "I2",
+    ),
+]
+
+
+@pytest.mark.parametrize(("kwargs", "expected_str"), custom_particle_str_table)
+def test_custom_particle_str(kwargs, expected_str) -> None:
+    """Test the string representation of a custom particle."""
+    instance = CustomParticle(**kwargs)
+    assert str(instance) == expected_str
+
+
 @pytest.mark.parametrize("cls", [CustomParticle, DimensionlessParticle])
 @pytest.mark.parametrize("not_a_str", [1, u.kg])
 def test_typeerror_redefining_symbol(cls, not_a_str) -> None:
diff --git a/tests/utils/data/test_downloader.py b/tests/utils/data/test_downloader.py
index b5b8d653..ceb7884d 100644
--- a/tests/utils/data/test_downloader.py
+++ b/tests/utils/data/test_downloader.py
@@ -154,7 +154,7 @@ def test_get_file(
 @pytest.mark.parametrize(
     "downloader", ["downloader_validated", "downloader_unvalidated"]
 )
-def test_get_local_only_fle(downloader: Downloader, request):
+def test_get_local_only_file(downloader: Downloader, request):
     """
     Test various file retrieval modes
     """