astro-plasma 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. astro_plasma-1.0.1/.gitattributes +3 -0
  2. astro_plasma-1.0.1/.github/scripts/loc_badge.py +155 -0
  3. astro_plasma-1.0.1/.github/scripts/register_freya_runner.sh +81 -0
  4. astro_plasma-1.0.1/.github/scripts/submit_freya_gpu_test.sh +114 -0
  5. astro_plasma-1.0.1/.github/workflows/AstroPlasma.yml +29 -0
  6. astro_plasma-1.0.1/.github/workflows/freya-slurm-gpu.yml +70 -0
  7. astro_plasma-1.0.1/.github/workflows/lines_of_code.yml +47 -0
  8. astro_plasma-1.0.1/.github/workflows/release.yml +134 -0
  9. astro_plasma-1.0.1/.gitignore +153 -0
  10. astro_plasma-1.0.1/.pre-commit-config.yaml +43 -0
  11. astro_plasma-1.0.1/LICENSE +21 -0
  12. astro_plasma-1.0.1/PKG-INFO +550 -0
  13. astro_plasma-1.0.1/README.md +518 -0
  14. astro_plasma-1.0.1/astro_plasma/__init__.py +29 -0
  15. astro_plasma-1.0.1/astro_plasma/core/__init__.py +1 -0
  16. astro_plasma-1.0.1/astro_plasma/core/compat.py +41 -0
  17. astro_plasma-1.0.1/astro_plasma/core/constants.py +51 -0
  18. astro_plasma-1.0.1/astro_plasma/core/data_dir.py +27 -0
  19. astro_plasma-1.0.1/astro_plasma/core/datasift.py +771 -0
  20. astro_plasma-1.0.1/astro_plasma/core/download_database.py +210 -0
  21. astro_plasma-1.0.1/astro_plasma/core/functions.py +67 -0
  22. astro_plasma-1.0.1/astro_plasma/core/ionization.py +460 -0
  23. astro_plasma-1.0.1/astro_plasma/core/spectrum.py +162 -0
  24. astro_plasma-1.0.1/astro_plasma/core/utils.py +257 -0
  25. astro_plasma-1.0.1/astro_plasma/data/cooltable.dat +8002 -0
  26. astro_plasma-1.0.1/astro_plasma/data/solar_GASS10.abn +50 -0
  27. astro_plasma-1.0.1/cloudy-codes/coolingFunction/cddefines.h +1462 -0
  28. astro_plasma-1.0.1/cloudy-codes/coolingFunction/cddrive.h +582 -0
  29. astro_plasma-1.0.1/cloudy-codes/coolingFunction/cdstd.h +43 -0
  30. astro_plasma-1.0.1/cloudy-codes/coolingFunction/cloudyconfig.h +20 -0
  31. astro_plasma-1.0.1/cloudy-codes/coolingFunction/compile-command +3 -0
  32. astro_plasma-1.0.1/cloudy-codes/coolingFunction/cpu.h +660 -0
  33. astro_plasma-1.0.1/cloudy-codes/coolingFunction/hazy_coolingcurve.cpp +308 -0
  34. astro_plasma-1.0.1/cloudy-codes/coolingFunction/mpi_utilities.h +168 -0
  35. astro_plasma-1.0.1/cloudy-codes/coolingFunction/version.h +35 -0
  36. astro_plasma-1.0.1/cloudy-codes/emmSpec/ProgressBar.py +30 -0
  37. astro_plasma-1.0.1/cloudy-codes/emmSpec/generateCloudyScript-parallel.py +369 -0
  38. astro_plasma-1.0.1/cloudy-codes/emmSpec/slurm-python-job +2 -0
  39. astro_plasma-1.0.1/cloudy-codes/emmSpec/slurm-script +18 -0
  40. astro_plasma-1.0.1/cloudy-codes/ionFrac/ProgressBar.py +30 -0
  41. astro_plasma-1.0.1/cloudy-codes/ionFrac/generate-hashlist.py +36 -0
  42. astro_plasma-1.0.1/cloudy-codes/ionFrac/generateIonFraction-parallel.py +187 -0
  43. astro_plasma-1.0.1/cloudy-codes/ionFrac/slurm-python-job +2 -0
  44. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cddefines.h +1462 -0
  45. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cddrive.h +582 -0
  46. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cdstd.h +43 -0
  47. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cloudyconfig.h +20 -0
  48. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/compile-script.sh +3 -0
  49. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cpu.h +660 -0
  50. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/ionization.cpp +245 -0
  51. astro_plasma-1.0.1/cloudy-codes/ionFrac/src/mpi_utilities.h +168 -0
  52. astro_plasma-1.0.1/codemeta.json +64 -0
  53. astro_plasma-1.0.1/example-scripts/ion-frac-Oxygen.txt +62 -0
  54. astro_plasma-1.0.1/example-scripts/ionization_cloudy_compare.png +0 -0
  55. astro_plasma-1.0.1/example-scripts/ionization_cloudy_compare.py +146 -0
  56. astro_plasma-1.0.1/example-scripts/parallel_query.py +54 -0
  57. astro_plasma-1.0.1/example-scripts/user_guide.ipynb +421 -0
  58. astro_plasma-1.0.1/misc/1912.00844.pdf +0 -0
  59. astro_plasma-1.0.1/misc/MillerBreg_OVII.txt +36 -0
  60. astro_plasma-1.0.1/misc/VirialRad.txt +61 -0
  61. astro_plasma-1.0.1/misc/aa55_tumlinson_supfig4.pdf +0 -0
  62. astro_plasma-1.0.1/misc/apjs456058t3_mrt.txt +1078 -0
  63. astro_plasma-1.0.1/misc/hazy3_06_01_rc1.pdf +0 -0
  64. astro_plasma-1.0.1/misc/nHval.txt +40 -0
  65. astro_plasma-1.0.1/pyproject.toml +85 -0
  66. astro_plasma-1.0.1/requirements/requirements-all.txt +5 -0
  67. astro_plasma-1.0.1/requirements/requirements-dev.txt +9 -0
  68. astro_plasma-1.0.1/requirements/requirements-gpu.txt +11 -0
  69. astro_plasma-1.0.1/requirements/requirements.txt +10 -0
  70. astro_plasma-1.0.1/tests/sample_spectrum.txt +8228 -0
  71. astro_plasma-1.0.1/tests/test_astro_palsma_basic.py +192 -0
  72. astro_plasma-1.0.1/tests/test_astro_palsma_run.py +174 -0
  73. astro_plasma-1.0.1/tests/test_cuda.py +206 -0
