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

on:
push:
branches: [master, dev]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
lint-format-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install pre-commit
run: python -m pip install -r requirements-dev.txt
- name: Lint, format, and type-check all source files
run: pre-commit run --all-files --show-diff-on-failure
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,5 @@ ENV/

# mypy
.mypy_cache/
# Ruff
.ruff_cache/
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
default_language_version:
python: python3
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.17.0
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
- repo: local
hooks:
- id: mypy
name: mypy (all Python sources)
entry: python scripts/typecheck.py
language: python
additional_dependencies: [mypy==2.4.0]
pass_filenames: false
always_run: true
38 changes: 35 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,38 @@ Ground Rules
2. Make the PR detailed and reference [specific issues](https://github.com/cavalab/srbench/issues) if the PR is meant to address any.
3. **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:

```bash
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
==============================

Expand Down Expand Up @@ -108,10 +140,10 @@ specify a mapping in the `model` function such as:

```python
def model(est, X=None):
mapping = {'x_'+str(i):k for i,k in enumerate(X.columns)}
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)
for k, v in reversed(mapping.items()):
new_model = new_model.replace(k, v)
```

2. The operators/functions in the model are available in [sympy's function set](https://docs.sympy.org/latest/modules/functions/index.html).
Expand Down
5 changes: 5 additions & 0 deletions algorithms/gp-elite/environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
channels:
- conda-forge
dependencies:
# Temporary ceiling: pyarrow has no Python 3.15 Conda package yet.
- python<3.15
2 changes: 1 addition & 1 deletion algorithms/gp-elite/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
gp-elite>=0.5.0
gp-elite>=0.8.0
2 changes: 2 additions & 0 deletions algorithms/keplearn/environment.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
channels:
- conda-forge
dependencies:
# Temporary ceiling: pyarrow has no Python 3.15 Conda package yet.
- python<3.15
- numpy
- sympy
- scikit-learn
Expand Down
5 changes: 5 additions & 0 deletions algorithms/pir/environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
channels:
- conda-forge
dependencies:
# Temporary ceiling: pyarrow has no Python 3.15 Conda package yet.
- python<3.15
41 changes: 23 additions & 18 deletions algorithms/tir/regressor.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
import sys
import os
import pyTIR as tir
from itertools import product
#os.environ["LD_LIBRARY_PATH"] = os.environ["CONDA_PREFIX"] + "/lib"

# os.environ["LD_LIBRARY_PATH"] = os.environ["CONDA_PREFIX"] + "/lib"

hyper_params = [
{
'transfunctions' : ('Id,Tanh,Sin,Cos,Log,Exp,Sqrt',),
'ytransfunctions' : ('Id,Sqrt,Exp,Log,ATan,Tan,Tanh',),
'exponents' : ((-5,5),)
"transfunctions": ("Id,Tanh,Sin,Cos,Log,Exp,Sqrt",),
"ytransfunctions": ("Id,Sqrt,Exp,Log,ATan,Tan,Tanh",),
"exponents": ((-5, 5),),
},
{
'transfunctions' : ('Id,Tanh,Sin,Cos,Log,Exp,Sqrt',),
'ytransfunctions' : ('Id,Sqrt,Exp,Log,ATan,Tan,Tanh',),
'exponents' : ((-2,2),)
"transfunctions": ("Id,Tanh,Sin,Cos,Log,Exp,Sqrt",),
"ytransfunctions": ("Id,Sqrt,Exp,Log,ATan,Tan,Tanh",),
"exponents": ((-2, 2),),
},
]

# Create the pipeline for the model
eval_kwargs = {'scale_x': False, 'scale_y': False}
est = tir.TIRRegressor(npop=1000, ngens=500, pc=0.3, pm=0.7, exponents=(-5,5), error="R^2", alg="MOO")
eval_kwargs = {"scale_x": False, "scale_y": False}
est = tir.TIRRegressor(
npop=1000, ngens=500, pc=0.3, pm=0.7, exponents=(-5, 5), error="R^2", alg="MOO"
)


def pre_train(est, X, y):
"""Adjust settings based on data before training"""
if X.shape[0]*X.shape[1] <= 1000:
if X.shape[0] * X.shape[1] <= 1000:
est.penalty = 0.01


def complexity(e):
return e.len


def model(e, X):
new_model = e.sympy.replace("^","**")
for i,f in reversed(list(enumerate(X.columns))):
new_model = new_model.replace(f'x{i}',f)
new_model = e.sympy.replace("^", "**")
for i, f in reversed(list(enumerate(X.columns))):
new_model = new_model.replace(f"x{i}", f)
return new_model


def get_population(est):
pop = []
for i in range(min(100, len(est.front))-1,-1,-1):
for i in range(min(100, len(est.front)) - 1, -1, -1):
pop.append(est.create_model_from(i))
return pop
return pop


def get_best_solution(est):
return est
69 changes: 39 additions & 30 deletions docs/CompetitionGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ specify a mapping in the `model` function such as:

```python
def model(est, X):
mapping = {'x_'+str(i):k for i,k in enumerate(X.columns)}
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)
for k, v in reversed(mapping.items()):
new_model = new_model.replace(k, v)
```

2. The operators/functions in the model are available in [sympy's function set](https://docs.sympy.org/latest/modules/functions/index.html).
Expand All @@ -92,8 +92,8 @@ If your method uses protected operators (e.g., `my_prot_div(a,b)= a/b if b!=0 el

