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.
- astro_plasma-1.0.1/.gitattributes +3 -0
- astro_plasma-1.0.1/.github/scripts/loc_badge.py +155 -0
- astro_plasma-1.0.1/.github/scripts/register_freya_runner.sh +81 -0
- astro_plasma-1.0.1/.github/scripts/submit_freya_gpu_test.sh +114 -0
- astro_plasma-1.0.1/.github/workflows/AstroPlasma.yml +29 -0
- astro_plasma-1.0.1/.github/workflows/freya-slurm-gpu.yml +70 -0
- astro_plasma-1.0.1/.github/workflows/lines_of_code.yml +47 -0
- astro_plasma-1.0.1/.github/workflows/release.yml +134 -0
- astro_plasma-1.0.1/.gitignore +153 -0
- astro_plasma-1.0.1/.pre-commit-config.yaml +43 -0
- astro_plasma-1.0.1/LICENSE +21 -0
- astro_plasma-1.0.1/PKG-INFO +550 -0
- astro_plasma-1.0.1/README.md +518 -0
- astro_plasma-1.0.1/astro_plasma/__init__.py +29 -0
- astro_plasma-1.0.1/astro_plasma/core/__init__.py +1 -0
- astro_plasma-1.0.1/astro_plasma/core/compat.py +41 -0
- astro_plasma-1.0.1/astro_plasma/core/constants.py +51 -0
- astro_plasma-1.0.1/astro_plasma/core/data_dir.py +27 -0
- astro_plasma-1.0.1/astro_plasma/core/datasift.py +771 -0
- astro_plasma-1.0.1/astro_plasma/core/download_database.py +210 -0
- astro_plasma-1.0.1/astro_plasma/core/functions.py +67 -0
- astro_plasma-1.0.1/astro_plasma/core/ionization.py +460 -0
- astro_plasma-1.0.1/astro_plasma/core/spectrum.py +162 -0
- astro_plasma-1.0.1/astro_plasma/core/utils.py +257 -0
- astro_plasma-1.0.1/astro_plasma/data/cooltable.dat +8002 -0
- astro_plasma-1.0.1/astro_plasma/data/solar_GASS10.abn +50 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/cddefines.h +1462 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/cddrive.h +582 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/cdstd.h +43 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/cloudyconfig.h +20 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/compile-command +3 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/cpu.h +660 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/hazy_coolingcurve.cpp +308 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/mpi_utilities.h +168 -0
- astro_plasma-1.0.1/cloudy-codes/coolingFunction/version.h +35 -0
- astro_plasma-1.0.1/cloudy-codes/emmSpec/ProgressBar.py +30 -0
- astro_plasma-1.0.1/cloudy-codes/emmSpec/generateCloudyScript-parallel.py +369 -0
- astro_plasma-1.0.1/cloudy-codes/emmSpec/slurm-python-job +2 -0
- astro_plasma-1.0.1/cloudy-codes/emmSpec/slurm-script +18 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/ProgressBar.py +30 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/generate-hashlist.py +36 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/generateIonFraction-parallel.py +187 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/slurm-python-job +2 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cddefines.h +1462 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cddrive.h +582 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cdstd.h +43 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cloudyconfig.h +20 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/compile-script.sh +3 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/cpu.h +660 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/ionization.cpp +245 -0
- astro_plasma-1.0.1/cloudy-codes/ionFrac/src/mpi_utilities.h +168 -0
- astro_plasma-1.0.1/codemeta.json +64 -0
- astro_plasma-1.0.1/example-scripts/ion-frac-Oxygen.txt +62 -0
- astro_plasma-1.0.1/example-scripts/ionization_cloudy_compare.png +0 -0
- astro_plasma-1.0.1/example-scripts/ionization_cloudy_compare.py +146 -0
- astro_plasma-1.0.1/example-scripts/parallel_query.py +54 -0
- astro_plasma-1.0.1/example-scripts/user_guide.ipynb +421 -0
- astro_plasma-1.0.1/misc/1912.00844.pdf +0 -0
- astro_plasma-1.0.1/misc/MillerBreg_OVII.txt +36 -0
- astro_plasma-1.0.1/misc/VirialRad.txt +61 -0
- astro_plasma-1.0.1/misc/aa55_tumlinson_supfig4.pdf +0 -0
- astro_plasma-1.0.1/misc/apjs456058t3_mrt.txt +1078 -0
- astro_plasma-1.0.1/misc/hazy3_06_01_rc1.pdf +0 -0
- astro_plasma-1.0.1/misc/nHval.txt +40 -0
- astro_plasma-1.0.1/pyproject.toml +85 -0
- astro_plasma-1.0.1/requirements/requirements-all.txt +5 -0
- astro_plasma-1.0.1/requirements/requirements-dev.txt +9 -0
- astro_plasma-1.0.1/requirements/requirements-gpu.txt +11 -0
- astro_plasma-1.0.1/requirements/requirements.txt +10 -0
- astro_plasma-1.0.1/tests/sample_spectrum.txt +8228 -0
- astro_plasma-1.0.1/tests/test_astro_palsma_basic.py +192 -0
- astro_plasma-1.0.1/tests/test_astro_palsma_run.py +174 -0
- astro_plasma-1.0.1/tests/test_cuda.py +206 -0
|
@@ -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
|