# GRABBO
## Instructions

The following instructions are entirely adapted from the BAxUS GitHub repository and can be followed to run the code.  
Alternatively, you may replace `baxus.py` and `embeddedturbo.py` with `grabbo.py` and `embeddedturbo.py` (but all occurrences of **grabbo** must be renamed to **baxus**).  

For the Humanoid benchmark, parsing support has not yet been implemented, so only `test_humanoid.py` is currently available.  

The complete system code will be released on GitHub after the paper is published.

## Installation

```bash
cd grabbo
pip install .
```

Or:
```bash
cd grabbo
pip install -r requirements.txt
```

## Running Examples

The entry file is `benchmark_runner.py`.

Example run (from source installation):
```bash
python3 benchmark_runner.py -id 100 -td 1 -n 10 -r 1 -m 1000 -f branin2 -a grabbo
```

Example run (from PyPi installation):
```bash
benchmark_runner.py -id 100 -td 1 -n 10 -r 1 -m 1000 -f branin2 -a grabbo
```

---

## Command Line Options

| **Name** | **Shortcut** | **Full argument** | **Default** | **Description** |
|----------|--------------|-------------------|-------------|-----------------|
| Algorithm | `-a` | `--algorithms` | `grabbo` | Choose from: `grabbo`, `embedded_turbo_target_dim`, `embedded_turbo_effective_dim`, `embedded_turbo_2_effective_dim`, `random_search` |
| Function | `-f` | `--functions` | None | Benchmark functions: `lunarlander`, `mnist`, `robotpushing`, `roverplanning`, `hartmann6`, `branin2`, `rosenbrock5`, `rosenbrock10`, `ackley`, `rosenbrock`, `levy`, `dixonprice`, `griewank`, `michalewicz`, `rastrigin`, `bipedalnncontroller`, `acrobotnncontroller`, `svm`, `lasso10`, `mopta08`, `hartmann6in1000_rotated`, `rosenbrock5in1000_rotated` |
| Input dimensionality | `-id` | `--input-dim` | `100` | Input dimension |
| Target dimensionality | `-td` | `--target-dim` | `10` | Initial target dimension |
| Acquisition function | None | `--acquisition-function` | `ts` | Either `ts` (Thompson sampling) or `ei` (Expected improvement) |
| Embedding type | None | `--embedding-type` | `grabbo` | Either `grabbo` or `hesbo` |
| Adjust target dimension | None | `--adjust-initial-target-dimension` | not set | Whether to automatically adjust the initial target dimension |
| Initial samples | `-n` | `--n-init` | target dim + 1 | Number of initial samples |
| Repetitions | `-r` | `--num-repetitions` | `1` | Number of repetitions |
| Max evaluations | `-m` | `--max-evals` | `300` | Maximum number of evaluations |
| Initial trust region length | `-l` | `--initial-baselength` | `0.8` | Initial trust region length |
| Minimum trust region length | `-lmin` | `--min-baselength` | `0.5^7` | Minimum trust region length |
| Maximum trust region length | `-l_max` | `--max-baselength` | `1.6` | Maximum trust region length |
| Noise std | None | `--noise-std` | `0` | Noise standard deviation (only for some benchmarks) |
| Results directory | None | `--results-dir` | `results` | Directory to store results |
| Run description | None | `--run-description` | None | Short description of the run |
| MLE multistart samples | None | `--multistart-samples` | `100` | Number of multistart samples for MLE GD optimization |
| MLE multistart after sample | None | `--multistart-after-sample` | `10` | Number of multistarts after selecting best candidates |
| MLE optimization method | None | `--mle-optimization` | `sample-and-choose-best` | Either `multistart-gd` or `sample-and-choose-best` |
| MLE training steps | None | `--mle-training-steps` | `50` | Number of gradient descent steps for MLE |
| Budget until input dim | None | `--budget-until-input-dim` | `0` | Budget after which GRABBO will roughly reach input dimension |
| Verbose | `-v` | `--verbose` | not set | Print verbose messages |

---

## Custom Functions

To define a custom function, extend `Benchmark` and implement the `__call__` method, e.g.:

```python
from typing import Union, List
import numpy as np
from grabbo.benchmarks.benchmark_function import Benchmark

class Parabula(Benchmark):
    def __init__(self):
        super().__init__(dim=100, ub=10*np.ones(100), lb=-10*np.ones(100), noise_std=0)

    def __call__(self, x: Union[np.ndarray, List[float], List[List[float]]]):
        x = np.array(x)
        if x.ndim == 0: x = np.expand_dims(x, 0)
        if x.ndim == 1: x = np.expand_dims(x, 0)
        assert x.ndim == 2
        return np.sum(x ** 2, axis=1)
```

Running:
```python
from grabbo import GRABBO

grabbo = GRABBO(
    run_dir="results",
    max_evals=100,
    n_init=10,
    f=Parabula(),
    target_dim=2,
    verbose=True,
)

grabbo.optimize()
```

Getting results:
```python
x_raw, y_raw = grabbo.optimization_results_raw()
x_inc, y_inc = grabbo.optimization_results_incumbent()
```
