Skip to content
Open
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
27 changes: 27 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: lint

# Controls when the workflow will run
on: [push, pull_request, workflow_dispatch]

jobs:
ruff:
runs-on: ubuntu-latest

steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4

# Lint (includes import sorting). Ruff version is pinned so that the rule set
# does not change under our feet when a new ruff release adds default rules.
- name: Ruff check
uses: astral-sh/ruff-action@v3
with:
version: "0.17.3"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my comment on other PR - if version is pinned here should we pin same in the toml so any local environment runs are consistent?

args: "check"

# Fail if any file is not formatted
- name: Ruff format
uses: astral-sh/ruff-action@v3
with:
version: "0.17.3"
args: "format --check --diff"
18 changes: 8 additions & 10 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@ name: pytest

# Controls when the workflow will run
on: [push, pull_request, workflow_dispatch]
# Triggers the workflow on push or pull request events but only for the master branch
# Triggers the workflow on push or pull request events for all branches
# workflow_dispatch allows you to run this workflow manually from the Actions tab

jobs:
test:
# The type of runner and python versions that the job will run on
strategy:
matrix:
os: [windows-latest, ubuntu-20.04, ubuntu-latest]
python-version: ["3.11", '3.12', '3.13']
os: [windows-latest, ubuntu-latest]
python-version: ['3.11', '3.12', '3.13', '3.14']
full-test:
- ${{ contains(github.ref, 'master') || contains(github.ref, 'main') || startsWith(github.ref, 'refs/heads/release') }}
- ${{ github.ref == 'refs/heads/developing' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release') }}
exclude:
- full-test: false
python-version: ["3.10"]
python-version: '3.11'
- full-test: false
python-version: ["3.11"]
- full-test: false
os: "ubuntu-20.04"
python-version: '3.12'
fail-fast: false
runs-on: ${{ matrix.os }}

Expand Down Expand Up @@ -108,8 +106,8 @@ jobs:

# Upload coverage to Codecov. Do it only for one case of the matrix.
- name: Upload coverage to Codecov
# do it only once for python 3.12
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
# do it only once for python 3.13 (runs on every branch)
if: matrix.python-version == '3.13' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v4
with:
verbose: true
Expand Down
5 changes: 4 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ sphinx:
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/source/requirements.txt
- method: pip
path: .
extra_requirements:
- docs
29 changes: 15 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ JADE is a Python V&V (Verification & Validation) framework for nuclear data libr

- **Documentation**: [ReadTheDocs](https://jade-a-nuclear-data-libraries-vv-tool.readthedocs.io/en/latest/)
- **PyPI package**: `jadevv` (imports as `jade`)
- **Python**: ≥ 3.10
- **Python**: ≥ 3.11

---

## Setup

```bash
pip install -e .[dev,ui] # dev tools: pytest, ruff, sphinx; ui: ttkthemes
pip install -e .[dev,ui,docs] # dev: pytest, ruff; ui: ttkthemes; docs: sphinx
```

## Running Tests
Expand Down Expand Up @@ -59,8 +59,8 @@ Every transport-code-extensible concept uses this pattern:

```python
# helper/constants.py
class CODE(str, Enum):
mcnp = "mcnp"; openmc = "openmc"; serpent = "serpent"; d1s = "d1s"; exp = "exp"
class CODE(Enum):
MCNP = "mcnp"; OPENMC = "openmc"; SERPENT = "serpent"; D1S = "d1s"; ACTINV = "actinv"; EXPERIMENT = "exp"

# e.g. run/benchmark.py
class SingleRun(ABC): ...
Expand All @@ -70,13 +70,13 @@ class SingleRunFactory:
def create(code: CODE, ...) -> SingleRun: ...
```

The same pattern applies to `Library`/`LibraryFactory`, `Input`/`InputFactory`, and `AbstractSimOutput`.
The same pattern applies to `Library`/`LibraryFactory`. `Input` subclasses are selected by `CODE` inside `BenchmarkRun` (`run/benchmark.py`), and `AbstractSimOutput` subclasses inside `RawProcessor.__init__()` (`post/raw_processor.py`).

### Config construction: always via named classmethods

```python
run_cfg = RunConfig.from_root(paths_tree) # reads 4 YAML files
run_cfg = RunConfig.from_yamls(env_path, ...) # explicit paths
run_cfg = RunConfig.from_root(paths_tree) # reads 4 YAML files
run_cfg = RunConfig.from_yamls(env_path, ...) # explicit paths
```

Never construct config dataclasses directly.
Expand All @@ -96,7 +96,7 @@ Expensive state (e.g. `GlobalStatus.simulations`) uses `@property` with an inter

### `from __future__ import annotations`

Every module starts with this import.
New modules should start with this import (most existing modules already do).

---

Expand All @@ -114,8 +114,8 @@ See [dev guide](docs/source/dev/add_transport_code.rst) for full details. The fo

1. `src/jade/helper/constants.py` — add tag to `CODE` enum
2. `src/jade/config/run_config.py` — subclass `Library`, register in `LibraryFactory`
3. `src/jade/run/benchmark.py` + `src/jade/run/input.py` — subclass `SingleRun` (implement `_build_command()`, `_get_lib_data_command()`) and `Input` (implement `set_nps()`, `translate()`, `_write()`); register in `SingleRunFactory`
4. `src/jade/post/sim_output.py` — subclass `AbstractSimOutput`; wire into `RawProcessor.__init__()`
3. `src/jade/run/benchmark.py` + `src/jade/run/input.py` — subclass `SingleRun` (implement `_build_command()`, `_get_lib_data_command()`) and `Input` (implement `set_nps()`, `translate()`, `_write()`); register in `SingleRunFactory` and in the input selection of `BenchmarkRun`
4. `src/jade/post/sim_output.py` — subclass `AbstractSimOutput`; wire into `RawProcessor.__init__()` in `src/jade/post/raw_processor.py`

Add tests mirroring the new code's path under `tests/`.

Expand All @@ -135,7 +135,7 @@ Input files are hosted externally (not in this repo): IAEA GitHub for open bench

## Branching & Versioning

- Permanent branches: `master` (stable), `developing` (integration)
- Permanent branches: `developing` (default, integration), `master` (stable)
- Branch types: `Feature/*` and `Benchmark/*` off `developing`; `Release/*` for stabilization; `Hotfix/*` off `master`
- **Semantic versioning**: Major = non-backwards-compatible change (new transport code, class restructure, folder layout). Minor = new features/benchmarks. Patch = bug fixes.
- Every bug fix must include a regression test.
Expand All @@ -145,7 +145,8 @@ Input files are hosted externally (not in this repo): IAEA GitHub for open bench

## CI Matrix

- **OS**: `ubuntu-latest`, `ubuntu-20.04`, `windows-latest`
- **Python**: 3.11, 3.12, 3.13
- Full matrix only on `master`/`release*`; feature branches run latest Python on Ubuntu only.
- **OS**: `ubuntu-latest`, `windows-latest`
- **Python**: 3.11, 3.12, 3.13, 3.14
- Full matrix only on pushes to `developing`/`master`/`release*`; other branches and PRs run only the two latest Python versions (both OSes).
- Coverage is uploaded to Codecov from the Ubuntu / Python 3.13 job.
- OpenMC is installed on Linux only; Windows CI skips OpenMC tests.
1 change: 0 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import sys

sys.path.insert(0, os.path.abspath("../.."))
import importlib.metadata

# -- Project information -----------------------------------------------------

Expand Down
20 changes: 0 additions & 20 deletions docs/source/requirements.txt

This file was deleted.

19 changes: 15 additions & 4 deletions docs/source/usage/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Or if you are using Anaconda:
| ``conda activate jade``

.. warning::
JADE officially supports only Python 3.10, 3.11 and 3.12.
JADE officially supports only Python 3.11, 3.12, 3.13 and 3.14.
It may work with other versions, but they are untested.

Install the JADE package
Expand Down Expand Up @@ -74,10 +74,21 @@ You can either dowload the zip file or clone it using git with:
Moving into the downloaded folder, the user can install the ``jade`` python package
through a local pip install.

It is recommeded to use the '-e' option when installing (editable mode)
and you should also install the additional 'dev' dependencies.
It is recommended to use the '-e' option when installing (editable mode),
together with the optional dependency groups needed for development:

| ``pip install -e .[dev]``
| ``pip install -e .[dev,ui,docs]``

The available optional dependency groups are:

- ``dev``: testing and formatting tools (``pytest``, ``pytest-cov``, ``pytest-xvfb``, ``ruff``).
- ``ui``: GUI dependencies (``ttkthemes``). Skip it on systems without Tkinter.
- ``docs``: tools needed to build this documentation locally (``sphinx`` and its extensions).

All dependencies are declared in ``pyproject.toml``, which is the single source of truth
(the documentation build on Read the Docs also installs from it). Most dependencies are capped
at their current major version (minor version for ``0.x`` packages); when bumping a cap,
make sure the test suite still passes.

If your dev system allows it, install also openmc in the same python environment.

Expand Down
41 changes: 22 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,41 @@ authors = [
]
description = "JADE, a V&V tool for nuclear data libraries and transport codes"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.11"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
"Operating System :: OS Independent",
]
dependencies = [
"xlsxwriter",
"matplotlib",
"aspose-words",
"requests",
"f4enix >= 1.0.0",
"pyyaml",
"seaborn",
"python-gitlab"
"xlsxwriter < 4",
Comment thread
Copilot marked this conversation as resolved.
"python-docx < 2",
"matplotlib < 4",
"requests < 3",
"f4enix >= 1.2.1, < 2",
"pyyaml < 7",
"seaborn < 0.14",
"python-gitlab < 9"
]

[project.optional-dependencies]
dev = [
"pytest",
"pytest-cov",
"pytest-xvfb", # needed for headless display
"pytest < 10",
"pytest-cov < 8",
"pytest-xvfb < 4", # needed for headless display
"ruff", # best formatter that should be used by everybody
"sphinx",
"myst-parser",
"sphinx-book-theme",
"sphinx-copybutton",
"sphinx-togglebutton",
"sphinx-design"
]
docs = [
"sphinx < 10",
"myst-parser < 6",
"sphinx-book-theme < 2",
"sphinx-copybutton < 0.6",
"sphinx-togglebutton < 0.5",
"sphinx-design < 0.8",
"pydata-sphinx-theme < 1",
]
ui = [
"ttkthemes"
"ttkthemes < 4"
]

[project.urls]
Expand Down
8 changes: 4 additions & 4 deletions src/jade/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from jade.config.raw_config import ConfigRawProcessor
from jade.config.run_config import RunConfig, RunMode
from jade.config.status import GlobalStatus
from jade.helper.__optionals__ import TKINTER_AVAIL, OMC_AVAIL
from jade.helper.__optionals__ import OMC_AVAIL, TKINTER_AVAIL

if TKINTER_AVAIL:
from jade.gui.post_config_gui import PostConfigGUI
Expand Down Expand Up @@ -147,8 +147,8 @@ def restore_default_cfg(self, msg: str = ""):
def rmv_runtpe(self):
"""Remove the runtpe files from the simulation folders of mcnp."""
# Walk through the root folder and remove the runtpe files
for pathroot, folder, files in os.walk(self.tree.simulations):
if len(files) > 0 and len(folder) == 0: # only files and no folders
for pathroot, folder, filenames in os.walk(self.tree.simulations):
if len(filenames) > 0 and len(folder) == 0: # only files and no folders
# get the code
codelib = Path(pathroot).parent.parent.name
code, _ = get_code_lib(codelib)
Expand Down Expand Up @@ -203,7 +203,7 @@ def run_benchmarks(self, testing: bool = False) -> list[str] | None:
def continue_run(self, testing: bool = False):
"""Continue the run of the benchmarks that were not completed."""
commands = []
for bench_name, cfg in self.run_cfg.benchmarks.items():
for cfg in self.run_cfg.benchmarks.values():
benchmark = BenchmarkRunFactory.create(
cfg,
self.tree.simulations,
Expand Down
15 changes: 13 additions & 2 deletions src/jade/app/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

IAEA_URL = r"https://github.com/IAEA-NDS/open-benchmarks/archive/main.zip"
RAW_DATA_GITHUB_URL = r"https://github.com/JADE-V-V/JADE-RAW-RESULTS/archive/main.zip"
GITLAB_TIMEOUT = 30 # seconds


def _fetch_from_git(
Expand Down Expand Up @@ -70,7 +71,7 @@ def _extract_zip(zip_content, dest: PathLike) -> PathLike:


def fetch_from_gitlab(
url: str, path: str, branch: str, authorization_token: str = None
url: str, path: str, branch: str, authorization_token: str | None = None
) -> PathLike | bool:
"""Download a repository from GitLab and extract
it to a temporary folder. It can also deal with authentication. Supported
Expand All @@ -90,13 +91,22 @@ def fetch_from_gitlab(
extracted_folder: Pathlike | bool
path to the extracted folder. False if the download was not successful.
"""
gl = gitlab.Gitlab(url=url, private_token=authorization_token, ssl_verify=False)
gl = gitlab.Gitlab(
url=url,
private_token=authorization_token,
ssl_verify=False,
timeout=GITLAB_TIMEOUT,
)
try:
gl.auth()
except gitlab.exceptions.GitlabAuthenticationError as e:
logger.error("Gitlab authentication failed")
logger.error(e)
return False
except gitlab.exceptions.GitlabConnectionError as e:
logger.error(f"Could not connect to {url}")
logger.error(e)
return False

# select the correct project
found = False
Expand Down Expand Up @@ -125,6 +135,7 @@ def _install_data(fetch_folder: str | os.PathLike, install_folder: str | os.Path
os.path.join(install_folder, item),
)


def _install_standard_folder_structure(
extracted_folder: str | os.PathLike,
inputs_root: PathLike,
Expand Down
Loading
Loading