Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
90fad7d
Made FieldCoupling more general
Ladme Nov 18, 2025
bc87410
Per-node properties
Ladme Nov 18, 2025
0c21456
Show available types of working directories in qq submit -h
Ladme Nov 18, 2025
9561e41
Better formatting for an unknown qq submit option error
Ladme Nov 18, 2025
83bc04b
Better help for the submission option --walltime
Ladme Nov 18, 2025
59840d2
missing size property correctly intepreted as zero size
Ladme Nov 19, 2025
64f4700
Better default numbers of MPI ranks in run_scripts and specifying MPI…
Ladme Nov 19, 2025
87e275e
Changes to run scripts to changelog
Ladme Nov 19, 2025
ec38ccd
Fixing exports
Ladme Nov 19, 2025
ca66e59
Renaming methods
Ladme Nov 20, 2025
cacc4e5
SubmitterFactory no longer needs a list of supported parameters
Ladme Nov 20, 2025
b92cb82
Getter methods for Submitter
Ladme Nov 20, 2025
2237c63
Added a comment that submitting a qq job is not thread-safe
Ladme Nov 20, 2025
c212cc3
Copying runtime files a bit later after killing a job
Ladme Nov 20, 2025
221e002
Removed the required command line specification in submitter
Ladme Nov 22, 2025
f0f8248
Updated changelog
Ladme Nov 22, 2025
81119cd
Working directories are now automatically fully removed on LUMI and IT4I
Ladme Nov 23, 2025
dd27d0e
Made collecting slurm jobs much faster
Ladme Nov 23, 2025
bdc7c01
Obtaining job's input directory
Ladme Nov 23, 2025
a7cd902
Documenting modules
Ladme Nov 23, 2025
d894ea7
Basic documentation for qq config options
Ladme Nov 24, 2025
91c66c3
Max width and min width for jobs panel
Ladme Nov 24, 2025
47e1d3c
Configurable code for total and other jobs
Ladme Nov 24, 2025
00eb533
Columns to show in the output of qq jobs/stat can now be customized
Ladme Nov 24, 2025
bdc6009
Wording in config comments
Ladme Nov 24, 2025
a9abe08
Read the docs
Ladme Nov 26, 2025
93f1c2b
qq scripts
Ladme Nov 26, 2025
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: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ wheels/

# Codecov
htmlcov/
.coverage
.coverage

# Documentations
docs/
15 changes: 15 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
## Version 0.6.0

### Support for per-node resources
- Number of CPU cores, number of GPUs, the amount of memory and the amount of storage can be now requested per-node using the submission options `ncpus-per-node`, `ngpus-per-node`, `mem-per-node`, and `work-size-per-node`. Per-node properties override per-cpu properties (`mem-per-cpu`, `work-size-per-cpu`) but are overriden by "total" properties (`ncpus`, `ngpus`, `mem`, `work-size`).

### Changes in Gromacs run scripts
- The scripts now by default try to allocate the maximum possible number of MPI ranks.
- Numbers of MPI ranks are now specified per node (in `*_md` scripts) or per client (in `*_re` scripts).

