# 🧩 2.4 Featurization

```{contents}
:depth: 2
```

## 🔰 Tutorial

Run the following in an interpreter of your choice (`pip install ax-platform`).

```python
import numpy as np
import pandas as pd
from ax.service.ax_client import AxClient, ObjectiveProperties
from ax.modelbridge.factory import Models
from ax.modelbridge.generation_strategy import GenerationStep, GenerationStrategy
from ax.core.observation import ObservationFeatures

rng = np.random.default_rng(seed=42)


def branin(x1, x2):
    return (
        (x2 - 5.1 / (4 * np.pi**2) * x1**2 + 5.0 / np.pi * x1 - 6.0) ** 2
        + 10 * (1 - 1.0 / (8 * np.pi)) * np.cos(x1)
        + 10
    )


def featurize_data(x1, x2):
    # An example featurization, but often featurization is non-invertible
    feat1 = (x1 + x2) ** 2
    feat2 = (x1 - x2) ** 2
    return feat1, feat2


# Synthetic training data
train_parameterizations = pd.DataFrame(
    {"x1": np.random.uniform(-5, 10, 10), "x2": np.random.uniform(0, 15, 10)}
)

train_data_feat = pd.DataFrame(
    [
        featurize_data(row["x1"], row["x2"])
        for _, row in train_parameterizations.iterrows()
    ],
    columns=["feat1", "feat2"],
)

train_data_feat["y"] = train_parameterizations.apply(
    lambda row: branin(row["x1"], row["x2"]), axis=1
)

# Generate candidate data
# typically many more candidates (e.g., 1e6) to approximate the continuous space
num_candidates = 10000

candidate_params = pd.DataFrame(
    {
        "x1": np.random.uniform(-5, 10, num_candidates),
        "x2": np.random.uniform(0, 15, num_candidates),
    }
)

candidate_features = pd.DataFrame(
    [featurize_data(row["x1"], row["x2"]) for _, row in candidate_params.iterrows()],
    columns=["feat1", "feat2"],
)

gs = GenerationStrategy(
    steps=[  # skip Sobol generation step
        GenerationStep(model=Models.BOTORCH_MODULAR, num_trials=-1, max_parallelism=3)
    ]
)

# The original parameters, for context
# parameters = [
#     {"name": "x1", "type": "range", "bounds": [-5.0, 10.0]},
#     {"name": "x2", "type": "range", "bounds": [0.0, 15.0]},
# ]

# bounds depend on features, sometimes needs to be estimated / empirical (e.g., calculated based on min/max's from candidates)
featurized_parameters = [
    {"name": "feat1", "type": "range", "bounds": [0.0, 625.0]},
    {"name": "feat2", "type": "range", "bounds": [0.0, 400.0]},
]

ax_client = AxClient(generation_strategy=gs, verbose_logging=False, random_seed=42)
ax_client.create_experiment(
    parameters=featurized_parameters,
    objectives={"y": ObjectiveProperties(minimize=True)},
)

# Add training data to the ax client
for _, row in train_data_feat.iterrows():
    _, trial_index = ax_client.attach_trial(row[["feat1", "feat2"]].to_dict())
    ax_client.complete_trial(trial_index=trial_index, raw_data=row["y"])

# Optimization loop
for _ in range(10):
    ax_client.fit_model()
    model = ax_client.generation_strategy.model

    obs_feat = [
        ObservationFeatures(row[["feat1", "feat2"]].to_dict())
        for _, row in candidate_features.iterrows()
    ]
    acqf_values = np.array(
        model.evaluate_acquisition_function(observation_features=obs_feat)
    )
    best_index = np.argmax(acqf_values)
    best_params = candidate_params.iloc[best_index].to_dict()
    result = branin(best_params["x1"], best_params["x2"])

    best_feat = candidate_features.iloc[best_index].to_dict()
    _, trial_index = ax_client.attach_trial(best_feat)
    ax_client.complete_trial(trial_index=trial_index, raw_data=result)

# Report the optimal composition and associated objective value
best_features, metrics = ax_client.get_best_parameters()
print(best_features)

# Find the original parameters from the best_features using lookup
best_params = candidate_params[
    (candidate_features["feat1"] == best_features["feat1"])
    & (candidate_features["feat2"] == best_features["feat2"])
]
```

:::{grid-item-card} Coding Tutorial
Optimizing MAX Phases with Featurization
+++
```{button-link} https://honegumi.readthedocs.io/en/latest/curriculum/tutorials/featurization/featurization.html
:color: info
:expand:
:click-parent:
Coding Tutorial
```
:::

## 🚀 Quiz

::::{tab-set}
:sync-group: category

:::{tab-item} Sp/Su 2024
:sync: sp2024

https://q.utoronto.ca/courses/370068/assignments/1327555
:::

:::{tab-item} Sp/Su 2025
:sync: sp2025

https://q.utoronto.ca/courses/393348/assignments/1499694?display=full_width_with_nav
:::

::::

## 📄 Assignment

::::{tab-set}
:sync-group: category

:::{tab-item} Sp/Su 2024
:sync: sp2024

https://q.utoronto.ca/courses/370068/assignments/1327556
:::

:::{tab-item} Sp/Su 2025
:sync: sp2025

https://q.utoronto.ca/courses/393348/assignments/1499695?display=full_width_with_nav
:::

::::
