Thank you for considering a contribution. Issues and pull requests are both welcome, whether they fix a bug, improve the documentation or add a feature.
pilotr ships as twin packages, R and Python, from this one repository. The R package lives in r/pilotr/ and the Python package in python/, with the no-code app in app-lite/. All three consume the same portable JSON design specification (documented in spec/). The two packages share a random-number contract so that, given the same specification and seed, they produce bit-identical data wherever they share a maths library, as on Linux. spec/SPEC.md sets out what can differ elsewhere. A change to the generative core therefore usually needs to land in both packages, keeping that contract intact.
Reporting a problem or suggesting a feature
Please open an issue at https://github.com/pablobernabeu/pilotr/issues. A small reproducible example helps a great deal; for a simulation bug, a minimal design specification plus the seed is ideal, along with the output of sessionInfo() (R) or your Python version.
Setting up for development
Working from a clone of the repository, the R package:
pak::pak(c("devtools", "roxygen2", "testthat", "spelling"))
devtools::document("r/pilotr") # regenerate man/ and NAMESPACE after editing roxygen
devtools::test("r/pilotr") # run the test suite
devtools::check("r/pilotr") # a full R CMD check
devtools::build_readme("r/pilotr") # regenerate README.md after editing README.Rmddevtools::check() also runs a spelling test, as CI does, and stops at any word found neither in the British English dictionary nor in r/pilotr/inst/WORDLIST. spelling::spell_check_package("r/pilotr") lists those words. Correct a misspelling where it occurs, and add a name or a term of art to the word list. CRAN and a plain R CMD check skip the test.
r/pilotr/README.md is generated from README.Rmd and is also the website’s home page, so edit the .Rmd and knit it rather than editing the Markdown directly. The cross-language parity table in r/pilotr/vignettes/parity-summary.csv is likewise generated, by python examples/parity_check.py --csv, which needs both demos to have been run first. That table compares CSV files at the precision they are written with, to a tolerance of 1e-6, so it summarises the agreement without proving it. The R site’s favicons in r/pilotr/pkgdown/favicon/ are built by Rscript tools/favicons/build_favicons.R. Run it, not pkgdown::build_favicons(), when the icon changes, since pkgdown’s function would draw them from the logo, whose wordmark cannot be read at favicon sizes.
brms is not a dependency of the package, so its test suite cannot ask brms whether it accepts the code that brms_bridge() and generate_design_analysis() emit. After changing either, run Rscript tools/brms/check_bridge.R with brms installed. It checks every shipped example and a design in each of the two response families no example uses, and it compiles nothing.
The Python package:
The cross-language contract is checked by the harness in tools/parity/. From the repository root, the first two commands below simulate every shipped and adversarial case from the local R and Python sources. The third compares the two dumps value for value, against the allowances in tools/parity/tolerance.json and the recorded hashes in tools/parity/golden.json. The last two check that both validators accept and refuse the same specifications with the same messages, and that both curve solvers agree. The validator check also holds spec/design.schema.json to the validators, for which it needs the jsonschema package.
Rscript tools/parity/run_r.R
python tools/parity/run_py.py
python tools/parity/compare.py
python tools/parity/validate_cross.py
python tools/parity/solve_cross.pyThe parity job in .github/workflows/python-tests.yml runs these and the harness’s other cross-checks. Its Linux cell gates the workflow. Its Windows cell is informational, because there R and Python take exp(), log() and pow() from different maths libraries, and a value that passes through them can differ in its last bits.
Conventions
British spelling throughout. The generative core stays dependency-free in Python (scipy, statsmodels and pandas are optional extras for the power tools), and the R package keeps its Imports minimal. Given the same specification and seed, the two languages produce bit-identical data wherever they share a maths library, and the tools/parity/ harness above must stay green. pilotr handles no credentials and makes no network requests.
Submitting a pull request
Base your work on main, keep the change focused, and add or update tests and documentation alongside the code. A scheduled continuous-integration workflow runs both test suites against the latest released dependency versions, and the website build exercises the vignettes and guides on every push to main.
By contributing you agree that your contribution is licensed under the same MIT licence as the package, and that you will follow the Code of Conduct.
Releasing
The R and Python packages are released separately, so their version numbers can differ. An R release sets Version in r/pilotr/DESCRIPTION. A Python release sets the version in python/pyproject.toml, python/pilotr/__init__.py and extra.version in python/mkdocs.yml together, after which a docs build rewrites python/docs/pilotr.bib for committing. CITATION.cff follows the newer release of the two, with the date it first became public. The README citations give no version. python tools/check_versions.py checks all of this, and CI runs it.
On the day CRAN or PyPI accepts a release, push the exact tree submitted, tag it by name (for example git push origin v0.4.0, never --tags) and publish the GitHub release, which Zenodo archives.