```python
def model(est, X):
... # any previous pre-processing
new_model = new_model.replace("my_prot_div","/").replace("my_prot_log","log")
... # any previous pre-processing
new_model = new_model.replace("my_prot_div", "/").replace("my_prot_log", "log")
```

### CLI methods
Expand Down Expand Up @@ -146,7 +146,8 @@ In python, one can grab this value as follows:

```python
import os
n_cpus = os.environ['OMP_NUM_THREADS']

n_cpus = os.environ["OMP_NUM_THREADS"]
```

### Time Budget
Expand All @@ -172,18 +173,17 @@ function in `regressor.py` as part of `eval_kwargs`.
As an example, one could define the following in `regressor.py`:

```python
def pre_train_fn(est, X, y):
def pre_train_fn(est, X, y):
"""set max_time in seconds based on length of X."""
if len(X)<=1000:
max_time = 360 - 1 # 1 second of slack
if len(X) <= 1000:
max_time = 360 - 1 # 1 second of slack
else:
max_time = 3600 - 1 # 1 second of slack
max_time = 3600 - 1 # 1 second of slack
est.set_params(max_time)


# pass the function to eval_kwargs
eval_kwargs = {
'pre_train': pre_train_fn
}
eval_kwargs = {"pre_train": pre_train_fn}
```

#### Timeout Handling
Expand All @@ -195,22 +195,25 @@ Nonetheless, timeout handling looks like this:
```python
import signal


class TimeOutException(Exception):
pass


def alarm_handler(signum, frame):
print(f"raising TimeOutException")
raise TimeOutException


def evaluate(est):
"""model evaluation"""
# alarm that sends SIGALRM
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(MAXTIME) # maximum time, defined above
signal.alarm(MAXTIME) # maximum time, defined above
try:
est.fit()
except TimeOutException:
print('evaluate timeout')
print("evaluate timeout")
```

Example approaches are the following:
Expand Down Expand Up @@ -280,39 +283,45 @@ Here is an example of how to do so:

```python
from sklearn.model_selection import GridSearchCV
#from mylibrary import MyMethod

# from mylibrary import MyMethod
from sklearn import linear_model


hyper_params = { 'alpha': (1e-04,0.001,0.01,0.1,1,) }
hyper_params = {
"alpha": (
1e-04,
0.001,
0.01,
0.1,
1,
)
}

# define your base estimator
base_est=linear_model.LassoLars()
base_est = linear_model.LassoLars()

# set est to be a GridSearchCV estimator
est = GridSearchCV(estimator=base_est,
param_grid=hyper_params,
cv=5,
refit=True)
est = GridSearchCV(estimator=base_est, param_grid=hyper_params, cv=5, refit=True)

# set max time w.r.t. hyper-param tuning
num_combos = 5 # five values of alpha
num_cv = 5 # 5 cross-validation splits as above
total_runs = num_combos * num_cv + 1 # +1 because refit=True
num_cv = 5 # 5 cross-validation splits as above
total_runs = num_combos * num_cv + 1 # +1 because refit=True


def pre_train_fn(est, X, y):
def pre_train_fn(est, X, y):
"""set max_time in seconds based on length of X & hyper-parameter tuning"""
if len(X)<=1000:
if len(X) <= 1000:
# account for hyper-parameter tuning, remove 1 extra second of slack to be sure it will terminate on time
max_time = 360 // total_runs - 1
max_time = 360 // total_runs - 1
else:
max_time = 3600 // total_runs - 1
est.set_params(max_time)


# pass the function eval_kwargs
eval_kwargs = {
'pre_train': pre_train_fn
}
eval_kwargs = {"pre_train": pre_train_fn}

# additional definitions
# ...
Expand Down
2 changes: 1 addition & 1 deletion docs/CompetitionGuide2023.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The final score of each competitor will be composed of:
The rank will be calculated for each data set independently such that, with N participants, the k-th ranked competitor (k=1 being the best) will be assigned a value of *N - k + 1*. The final score will be the harmonic mean of all of the scores and each participant will be ranked accordingly:

```python
score = 2*n / sum([ (1/acc[i]) + (1/simpl[i]) for i in (1..n)])
score = 2 * n / sum([(1 / acc[i]) + (1 / simpl[i]) for i in ((1.0).n)])
```

# Track 2 - Interpretability
Expand Down
Loading
Loading