Contribution Guide
We are happy to accept contributions of methods, as well as updates to the benchmarking framework. Below we specify minimal requirements for contributing a method to this benchmark.
Ground Rules
- In general you should submit pull requests to the dev branch.
- Make the PR detailed and reference specific issues if the PR is meant to address any.
- Please be kind and please be patient. We will be, too.
Code quality
Install the development tools and enable the commit hooks from the repository root:
python -m pip install -r requirements-dev.txt
pre-commit install
pre-commit run --all-files
The hooks run Ruff linting (with safe automatic fixes), Ruff formatting, and mypy.
CI runs the same hooks over the entire repository on every pull request and on
pushes to master and dev. If a hook changes files, review and stage the changes,
then rerun it before committing. Python scripts and Jupyter notebook code cells
are linted and formatted; Markdown Python examples are also formatted. Notebook
outputs are preserved.
Ruff rules and mypy settings live in pyproject.toml. Tool versions are pinned
in .pre-commit-config.yaml; update Ruff’s hook revision and required-version
together when upgrading. The formatter targets Python 3.7 syntax to accommodate
older algorithm environments; development hooks and CI run on Python 3.12.
For type checking alone, install mypy==2.4.0 and run
python scripts/typecheck.py. The runner checks all Python files, including
hyphenated method directories that mypy’s CLI cannot treat as package names.
Mypy checks unannotated function bodies without requiring type annotations
throughout this existing codebase. Algorithm libraries are installed in separate
containers, so third-party imports are treated as Any and site packages are
ignored for consistent results across environments. Python source in notebooks
is covered by Ruff, but is not type-checked by mypy.
How to contribute an SR method
To contribute a symbolic regression method for benchmarking, fork the repo, make the changes listed below, and submit a pull request to the dev branch.
Once your method passes the basic tests and we’ve reviewed it, it will be available as part of srbench.
Please note that the schedule for updating benchmarks is dependent on a lot of factors including availability of computing resources and availability of all our contributors. If you are on a tight schedule, it is better to plan to benchmark your method yourself. You can leverage this code base and previous experimental results to do so.
Requirements
- An open-source method with a scikit-learn compatible API
- Your method should be compatible with Python 3.7 or higher to ensure compatibility with conda-forge.
- If your method uses a random seed, it should have a
random_stateattribute that can be set.
Where your files go
A submission spans two directories, and both are required. This is the single most common thing to get wrong, so it is worth reading closely.
algorithms/<your-method>/ # how to INSTALL your method
├── metadata.yml # required
├── environment.yml # optional
├── requirements.txt # optional
├── install.sh # optional
├── Dockerfile # optional
└── LICENSE # optional
experiment/methods/<your-method>/ # how to CALL your method
├── regressor.py # required
└── __init__.py # required (an empty file)
The split follows from how the benchmark runs.
algorithms/<your-method>/ is copied into your Docker image when it is built, so it holds everything needed to install your method.
experiment/ is mounted into the running container, so regressor.py is read at run time and is never baked into the image.
Use the same directory name in both places.
Note that metadata.yml belongs with the install files in algorithms/, not next to regressor.py.
You can check your layout before opening a PR:
python scripts/check_method_layout.py
CI runs this same check, and it will fail your PR if anything is out of place.
algorithms/<your-method>/
metadata.yml(required): A file describing your submission, following the descriptions in algorithms/feat/metadata.yml. Please fill inname,authors,email,descriptionandurl.LICENSE(optional) A license fileenvironment.yml(optional): a conda environment file that specifies dependencies for your submission. It will be used to update the baseline environment (base_environment.ymlin the root directory). To the extent possible, conda should be used to specify the dependencies you need. If your method is part of conda, great! You can just put that in here and leaveinstall.shblank.requirements.txt(optional): a pypi requirements file. The script will runpip install -r requirements.txtif this file is found, before proceeding.install.sh(optional): a bash script that installs your method. **Note: scripts should not require sudo permissions. The library and include paths should be directed to conda environment; the environmental variable$CONDA_PREFIXspecifies the path to the environment.Dockerfile(optional): we will try to dockerize all algorithms. You can optionally have aDockerfileinside youralgorithms/your-submissionfolder to describe specific images for running your algorithm. If no file is provided, it will usealg-Dockerfilefor your container. You can specify the image as you like, as long as you have as minimal dependences the python packages described inbase_environment.yml, as they are used to run the experiment scripts. See this example in case you want to use a custom image. Notice that there is a workflow to build the docker images and push them to dockerhub.- do not include your source code. use
install.shto pull it from a stable source repository.
experiment/methods/<your-method>/
regressor.py(required): a Python file that defines your method. See experiment/methods/feat/regressor.py for complete documentation. It should contain:est: a sklearn-compatibleRegressorobject.model(est, X=None): a function that returns a sympy-compatible string specifying the final model. It can optionally take the training data as an input argument. See guidance below.eval_kwargs(optional): a dictionary that can specify method-specific arguments toevaluate_model.py. Only these keys are recognized:test_params,max_train_samples,scale_x,scale_y,pre_train,use_dataframe.- We expect your algorithm to have a
max_timeparameter that lets us control the maximum execution time in seconds. When running the experiments in a cluster, we will give extra time to compensate for the overhead of initializing everything, and the maximum time considered is just the fit process. A signalsignal.SIGALRMwill be sent to your process iffit(X, y)exceeds the maximum time, and you can implement strategies to handle this signal. One idea is to store a random initial solution as the best and update it during the execution to ensure theevaluate_model.pyscript will find an equation to work on. - The harness looks for the time limit under any of these attribute names:
max_time,timeout_in_seconds,timeout,stop_time,time_limit. If your estimator exposes none of them, it will simply be killed when it runs long.
__init__.py(required): an empty file, so the harness can import your method.
Testing your submission locally
Build your image and run the same tests CI runs:
bash scripts/make_docker_compose_file.sh # regenerates docker-compose.yml
docker compose build base # the shared base image, needed once
docker compose build <your-method>
docker compose run --rm <your-method> bash test.sh
model compatibility with sympy
In order to check for exact solutions to problems with known, ground-truth models, each SR method returns a model string that can be manipulated in sympy. Assure the returned model meets these requirements:
- The variable names appearing in the model are identical to those in the training data,
X, which is apd.Dataframe. If your method names variables some other way, e.g.[x_0 ... x_m], you can specify a mapping in themodelfunction such as:
def model(est, X=None):
mapping = {"x_" + str(i): k for i, k in enumerate(X.columns)}
new_model = est.model_
for k, v in reversed(mapping.items()):
new_model = new_model.replace(k, v)
- The operators/functions in the model are available in sympy’s function set.