@@ -0,0 +1,3 @@
1
+ cloudy-codes/** linguist-vendored=true
2
+ misc/** linguist-vendored=true
3
+ example-scripts/** linguist-vendored=true
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Count the lines of code in the repository and render the badge SVG.
4
+
5
+ Replaces shadowmoose/GHA-LoC-Badge, which is pinned at its only release (1.0.0,
6
+ 2020), still declares ``using: node12`` and reports its results with the retired
7
+ ``set-output`` command. There is no newer tag to upgrade to, so the behaviour is
8
+ reproduced here instead: the same badge layout, and results written to
9
+ ``$GITHUB_OUTPUT``.
10
+
11
+ Only files tracked by git are counted, which keeps the total independent of
12
+ whatever untracked material happens to sit in the working tree on a runner.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import os
19
+ import re
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ # Layout constants taken from the badge this replaces, so the rendered SVG is
25
+ # unchanged for a given count. The label is fixed, hence a fixed text length;
26
+ # digits and separators in the value each advance by 64 units at font-size 110.
27
+ LABEL_TEXT_LENGTH = 747
28
+ VALUE_ADVANCE_PER_CHAR = 64
29
+ TEXT_SIDE_PADDING = 100
30
+
31
+ SVG_TEMPLATE = """<svg width="{width}" height="20" viewBox="0 0 {total} 200" xmlns="http://www.w3.org/2000/svg">
32
+ <linearGradient id="a" x2="0" y2="100%">
33
+ <stop offset="0" stop-opacity=".1" stop-color="#EEE"/>
34
+ <stop offset="1" stop-opacity=".1"/>
35
+ </linearGradient>
36
+ <mask id="m"><rect width="{total}" height="200" rx="30" fill="#FFF"/></mask>
37
+ <g mask="url(#m)">
38
+ <rect width="{label_w}" height="200" fill="#555"/>
39
+ <rect width="{value_w}" height="200" fill="{color}" x="{label_w}"/>
40
+ <rect width="{total}" height="200" fill="url(#a)"/>
41
+ </g>
42
+ <g fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110">
43
+ <text x="60" y="148" textLength="{label_len}" fill="#000" opacity="0.25">{label}</text>
44
+ <text x="50" y="138" textLength="{label_len}">{label}</text>
45
+ <text x="{value_shadow_x}" y="148" textLength="{value_len}" fill="#000" opacity="0.25">{value}</text>
46
+ <text x="{value_x}" y="138" textLength="{value_len}">{value}</text>
47
+ </g>
48
+ {gap}
49
+ </svg>"""
50
+
51
+ # The replaced action emitted a two-space line before the closing tag and no
52
+ # final newline. Kept as a placeholder so the badge stays byte-identical
53
+ # without leaving trailing whitespace in this file for the linters to strip.
54
+ SVG_GAP = " "
55
+
56
+
57
+ def tracked_files(root: Path) -> list[str]:
58
+ out = subprocess.run(
59
+ ["git", "-C", str(root), "ls-files", "-z"],
60
+ check=True,
61
+ capture_output=True,
62
+ text=True,
63
+ ).stdout
64
+ return [name for name in out.split("\0") if name]
65
+
66
+
67
+ def is_hidden(name: str) -> bool:
68
+ """
69
+ True when any component of the path starts with a dot.
70
+
71
+ The action being replaced walked the working tree and skipped dot-prefixed
72
+ entries, so .github, .gitignore and friends never reached the total.
73
+ """
74
+ return any(part.startswith(".") for part in name.split("/"))
75
+
76
+
77
+ def count_lines(path: Path) -> int | None:
78
+ """
79
+ Line count for a text file, or None when the file looks binary.
80
+ """
81
+ try:
82
+ data = path.read_bytes()
83
+ except OSError:
84
+ return None
85
+ if b"\0" in data:
86
+ return None
87
+ # Matches the replaced action, which split the text on newlines and took the
88
+ # length -- so a trailing newline contributes a final empty line.
89
+ return data.count(b"\n") + 1
90
+
91
+
92
+ def render(label: str, value: str, color: str) -> str:
93
+ label_w = LABEL_TEXT_LENGTH + TEXT_SIDE_PADDING
94
+ value_len = VALUE_ADVANCE_PER_CHAR * len(value)
95
+ value_w = value_len + TEXT_SIDE_PADDING
96
+ total = label_w + value_w
97
+ return SVG_TEMPLATE.format(
98
+ width=f"{total / 10:g}",
99
+ total=total,
100
+ label_w=label_w,
101
+ value_w=value_w,
102
+ color=color,
103
+ label_len=LABEL_TEXT_LENGTH,
104
+ label=label,
105
+ value_len=value_len,
106
+ value=value,
107
+ value_x=label_w + 45,
108
+ value_shadow_x=label_w + 55,
109
+ gap=SVG_GAP,
110
+ )
111
+
112
+
113
+ def main() -> int:
114
+ parser = argparse.ArgumentParser(description=__doc__)
115
+ parser.add_argument("--root", default=".", help="Repository root (default: %(default)s)")
116
+ parser.add_argument("--ignore", default="", help="Regex alternation of paths to skip")
117
+ parser.add_argument("--badge", required=True, help="Path of the SVG to write")
118
+ parser.add_argument("--label", default="Lines of Code", help="Badge label (default: %(default)s)")
119
+ parser.add_argument("--color", default="#08C", help="Badge value colour (default: %(default)s)")
120
+ args = parser.parse_args()
121
+
122
+ root = Path(args.root).resolve()
123
+ ignore = re.compile(args.ignore) if args.ignore else None
124
+
125
+ total_lines = 0
126
+ counted_files = 0
127
+ for name in tracked_files(root):
128
+ if is_hidden(name):
129
+ continue
130
+ if ignore is not None and ignore.search(name):
131
+ continue
132
+ lines = count_lines(root / name)
133
+ if lines is None:
134
+ continue
135
+ total_lines += lines
136
+ counted_files += 1
137
+
138
+ badge_path = Path(args.badge)
139
+ badge_path.parent.mkdir(parents=True, exist_ok=True)
140
+ badge_path.write_text(render(args.label, f"{total_lines:,}", args.color))
141
+
142
+ print(f"Scanned: {counted_files}")
143
+ print(f"Line Count: {total_lines}")
144
+
145
+ # Environment files, the supported replacement for the set-output command.
146
+ github_output = os.environ.get("GITHUB_OUTPUT")
147
+ if github_output:
148
+ with open(github_output, "a") as handle:
149
+ handle.write(f"counted_files={counted_files}\n")
150
+ handle.write(f"total_lines={total_lines}\n")
151
+ return 0
152
+
153
+
154
+ if __name__ == "__main__":
155
+ sys.exit(main())
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Register a GitHub self-hosted runner for this repository on Freya.
5
+ #
6
+ # Required:
7
+ # RUNNER_TOKEN One-time registration token from:
8
+ # Repo -> Settings -> Actions -> Runners -> New self-hosted runner
9
+ #
10
+ # Optional env vars:
11
+ # REPO_URL default: https://github.com/dutta-alankar/AstroPlasma
12
+ # RUNNER_ROOT default: $HOME/actions-runner-astroplasma
13
+ # RUNNER_VERSION default: 2.326.0
14
+ # RUNNER_NAME default: <hostname>-gpu
15
+ # RUNNER_LABELS default: self-hosted,linux,freya,gpu,slurm
16
+ # RUNNER_WORKDIR default: _work
17
+ # START_MODE default: interactive (interactive|nohup)
18
+ # FORCE_RECONFIGURE default: 0 (set to 1 to re-run config with --replace)
19
+
20
+ REPO_URL="${REPO_URL:-https://github.com/dutta-alankar/AstroPlasma}"
21
+ RUNNER_ROOT="${RUNNER_ROOT:-$HOME/actions-runner-astroplasma}"
22
+ RUNNER_VERSION="${RUNNER_VERSION:-2.326.0}"
23
+ RUNNER_NAME="${RUNNER_NAME:-$(hostname -s)-gpu}"
24
+ RUNNER_LABELS="${RUNNER_LABELS:-self-hosted,linux,freya,gpu,slurm}"
25
+ RUNNER_WORKDIR="${RUNNER_WORKDIR:-_work}"
26
+ START_MODE="${START_MODE:-interactive}"
27
+ FORCE_RECONFIGURE="${FORCE_RECONFIGURE:-0}"
28
+
29
+ if [[ -z "${RUNNER_TOKEN:-}" ]]; then
30
+ echo "error: RUNNER_TOKEN is required." >&2
31
+ exit 1
32
+ fi
33
+
34
+ mkdir -p "${RUNNER_ROOT}"
35
+ cd "${RUNNER_ROOT}"
36
+
37
+ RUNNER_TGZ="actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"
38
+ RUNNER_URL="https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/${RUNNER_TGZ}"
39
+
40
+ if [[ ! -x "./config.sh" ]]; then
41
+ echo "Downloading GitHub Actions runner ${RUNNER_VERSION}..."
42
+ curl -fsSL -o "${RUNNER_TGZ}" "${RUNNER_URL}"
43
+ tar xzf "${RUNNER_TGZ}"
44
+ rm -f "${RUNNER_TGZ}"
45
+ fi
46
+
47
+ if [[ -f ./.runner && "${FORCE_RECONFIGURE}" != "1" ]]; then
48
+ echo "Runner already configured in ${RUNNER_ROOT}."
49
+ echo "Set FORCE_RECONFIGURE=1 to reconfigure with --replace."
50
+ else
51
+ echo "Configuring runner ${RUNNER_NAME} for ${REPO_URL}..."
52
+ ./config.sh \
53
+ --url "${REPO_URL}" \
54
+ --token "${RUNNER_TOKEN}" \
55
+ --name "${RUNNER_NAME}" \
56
+ --labels "${RUNNER_LABELS}" \
57
+ --work "${RUNNER_WORKDIR}" \
58
+ --unattended \
59
+ --replace
60
+ fi
61
+
62
+ case "${START_MODE}" in
63
+ interactive)
64
+ cat <<MSG
65
+ Runner is configured.
66
+ Start it in the current shell with:
67
+ cd ${RUNNER_ROOT}
68
+ ./run.sh
69
+ MSG
70
+ ;;
71
+ nohup)
72
+ LOG_FILE="${RUNNER_ROOT}/runner-nohup.log"
73
+ nohup ./run.sh >"${LOG_FILE}" 2>&1 &
74
+ echo "Runner started in background (nohup)."
75
+ echo "Log: ${LOG_FILE}"
76
+ ;;
77
+ *)
78
+ echo "error: START_MODE must be 'interactive' or 'nohup'" >&2
79
+ exit 1
80
+ ;;
81
+ esac
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Submit GPU tests to Freya Slurm and block until completion.
5
+
6
+ if ! command -v sbatch >/dev/null 2>&1; then
7
+ echo "error: sbatch not found. This job must run on a Freya node with Slurm available." >&2
8
+ exit 1
9
+ fi
10
+
11
+ REPO_ROOT="${GITHUB_WORKSPACE:-$(pwd)}"
12
+ SLURM_SCRIPT="$(mktemp -t astroplasma-gpu-XXXXXX.sbatch)"
13
+
14
+ cat >"${SLURM_SCRIPT}" <<EOF
15
+ #!/usr/bin/env bash
16
+ #SBATCH --job-name=astroplasma-gpu-ci
17
+ #SBATCH --partition=p.gpu
18
+ #SBATCH --nodes=1 # Request 1 or more full nodes
19
+ #SBATCH --constraint="gpu" # providing GPUs.
20
+ #SBATCH --gres=gpu:1 # Request 1 GPU per node.
21
+ #SBATCH --ntasks-per-node=1 # Run one task per GPU
22
+ #SBATCH --cpus-per-task=1 # using 1 cores for each task.
23
+ #SBATCH --mail-type=none
24
+ #SBATCH --time=00:45:00
25
+ #SBATCH --output=${REPO_ROOT}/slurm-%j.out
26
+
27
+ set -euo pipefail
28
+
29
+ module purge
30
+ module load cuda/12.8 gcc/12 openmpi/4.1
31
+
32
+ export LD_LIBRARY_PATH="/mpcdf/soft/SLE_15/packages/skylake/openmpi/gcc_12-12.1.0/4.1.8/lib:${LD_LIBRARY_PATH:-}"
33
+
34
+ cd "${REPO_ROOT}"
35
+
36
+ if [[ ! -f .venv/bin/activate ]]; then
37
+ echo "error: .venv is missing in ${REPO_ROOT}. Ensure workflow step 'Prepare virtual environment' ran." >&2
38
+ exit 1
39
+ fi
40
+
41
+ source .venv/bin/activate
42
+
43
+ if [[ -x .venv/bin/uv ]]; then
44
+ UV_BIN=.venv/bin/uv
45
+ elif command -v uv >/dev/null 2>&1; then
46
+ UV_BIN="$(command -v uv)"
47
+ else
48
+ echo "error: uv is unavailable. Ensure workflow prep step installs dependencies with uv." >&2
49
+ exit 1
50
+ fi
51
+
52
+ # Derive the header location from the venv interpreter itself, so this keeps
53
+ # working when the Python version changes.
54
+ PY_INCLUDE="\$(python -c 'import sysconfig; print(sysconfig.get_paths()["include"])')"
55
+ MPICC=/mpcdf/soft/SLE_15/packages/skylake/openmpi/gcc_12-12.1.0/4.1.8/bin/mpicc
56
+
57
+ MPICC="\${MPICC}" \
58
+ CPPFLAGS="-I\${PY_INCLUDE}" \
59
+ CFLAGS="-I\${PY_INCLUDE}" \
60
+ "\${UV_BIN}" pip install --python "${REPO_ROOT}/.venv/bin/python" \
61
+ --force-reinstall --no-cache-dir --no-binary=mpi4py mpi4py
62
+
63
+ export RUN_ON_CUDA=1
64
+ python -m pytest tests/test_cuda.py -v
65
+ EOF
66
+
67
+ chmod +x "${SLURM_SCRIPT}"
68
+
69
+ echo "Submitting Slurm job with script: ${SLURM_SCRIPT}"
70
+ JOB_ID="$(sbatch --parsable "${SLURM_SCRIPT}" | cut -d';' -f1)"
71
+
72
+ if [[ -z "${JOB_ID}" ]]; then
73
+ echo "error: failed to parse Slurm job id from sbatch output" >&2
74
+ exit 1
75
+ fi
76
+
77
+ echo "Submitted job id: ${JOB_ID}"
78
+
79
+ LOG_FILE="${REPO_ROOT}/slurm-${JOB_ID}.out"
80
+
81
+ while true; do
82
+ STATE="$(sacct -j "${JOB_ID}" --format=State --noheader 2>/dev/null | head -n1 | awk '{print $1}')"
83
+ if [[ -z "${STATE}" ]]; then
84
+ STATE="$(squeue -j "${JOB_ID}" -h -o "%T" 2>/dev/null | head -n1 || true)"
85
+ fi
86
+
87
+ echo "Job ${JOB_ID} state: ${STATE:-UNKNOWN}"
88
+
89
+ case "${STATE}" in
90
+ COMPLETED)
91
+ break
92
+ ;;
93
+ FAILED|CANCELLED|TIMEOUT|PREEMPTED|NODE_FAIL|OUT_OF_MEMORY|BOOT_FAIL|DEADLINE)
94
+ if [[ -f "${LOG_FILE}" ]]; then
95
+ echo "--- Slurm log tail (${LOG_FILE}) ---"
96
+ tail -n 200 "${LOG_FILE}" || true
97
+ fi
98
+ exit 1
99
+ ;;
100
+ PENDING|CONFIGURING|RUNNING|COMPLETING|"")
101
+ ;;
102
+ *)
103
+ ;;
104
+ esac
105
+
106
+ sleep 20
107
+ done
108
+
109
+ if [[ -f "${LOG_FILE}" ]]; then
110
+ echo "--- Slurm log tail (${LOG_FILE}) ---"
111
+ tail -n 200 "${LOG_FILE}" || true
112
+ fi
113
+
114
+ echo "Slurm GPU test job ${JOB_ID} completed successfully."
@@ -0,0 +1,29 @@
1
+ name: AstroPlasma Unit tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ push:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ python-env:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v5
14
+ - uses: actions/setup-python@v6
15
+ with:
16
+ python-version: '3.11'
17
+ # The working directory used to be captured with mathiasvr/command-output,
18
+ # which is Node 16 and has had no release since 2023. It only ever ran
19
+ # `pwd`, which after checkout is exactly github.workspace, so the step goes
20
+ # rather than being replaced.
21
+ - name: Install AstroPlasma dependencies
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install uv
25
+ uv pip install --system -r requirements/requirements-dev.txt
26
+ - name: Test AstroPlasma
27
+ run: |
28
+ export PYTHONPATH=${{ github.workspace }}
29
+ pytest
@@ -0,0 +1,70 @@
1
+ name: Freya Slurm GPU Test
2
+
3
+ env:
4
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
5
+ PYTHON_VERSION: '3.14'
6
+
7
+ on:
8
+ workflow_dispatch:
9
+ inputs:
10
+ approvers:
11
+ description: Comma-separated GitHub usernames allowed to approve this run
12
+ required: false
13
+ default: dutta-alankar
14
+ pull_request:
15
+ types: [opened, synchronize, reopened, ready_for_review]
16
+
17
+ jobs:
18
+ approval-gate:
19
+ name: Await Approval
20
+ runs-on: ubuntu-latest
21
+ if: github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false
22
+ steps:
23
+ - name: Manual approval
24
+ uses: trstringer/manual-approval@v1
25
+ with:
26
+ secret: ${{ github.token }}
27
+ approvers: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.approvers || github.repository_owner }}
28
+ minimum-approvals: 1
29
+ issue-title: Approve Freya Slurm GPU test
30
+ issue-body: |
31
+ Approve this run to submit a GPU test job to Freya Slurm.
32
+ Ref: ${{ github.ref }}
33
+ SHA: ${{ github.sha }}
34
+
35
+ freya-gpu-slurm-test:
36
+ name: Submit Slurm GPU Test
37
+ needs: approval-gate
38
+ runs-on: [self-hosted, linux, freya]
39
+ timeout-minutes: 90
40
+ steps:
41
+ - name: Checkout repository
42
+ uses: actions/checkout@v5
43
+
44
+ - name: Prepare virtual environment (CUDA + test deps)
45
+ run: |
46
+ set -euo pipefail
47
+ module purge
48
+ module load cuda/12.8 gcc/12 openmpi/4.1
49
+
50
+ if ! command -v uv >/dev/null 2>&1; then
51
+ echo "error: uv is not available on this runner PATH" >&2
52
+ exit 1
53
+ fi
54
+
55
+ # 'module purge' drops conda from PATH, leaving the system python3 (3.6),
56
+ # which uv refuses to use. Let uv provide the interpreter instead.
57
+ uv python install "${PYTHON_VERSION}"
58
+ uv venv --python "${PYTHON_VERSION}" .venv
59
+ .venv/bin/python --version
60
+
61
+ uv pip install --python .venv/bin/python -r requirements/requirements-dev.txt
62
+ uv pip install --python .venv/bin/python cupy-cuda12x
63
+
64
+ .venv/bin/python -c "import cupy as cp; print('cupy', cp.__version__)"
65
+
66
+ - name: Ensure submit script is executable
67
+ run: chmod +x .github/scripts/submit_freya_gpu_test.sh
68
+
69
+ - name: Submit and monitor Slurm GPU job
70
+ run: .github/scripts/submit_freya_gpu_test.sh
@@ -0,0 +1,47 @@
1
+ name: Count lines of code
2
+ on:
3
+ push:
4
+ branches: [main]
5
+
6
+ jobs:
7
+ lines_of_code_job:
8
+ runs-on: ubuntu-latest
9
+ name: Count lines of code
10
+ # Needed so the built-in GITHUB_TOKEN may push the badge to the badges
11
+ # branch; the default token is read-only when the repository opts into
12
+ # restricted workflow permissions.
13
+ permissions:
14
+ contents: write
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v5
18
+
19
+ # Was shadowmoose/GHA-LoC-Badge@1.0.0, which is pinned at its only release
20
+ # (2020), still declares Node 12 and reports through the retired set-output
21
+ # command -- six of this job's seven deprecation warnings came from it, and
22
+ # there is no newer tag to move to. The script reproduces it exactly: the
23
+ # same files counted, the same total, and a byte-identical SVG.
24
+ - name: Count lines of code
25
+ id: badge
26
+ env:
27
+ LOC_IGNORE: misc|example-scripts|cloudy-codes|requirements|astro_plasma/data|README.md|codemeta.json|LICENSE|poetry.lock|poetry.toml|tests/sample_spectrum.txt|pyproject.toml
28
+ run: |
29
+ python3 .github/scripts/loc_badge.py \
30
+ --ignore "$LOC_IGNORE" \
31
+ --badge ./misc/lines_of_code.svg
32
+
33
+ - name: Print the output
34
+ run: |
35
+ echo "Scanned: ${{ steps.badge.outputs.counted_files }}";
36
+ echo "Line Count: ${{ steps.badge.outputs.total_lines }}";
37
+
38
+ - name: Deploy to badges branch
39
+ uses: peaceiris/actions-gh-pages@v4
40
+ with:
41
+ publish_dir: ./misc
42
+ publish_branch: badges
43
+ # The built-in token, rather than a personal access token that has to be
44
+ # rotated by hand and silently breaks this job when it lapses.
45
+ github_token: ${{ secrets.GITHUB_TOKEN }}
46
+ user_name: github-actions[bot]
47
+ user_email: github-actions[bot]@users.noreply.github.com
@@ -0,0 +1,134 @@
1
+ name: Release
2
+
3
+ # Pushing a release_v* tag is the whole trigger: the tag is the source of truth
4
+ # for the version, and the job refuses to go further if it disagrees with
5
+ # pyproject.toml. Order is deliberate -- build once, rehearse on TestPyPI, then
6
+ # the irreversible upload to PyPI, and only then the GitHub release.
7
+ on:
8
+ push:
9
+ tags: [release_v*]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ build:
16
+ name: Build distributions
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v5
20
+
21
+ - uses: actions/setup-python@v6
22
+ with:
23
+ python-version: '3.11'
24
+
25
+ - name: Check the tag matches the packaged version
26
+ run: |
27
+ tag_version="${GITHUB_REF_NAME#release_v}"
28
+ pkg_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
29
+ echo "tag: $GITHUB_REF_NAME -> $tag_version, pyproject.toml: $pkg_version"
30
+ if [ "$tag_version" != "$pkg_version" ]; then
31
+ echo "::error::Tag $GITHUB_REF_NAME implies version $tag_version but pyproject.toml declares $pkg_version"
32
+ exit 1
33
+ fi
34
+
35
+ - name: Build the sdist and the wheel
36
+ run: |
37
+ python -m pip install --upgrade pip build twine
38
+ python -m build
39
+
40
+ - name: Check the metadata
41
+ run: twine check dist/*
42
+
43
+ # PyPI versions are permanent, so verify the payload before it is uploaded
44
+ # rather than after. The bulk Cloudy tables are downloaded on demand and
45
+ # must never be packaged; the two small tables the code reads at runtime
46
+ # must always be.
47
+ - name: Check the wheel contents
48
+ run: |
49
+ python - <<'PY'
50
+ import glob
51
+ import sys
52
+ import zipfile
53
+
54
+ wheel = glob.glob("dist/*.whl")[0]
55
+ names = zipfile.ZipFile(wheel).namelist()
56
+
57
+ leaked = [n for n in names if n.endswith((".h5", ".h5data"))]
58
+ required = ["astro_plasma/data/solar_GASS10.abn", "astro_plasma/data/cooltable.dat"]
59
+ missing = [n for n in required if n not in names]
60
+
61
+ if leaked:
62
+ print(f"::error::Cloudy data files must not be packaged, found: {leaked[:5]}")
63
+ for name in missing:
64
+ print(f"::error::{name} is read at runtime and is missing from the wheel")
65
+ if leaked or missing:
66
+ sys.exit(1)
67
+ print(f"{wheel}: {len(names)} entries, runtime tables present, no Cloudy data")
68
+ PY
69
+
70
+ - uses: actions/upload-artifact@v7
71
+ with:
72
+ name: distributions
73
+ path: dist/
74
+ if-no-files-found: error
75
+
76
+ testpypi:
77
+ name: Publish to TestPyPI
78
+ needs: build
79
+ runs-on: ubuntu-latest
80
+ environment:
81
+ name: testpypi
82
+ url: https://test.pypi.org/p/astro-plasma
83
+ permissions:
84
+ id-token: write # mints the OIDC token for trusted publishing
85
+ steps:
86
+ - uses: actions/download-artifact@v8
87
+ with:
88
+ name: distributions
89
+ path: dist/
90
+
91
+ - uses: pypa/gh-action-pypi-publish@release/v1
92
+ with:
93
+ repository-url: https://test.pypi.org/legacy/
94
+ # A re-run of a tag that already reached TestPyPI should not fail here;
95
+ # the real PyPI step below is deliberately left strict.
96
+ skip-existing: true
97
+
98
+ pypi:
99
+ name: Publish to PyPI
100
+ needs: testpypi
101
+ runs-on: ubuntu-latest
102
+ environment:
103
+ name: pypi
104
+ url: https://pypi.org/p/astro-plasma
105
+ permissions:
106
+ id-token: write
107
+ steps:
108
+ - uses: actions/download-artifact@v8
109
+ with:
110
+ name: distributions
111
+ path: dist/
112
+
113
+ - uses: pypa/gh-action-pypi-publish@release/v1
114
+
115
+ github-release:
116
+ name: Create the GitHub release
117
+ needs: pypi
118
+ runs-on: ubuntu-latest
119
+ permissions:
120
+ contents: write # gh release create writes a tag-backed release
121
+ steps:
122
+ - uses: actions/download-artifact@v8
123
+ with:
124
+ name: distributions
125
+ path: dist/
126
+
127
+ - name: Create the release and attach the distributions
128
+ env:
129
+ GH_TOKEN: ${{ github.token }}
130
+ run: |
131
+ gh release create "$GITHUB_REF_NAME" dist/* \
132
+ --repo "$GITHUB_REPOSITORY" \
133
+ --title "AstroPlasma ${GITHUB_REF_NAME#release_}" \
134
+ --generate-notes