Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ jobs:
matrix:
include:

- name: Run plasmapy-calculator
os: ubuntu-latest
python: '3.11'
nox_session: smoke_test

- name: Check consistency of pinned & project requirements
os: ubuntu-latest
python: '3.13'
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11
3.10
7 changes: 6 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@ def validate_requirements(session: nox.Session) -> None:
session.run("uv", "lock", "--check", "--offline", "--no-progress")


@nox.session(python="3.11")
@nox.session(python="3.10")
def smoke_test(session: nox.Session) -> None:
session.install(".")
session.run("plasmapy-calculator")


@nox.session
def build(session: nox.Session) -> None:
session.run("uv", "build", "--no-progress")
103 changes: 98 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,110 @@ authors = [
{ name = "Erik Everson" },
{ name = "Nick Murphy", email = "namurphy@cfa.harvard.edu" },
]
requires-python = ">=3.11.11,<3.12"
requires-python = ">=3.10,<3.11"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.10",
]
# There were breaking changes in a voila | voila's dependencies since
# the plasma calculator notebook was originally contributed to PlasmaPy,
# which makes it prudent to tentatively pin dependencies to versions
# that are known to work.
dependencies = [
"anyio==3.6.1",
"argon2-cffi==21.3",
"argon2-cffi-bindings==21.2",
"astropy==5.1",
"asttokens==2.0.5",
"attrs==21.4",
"backcall==0.2",
"beautifulsoup4==4.11.1",
"bleach==5.0.1",
"cached-property==1.5.2",
"cffi==1.15.1",
"cycler==0.11",
"debugpy==1.6",
"decorator==5.1.1",
"defusedxml==0.7.1",
"entrypoints==0.4",
"executing==0.8.3",
"fastjsonschema==2.15.3",
"fonttools==4.33.3",
"idna==3.3",
"ipykernel==6.15",
"ipython==8.4",
"ipython-genutils==0.2",
"ipywidgets==7.6.5",
"jedi==0.18.1",
"jinja2==2.11.3",
"jsonschema==4.6.1",
"jupyter-client==6.1.12",
"jupyter-core==4.10",
"jupyter-server==1.18.1",
"jupyterlab-pygments==0.2.2",
"jupyterlab-widgets==1.1.1",
"kiwisolver==1.4.3",
"lxml==4.9.1",
"markupsafe==2.0.1",
"matplotlib==3.5.2",
"matplotlib-inline==0.1.3",
"mistune==0.8.4",
"nbclient==0.5.13",
"nbconvert==6.4.5",
"nbformat==5.4",
"nest-asyncio==1.5.5",
"notebook==6.4.12",
"numpy==1.23",
"packaging==21.3",
"pandas==1.4.3",
"pandocfilters==1.5",
"parso==0.8.3",
"pexpect==4.8",
"pickleshare==0.7.5",
"pillow==9.2",
"plasmapy==0.7",
"prometheus-client==0.14.1",
"prompt-toolkit==3.0.30",
"psutil==5.9.1",
"ptyprocess==0.7",
"pure-eval==0.2.2",
"pycparser==2.21",
"pyerfa==2.0.0.1",
"pygments==2.12",
"pyparsing==3.0.9",
"pyrsistent==0.18.1",
"python-dateutil==2.8.2",
"pytz==2022.1",
"pyyaml==6",
"pyzmq==23.2",
"scipy==1.8.1",
"send2trash==1.8",
"setuptools==63.1",
"six==1.16",
"sniffio==1.2",
"soupsieve==2.3.2.post1",
"stack-data==0.3",
"terminado==0.15",
"testpath==0.6",
"tornado==6.2",
"tqdm==4.64",
"traitlets==5.3",
"voila==0.2.15",
"wcwidth==0.2.5",
"webencodings==0.5.1",
"websocket-client==1.3.3",
"widgetsnbextension==3.5.2",
"xarray==2022.3",
]
dependencies = [ ]

scripts.plasmapy-calculator = "plasmapy_calculator:main"

[dependency-groups]
dev = [
"nox>=2025.5.1",
"uv>=0.8.4",
"nox",
"uv",
]

[tool.uv.build-backend]
source-include = [ "src/plasmapy_calculator/favicon.ico" ]
data = { data = "src/plasmapy_calculator/favicon.ico" }
58 changes: 57 additions & 1 deletion src/plasmapy_calculator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,58 @@
"""
Script and utilities to launch the plasma calculator.
"""

__all__ = ["main"]

import argparse
import pathlib
import subprocess

_description = """
Plasma calculator is a tool that opens a page in a web browser for
interactive calculation of plasma parameters.

This tool is currently in the prototype stage and is expected to change in
the future. Please raise an issue at the following link to provide suggestions
and feedback: https://github.com/PlasmaPy/PlasmaPy/issues/new
"""


def main() -> None:
print("Hello from plasmapy-calculator!")
"""
Stub function for command line tool that launches the plasma calculator notebook.
"""
parser = argparse.ArgumentParser(description=_description)
parser.add_argument(
"--port", type=int, default=8866, help="Port to run the notebook"
)
parser.add_argument(
"--dark", action="store_true", help="Turn on dark mode, reduces eye strain"
)
parser.add_argument(
"--no-browser", action="store_true", help="Do not open the browser"
)

module_path = pathlib.Path(__file__).parent.absolute()
notebook_path = module_path / "plasma_calculator.ipynb"
favicon_path = module_path / "favicon.ico"