### Bug fixes and minor improvements
- The available types of working directories for the current environment are now shown in the output of `qq submit -h`.
- Fixed a regression from v0.5: missing size property in `qq nodes` is now correctly intepreted as zero size.
- When a job is killed, runtime files are copied to the input directory only after the executed process finishes.
- Changed the way working directories on Karolina and LUMI are created allowing their complete removal.
- Collection of Slurm jobs (which is complicated by Slurm's architecture) is now performed in parallel and is consequently much faster.

### Internal changes
- `Wiper.delete` method has been renamed to `Wiper.wipe`.
- `Killer.terminate` method has been renamed to `Killer.kill`.
- `SubmitterFactory` no longer requires a list of supported parameters and instead loads it itself.
- Added getter methods to `Submitter`.
- `Submitter` no longer requires to provide the "command line". Command line is no longer written into qq info files.

***

## Version 0.5.1

- If no info file is detected when running `qq go`, `qq info`, `qq kill`, `qq sync`, and `qq wipe`, an error message is printed. (This fixes a regression in v0.5.0.)
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "qq"
version = "0.5.1"
version = "0.6.0-dev.2"
description = "A friendly interface to batch processing"
readme = "README.md"
requires-python = ">=3.12"
Expand Down Expand Up @@ -40,6 +40,7 @@ Documentation = "https://ladme.github.io/qq-manual"
[dependency-groups]
dev = [
"codecov>=2.1.13",
"pdoc>=16.0.0",
"pre-commit>=4.3.0",
"pyinstaller>=6.16.0",
"pytest>=8.4.2",
Expand All @@ -49,6 +50,9 @@ dev = [
"ty>=0.0.1a24",
]

[tool.ruff]
extend-include = ["scripts/qq_scripts/*"]

[tool.ruff.lint]
extend-select = [
"F", # Pyflakes rules
Expand All @@ -68,4 +72,4 @@ extend-select = [
"NPY", # Some numpy-specific things
"ARG", # Catch incorrect use of arguments
]
ignore = ["E501"]
ignore = ["E501"]
103 changes: 103 additions & 0 deletions scripts/qq_scripts/gmx-eta
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env -S uv run --script

# Released under MIT License.
# Copyright (c) 2025 Ladislav Bartos and Robert Vacha Lab

"""
Get the estimated time of a Gromacs simulation finishing.
Version 0.2.
Requires `uv`: https://docs.astral.sh/uv
"""

# /// script
# requires-python = ">=3.12"
# dependencies = [
# "qq",
# ]
#
# [tool.uv.sources]
# qq = { git = "https://github.com/Ladme/qq.git", tag = "v0.6.0" }
# ///

import argparse
from datetime import datetime
from pathlib import Path

from rich import print

from qq_lib.batch.interface import BatchMeta
from qq_lib.core.common import format_duration_wdhhmmss, get_info_files
from qq_lib.core.navigator import Navigator
from qq_lib.info import Informer


def get_informer(id: str | None) -> Informer:
"""Get informer for the given job id or for the newest job in the current directory."""
if id:
return Informer.fromJobId(id)
return Informer.fromFile(get_info_files(Path())[-1])


def get_eta_from_content(content: str) -> datetime | None:
"""Get the time at which the Gromacs simulation is expected to finish."""

# find the last line containing the ETA
eta_line = next((s for s in content if "will finish" in s), None)
if not eta_line:
return None

# assuming the time information is stored in the last 5 words
eta = " ".join(eta_line.split()[-5:])

try:
return datetime.strptime(eta, "%a %b %d %H:%M:%S %Y")
except Exception:
return None


def main():
# parse command line options
parser = argparse.ArgumentParser(
"gmx-eta",
description="Get the estimated time of a Gromacs simulation finishing.",
)
parser.add_argument("job_id", nargs="?", help="Job ID. Optional.", default=None)
args = parser.parse_args()

informer = get_informer(args.job_id)
navigator = Navigator.fromInformer(informer)

if (main_node := navigator.getMainNode()) and (work_dir := navigator.getWorkDir()):
BatchSystem = BatchMeta.fromEnvVarOrGuess()
# use the batch system to read the remote file with Gromacs output
# split the lines and reverse the content to read from the end
try:
content = reversed(
BatchSystem.readRemoteFile(
main_node, work_dir / informer.info.stderr_file
).splitlines()
)
except Exception as e:
print(f"No information is available: {e}")
return

# get eta
eta = get_eta_from_content(content)
if eta and datetime.now() <= eta:
print(
f"Simulation will finish in [bright_blue bold]{format_duration_wdhhmmss(eta - datetime.now())}[/bright_blue bold]."
)
elif eta and datetime.now() > eta:
print(
f"Simulation has finished at [bright_green bold]{str(eta)}[/bright_green bold]."
)
else:
print("No information is available.")

else:
print("No information is available: job does not have a working directory.")
return


if __name__ == "__main__":
main()
Loading
Loading