Skip to main content

Research Helpers

License Python Tests Documentation

Research Helpers is a set of utilities for managing code-intensive research projects and their accompanying papers. Includes shared figure styling, LaTeX table generation and rendering, a build pipeline that keeps a paper's generated tables and figures from drifting out of date, submission packaging for arXiv and Zenodo, structured logging, and a parameter-sweep engine for scheduler job arrays.

Read the documentation

Features

  • Generated Tables and Figures: Build the paper's tables and figures from the repository's own data
  • Drift Detection: Detect when the paper and the data differ
  • LaTeX Assembly: Compose tables for LaTeX and also display them in Jupyter
  • Shared Figure Styling: Consistent look across LaTeX and Jupyter
  • Submission Packaging: Automatic preparation and pre-checking for arXiv and Zenodo
  • Parameter Sweeps: Plan a grid, run it as a scheduler job array, resume if necessary, and collect the results
  • Aggregation: Combine repeated runs into confidence intervals, with no dependencies on the cluster side
  • Structured Logging: Console and file output at separate levels, with progress bars and colour output

Installation

The core package has no third-party dependencies. Specifically, the code path that runs on a cluster only depends on the standard library. Planning a sweep, running an array task, checking its status, sizing the array and computing a confidence interval all work on a node with nothing but Python on it. Everything that needs a library is a capability, installed as an extra:

pip install research-helpers[figures]   # matplotlib, seaborn
pip install research-helpers[latex]     # LaTeX table assembly and rendering (stdlib only)
pip install research-helpers[log]       # structlog, colorama, tqdm
pip install research-helpers[sweep]     # pandas, pyarrow (planning, running, and intervals need neither)

Requires Python 3.11 or newer.

Quick Start

Wire up the project

A project's wiring—i.e., where the paper lives, how wide it is, where runs are written—goes in the [tool.research-helpers] section of the pyproject.toml file.

[tool.research-helpers.paper]
main = "tex/paper.tex"
tables-dir = "tex/tables"
figures-dir = "tex/figures"
text-width-pt = 468.0 # as reported by \showthe\textwidth

[tool.research-helpers.figures]
profile = "print"
palette = "colorblind"
dpi = 300

research-helpers doctor shows every setting, the value in force, and where it came from.

Keys and values are described in the configuration reference.

Generate a table from data

import json

from research_helpers.build import TABLES, Registry
from research_helpers.latex import half_up, header, table

tables = Registry(TABLES)


@tables.register('tab:scores')
def scores() -> str:
    """Accuracy on the held-out set."""
    data = json.loads(SCORES_PATH.read_text())
    return table(
        spec='lr',
        header_rows=[[header('System'), header('Accuracy')]],
        body_rows=[[name.title(), half_up(v['accuracy'] * 100)] for name, v in data.items()],
        caption='Accuracy on the held-out set.',
        label='tab:scores',
    )


if __name__ == '__main__':
    raise SystemExit(tables.main())

The label names the file, so this is written to scores.tex and the paper reads it with \input{tables/scores}:

$ python -m demo.tables --install
wrote build/tables/scores.tex
installed tex/tables/scores.tex

Check for drift

$ python -m demo.tables --check
OK: all 1 tables in the paper are current

If the data is changed without rebuilding, the check fails:

$ python -m demo.tables --check
stale, the paper is behind the data: tab:scores
to fix: rebuild and install the tables
$ echo $?
1

For a full example, see adding a generated table.

Style a figure for the page

import matplotlib.pyplot as plt

from research_helpers.figures import apply_style, save

apply_style(profile='print')  # \textwidth across, paper font sizes
fig, ax = plt.subplots()  # the profile's size is already in rcParams
ax.plot(iterations, accuracy, marker='o')
save(fig, 'tex/figures/learning-curve.png')

See details in the figures guide.

Run a parameter sweep

from research_helpers.sweep import Sweep

sweep = Sweep()


@sweep.context
def prepare(manifest, run_dir):
    """Loaded once per array task."""
    return load_corpus(manifest.metadata['corpus'])


@sweep.evaluate
def evaluate(params, corpus):
    """One parameter combination."""
    return {'f1': score(corpus, **params)}


if __name__ == '__main__':
    raise SystemExit(sweep.main())
$ python sweep_demo.py plan --config sweep.toml --run-dir runs/demo --tasks 4
planned 12 combinations over 4 array tasks (~3 per task)
manifest: runs/demo/manifest.json

submit with:
  sbatch --array=1-4 <your sbatch script> runs/demo

The manifest is written once and the tasks read it, so re-submitting the two that were killed reproduces exactly their original slices instead of repartitioning the grid. Resumption is per combination, not per task, so a job that hits its walltime keeps everything it finished. status and run need only the standard library, so nothing needs to be installed to run a progress check from a login node.

Full details are discussed in the sweeps guide.

Package a submission

$ research-helpers arxiv --dry-run
  paper.tex                       43.1 KB
  paper.bbl                       82.1 KB
  tables/scores.tex                0.3 KB
  figures/learning-curve.png      66.3 KB

  4 files, 0.19 MB
  bbl format 3.3 (TeX Live 2025)

ready to upload: select xelatex and TeX Live 2025

(dry run, nothing written)

The target files are keyed by the path they take inside the submission, so what resolves locally will also resolve at the destination. The checks cover filenames arXiv rejects, a .bbl that is missing, stale, or in a format the selected TeX Live will not read, and microtype font expansion under an engine that has none. --tar packs it reproducibly, so re-packing an unchanged submission gives an identical file.

See the submission guide.

Logging

from research_helpers.log import get_logger, setup_logging

setup_logging()
log = get_logger(__name__)

log.info('scoring', corpus='perseus', sentences=18_000)
[INFO    ] 19:41:47 __main__ scoring (corpus=perseus, sentences=18000)

The console and the log file take separate levels. Colour is applied only when the stream is a terminal, so no escape codes are included in a redirected log.

Details in the logging guide.

Documentation

The full documentation includes:

License

The project is licensed under the MIT License, allowing free use, modification, and distribution.

Metadata

Release files for research-helpers 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for research-helpers 1.1.0
File Size Uploaded
research_helpers-1.1.0.tar.gz 88.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for research-helpers 1.1.0
File Interpreter ABI Platform
research_helpers-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 151.0 kB

Release files / research_helpers-1.1.0.tar.gz

Download URL research_helpers-1.1.0.tar.gz
Size 88.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9b5aeea5e395f735b529b1520f2b7db23d10cd8fa8b2b0f3d3ced5dc2f0e97d7
BLAKE2b-256 checksum
How to use checksums
26fd7e912afaf250698591b33debc77e70fd383663597936b878cd66cad81acb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / research_helpers-1.1.0-py3-none-any.whl

Download URL research_helpers-1.1.0-py3-none-any.whl
Size 62.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
36cb81491f78e04fc8f1c6fb7172c915f0fa3b58251174b47a495cbc78c4efc8
BLAKE2b-256 checksum
How to use checksums
dafefbe82e1bc637cbcb1f819bc54ee12c7c8ce959eb5b9a83e299416ea7edaa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page