args = parser.parse_args()
theme = "dark" if args.dark else "light"
no_browser = "--no-browser" if args.no_browser else ""

command = [
"voila",
notebook_path,
f"--port={args.port}",
f"--theme={theme}",
f"--VoilaConfiguration.file_whitelist={favicon_path}",
]
if no_browser:
command.append(no_browser)

try:
subprocess.call(command) # noqa: S603
except KeyboardInterrupt:
# We'll need to switch from print() to using logging library
print("Stopping calculator! Bye") # noqa: T201
Binary file added src/plasmapy_calculator/favicon.ico
Binary file not shown.
180 changes: 180 additions & 0 deletions src/plasmapy_calculator/main_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""
Collection of private functions to load properties and construct widgets.
"""

__all__: list[str] = []

import json
import warnings
from pathlib import Path

import astropy.units as u
from ipywidgets import widgets

from plasmapy_calculator.widget_helpers import (
_calculate_button,
_CheckBox,
_clear_button,
_create_label,
_create_widget,
_FloatBox,
_FunctionInfo,
_IonBox,
_ParticleBox,
_process_queue,
)

warnings.filterwarnings(
action="ignore",
message="Passing unrecognized arguments",
category=DeprecationWarning,
) # see issue 2370 for a deprecation warning from traitlets

LIGHT_BLUE = "#00BFD8"
"""
LIGHT_BLUE: Constant for light blue color 00BFD8
"""
LIGHT_GRAY = "#A9A9A9"
"""
LIGHT_GRAY: Constant for light gray color A9A9A9
"""

grid_data = [
[
_create_label("Parameter", color=LIGHT_BLUE),
_create_label("Value", color=LIGHT_BLUE),
_create_label("Unit", color=LIGHT_BLUE),
],
[
_create_label("B - Magnetic Field Magnitude:"),
*_create_widget(
_FloatBox,
property_name="B",
unit=u.T,
opts=[u.T, u.G, u.uG],
),
],
[
_create_label("Particle:"),
_create_widget(
_ParticleBox,
property_name="particle",
property_alias="particle_type",
placeholder="Enter particle e.g. neutron,He",
),
],
[
_create_label("Ion:"),
_create_widget(
_IonBox,
property_name="ion",
property_alias="ion_type",
placeholder="Enter ion e.g. He 2+",
),
],
[
_create_label("Convert to Hertz:"),
_create_widget(_CheckBox, property_name="to_hz"),
],
[
_create_label("_" * 20, color=LIGHT_BLUE),
_create_label("Density Number"),
_create_label("_" * 20, color=LIGHT_BLUE),
],
[
_create_label("n - Standard Density Number:"),
*_create_widget(
_FloatBox,
property_name="n",
unit=u.m**-3,
opts=[u.m**-3, u.cm**-3, u.mm**-3],
),
],
[
_create_label("n<sub>e</sub> - Electron Density Number:"),
*_create_widget(
_FloatBox,
property_name="n_e",
unit=u.m**-3,
opts=[u.m**-3, u.cm**-3, u.mm**-3],
),
],
[
_create_label("n<sub>i</sub> - Ion Number Density:"),
*_create_widget(
_FloatBox,
property_name="n_i",
unit=u.m**-3,
opts=[u.m**-3, u.cm**-3, u.mm**-3],
),
],
[
_create_label("_" * 20, color=LIGHT_BLUE),
_create_label("Temperature"),
_create_label("_" * 20, color=LIGHT_BLUE),
],
[
_create_label("T - Standard Temperature:"),
_create_widget(_FloatBox, property_name="T", min=0, unit=u.K),
_create_label("K", color=LIGHT_GRAY),
],
[
_create_label("T<sub>e</sub> - Electron Temperature:"),
_create_widget(_FloatBox, property_name="T_e", min=0, unit=u.K),
_create_label("K", color=LIGHT_GRAY),
],
[
_create_label("T<sub>i</sub> - Ion Temperature:"),
_create_widget(_FloatBox, property_name="T_i", min=0, unit=u.K),
_create_label("K", color=LIGHT_GRAY),
],
]
"""
grid_data: Contains widgets layout for input parameters
"""


def _create_interactive_layout():
"""
Interactive grid layout for input parameters populated in grid_data.
"""
grid = widgets.GridspecLayout(18, 3)
grid.layout.margin = "10px"

for i, row in enumerate(grid_data):
for j, cell in enumerate(row):
grid[i, j] = cell

grid[-1, 0] = _calculate_button
grid[-1, 1] = _clear_button
return grid


def _create_output_layout():
"""
Tab layout for output parameters, populated from
``properties_metadata.json``.
"""
app = widgets.Tab()
children = []

properties_metadata = Path("properties_metadata.json")

with properties_metadata.open() as f:
data = json.load(f)

for i, title in enumerate(data):
grid_layout = widgets.GridspecLayout(10, 2, width="100%")
for j, prop in enumerate(data[title]):
fn = _FunctionInfo(prop["module_name"], prop["function_name"])
if "spec_combo" in prop:
for spec_combo in prop["spec_combo"]:
fn.add_combo(spec_combo)
grid_layout[j, 0] = _create_label(prop["function_name"] + ":")
grid_layout[j, 1] = fn.get_output_widget()
_process_queue.append(fn)
children.append(grid_layout)
app.set_title(i, title)
app.children = children

return app
Loading