Add official BatterySwapAI example submission
Browse files- Dockerfile +23 -0
- README_example.md +85 -0
- batteryswap_example/planners/best.pickle +0 -0
- batteryswap_example/train.py +234 -0
- requirements.dev.txt +1 -0
- requirements.txt +22 -0
- script.py +42 -0
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Submission will be executed with this image
|
| 3 |
+
FROM huggingface/competitions:latest
|
| 4 |
+
|
| 5 |
+
# Default to running on train split only
|
| 6 |
+
ENV BATTERYSWAP_SPLITS=train
|
| 7 |
+
ENV BATTERYSWAP_SUBMISSION_PATH=submission.csv
|
| 8 |
+
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
|
| 11 |
+
# NOTE: allowed requirements are specified by competition
|
| 12 |
+
# WARNING: Your customizations in requirements.txt will be ignored
|
| 13 |
+
COPY requirements.txt ./
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
+
|
| 16 |
+
# Copy relevant files
|
| 17 |
+
# You can copy more in here, if needed
|
| 18 |
+
# (or just keep it under batteryswap_example/)
|
| 19 |
+
COPY batteryswap_example/ ./batteryswap_example
|
| 20 |
+
COPY script.py ./
|
| 21 |
+
|
| 22 |
+
# Default to making submissions
|
| 23 |
+
CMD python3 script.py
|
README_example.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
# Example Model for BatterySwapAI 2026
|
| 6 |
+
|
| 7 |
+
This repository shows how to create a solution for the
|
| 8 |
+
[BatterySwapAI 2026 challenge](https://www.nora.ai/competitions/batteryswapai/batteryswapai2026.html).
|
| 9 |
+
|
| 10 |
+
It contains working example code that can be submitted as-is,
|
| 11 |
+
along with tools to check the solution before submitting.
|
| 12 |
+
|
| 13 |
+
# Setup
|
| 14 |
+
|
| 15 |
+
## Prerequisites
|
| 16 |
+
|
| 17 |
+
You need to have the following installed
|
| 18 |
+
|
| 19 |
+
- git
|
| 20 |
+
- Python 3.10+
|
| 21 |
+
- Docker
|
| 22 |
+
|
| 23 |
+
## Setting up virtual environment
|
| 24 |
+
|
| 25 |
+
On Linux / Mac OS / Windows Subsystem for Linux (WSL)
|
| 26 |
+
```
|
| 27 |
+
python -m venv venv
|
| 28 |
+
source venv/bin/activate
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
Install dependencies
|
| 32 |
+
```
|
| 33 |
+
pip install -r requirements.txt -r requirements.dev.txt
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Developing
|
| 38 |
+
|
| 39 |
+
## Develop in virtual environment
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
Run the training
|
| 43 |
+
```
|
| 44 |
+
python batteryswap_example/train.py
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
# Making submissions
|
| 48 |
+
|
| 49 |
+
## Submitting trained models
|
| 50 |
+
|
| 51 |
+
Trained models or other output used by submission processing (`script.py`),
|
| 52 |
+
must be committed in the git repository.
|
| 53 |
+
An example is `batteryswap_example/planners/best.pickle`.
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
## Test submissions in Docker - recommended
|
| 57 |
+
|
| 58 |
+
Using Docker allows to have exactly the same software versions as the submissions system.
|
| 59 |
+
|
| 60 |
+
This helps to ensure there are no errors when running in the submission environment.
|
| 61 |
+
|
| 62 |
+
NOTE: this requires around 20 GB+ of disk space.
|
| 63 |
+
|
| 64 |
+
Build Docker image
|
| 65 |
+
```
|
| 66 |
+
docker build -t batteryswapai-2026-example .
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
Make submissions and run evaluation
|
| 70 |
+
```
|
| 71 |
+
docker run --name batteryswapai -v ./dataset:/tmp/data batteryswapai-2026-example bash -c "/app/env/bin/python3 script.py && /app/env/bin/python3 -m batteryswap_public.metric"
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
Copy submission.csv out of container
|
| 75 |
+
```
|
| 76 |
+
docker cp batteryswapai:/app/submission.csv ./submission.csv
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## Create new submission
|
| 80 |
+
|
| 81 |
+
NOTE: remember to commit and push your changes to the HuggingFace model repository.
|
| 82 |
+
|
| 83 |
+
Use `New submission` in the competition application to submit your current code for evaluation.
|
| 84 |
+
|
| 85 |
+
|
batteryswap_example/planners/best.pickle
ADDED
|
Binary file (589 Bytes). View file
|
|
|
batteryswap_example/train.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import pandas
|
| 3 |
+
import numpy
|
| 4 |
+
import pickle
|
| 5 |
+
from typing import Optional, Sequence
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import pathlib
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
from sklearn.dummy import DummyRegressor
|
| 12 |
+
import pandas as pd
|
| 13 |
+
import numpy
|
| 14 |
+
import numpy as np
|
| 15 |
+
import pandas
|
| 16 |
+
from pydantic import Field
|
| 17 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 18 |
+
import structlog
|
| 19 |
+
|
| 20 |
+
from batteryswap_public.interfaces import Planner, RULModel
|
| 21 |
+
from batteryswap_public.utils import load_dataset, iterate_scenarios
|
| 22 |
+
from batteryswap_public.evaluate import evaluate_plan, check_plan_valid
|
| 23 |
+
|
| 24 |
+
log = structlog.get_logger()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class OrderedPlanner(Planner):
|
| 28 |
+
def __init__(self, rul_estimator):
|
| 29 |
+
self.rul_estimator = rul_estimator
|
| 30 |
+
|
| 31 |
+
def plan(self, battery_data, locations, travel_costs, settings):
|
| 32 |
+
|
| 33 |
+
# Remaining Useful Life estimation
|
| 34 |
+
# FIXME: consider balance of over/under-estimation
|
| 35 |
+
percentile = 'p50'
|
| 36 |
+
rul = self.rul_estimator.predict(battery_data)
|
| 37 |
+
rul_days = rul[percentile]
|
| 38 |
+
# convert to a date
|
| 39 |
+
start_time = battery_data.reset_index()['end_time'].max().normalize()
|
| 40 |
+
predict_eol = start_time + pandas.to_timedelta(rul_days, unit='D')
|
| 41 |
+
|
| 42 |
+
loc = locations.copy().set_index('battery')
|
| 43 |
+
loc['eol_time'] = predict_eol
|
| 44 |
+
order = loc.sort_values('eol_time', ascending=True)
|
| 45 |
+
|
| 46 |
+
# Planner
|
| 47 |
+
# Stupid heuristic: Do one swap per day
|
| 48 |
+
# FIXME: take travel distances into account
|
| 49 |
+
# FIXME: take co-location into account
|
| 50 |
+
# FIXME: take daily and weekly limits into account
|
| 51 |
+
days = start_time + pandas.to_timedelta(numpy.arange(len(order)), unit='D')
|
| 52 |
+
plan = pandas.DataFrame({
|
| 53 |
+
'day': days,
|
| 54 |
+
'battery': order.index,
|
| 55 |
+
})
|
| 56 |
+
|
| 57 |
+
check_plan_valid(plan, locations, start_time=start_time)
|
| 58 |
+
|
| 59 |
+
return plan
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class DummyRULModel(RULModel):
|
| 63 |
+
# RUL model that predicts (no information rate)
|
| 64 |
+
# FIXME: make a model that actually uses the data to improve predictions
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
time_col: str = 'end_time',
|
| 69 |
+
group_col: str = 'device_id',
|
| 70 |
+
value_cols: Sequence[str] = ('voltage', 'temperature'),
|
| 71 |
+
quantiles: Sequence[float] = (0.5, ),
|
| 72 |
+
):
|
| 73 |
+
self.time_col = time_col
|
| 74 |
+
self.group_col = group_col
|
| 75 |
+
self.value_cols = list(value_cols)
|
| 76 |
+
self.quantiles = sorted(quantiles)
|
| 77 |
+
self.quantile_cols = [f"p{round(q * 100):02d}" for q in self.quantiles]
|
| 78 |
+
|
| 79 |
+
self.model = None
|
| 80 |
+
self.use_total_elapsed_days = True
|
| 81 |
+
|
| 82 |
+
def _compute_features(self, unit_df: pd.DataFrame) -> np.ndarray:
|
| 83 |
+
unit_df = unit_df.sort_values(self.time_col)
|
| 84 |
+
|
| 85 |
+
all_stats = {}
|
| 86 |
+
# FIXME: actually compute some features
|
| 87 |
+
|
| 88 |
+
feature_names = sorted(all_stats.keys())
|
| 89 |
+
self._feature_names_ = feature_names # stored for inspection/debugging
|
| 90 |
+
return np.array([all_stats[k] for k in feature_names], dtype=float)
|
| 91 |
+
|
| 92 |
+
def _build_feature_matrix(self, timeseries: pd.DataFrame) -> tuple[np.ndarray, list]:
|
| 93 |
+
rows, ids = [], []
|
| 94 |
+
for unit_id, unit_df in timeseries.groupby(self.group_col):
|
| 95 |
+
if len(unit_df) < 2:
|
| 96 |
+
continue
|
| 97 |
+
rows.append(self._compute_features(unit_df))
|
| 98 |
+
ids.append(unit_id)
|
| 99 |
+
return np.vstack(rows), ids
|
| 100 |
+
|
| 101 |
+
def fit(self, timeseries: pd.DataFrame, rul: pd.Series):
|
| 102 |
+
X, ids = self._build_feature_matrix(timeseries)
|
| 103 |
+
y = np.array([rul[unit_id] for unit_id in ids])
|
| 104 |
+
|
| 105 |
+
# FIXME: actually use an estimator that learns
|
| 106 |
+
self.model = DummyRegressor(strategy='median')
|
| 107 |
+
self.model.fit(X, y)
|
| 108 |
+
return self
|
| 109 |
+
|
| 110 |
+
def predict(self, timeseries: pd.DataFrame) -> pd.DataFrame:
|
| 111 |
+
rows, ids = [], []
|
| 112 |
+
for unit_id, unit_df in timeseries.groupby(self.group_col):
|
| 113 |
+
rows.append(self._compute_features(unit_df))
|
| 114 |
+
ids.append(unit_id)
|
| 115 |
+
|
| 116 |
+
X = np.vstack(rows)
|
| 117 |
+
|
| 118 |
+
# every quantile column just gets the single point prediction.
|
| 119 |
+
point_pred = self.model.predict(X)
|
| 120 |
+
preds = {col: point_pred for col in self.quantile_cols}
|
| 121 |
+
|
| 122 |
+
out = pd.DataFrame(preds, index=pd.Index(ids, name=self.group_col))
|
| 123 |
+
return out[self.quantile_cols]
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def train_rul_model(locations, timeseries, eol_times, scenarios, limit_scenarios=None):
|
| 127 |
+
|
| 128 |
+
# Collect training data
|
| 129 |
+
# FIXME: train/validate/test split to estimate generalized predictive performance
|
| 130 |
+
gen = iterate_scenarios(locations, timeseries, eol_times, scenarios)
|
| 131 |
+
cut_dfs = []
|
| 132 |
+
cut_ruls = []
|
| 133 |
+
scenarios_loaded = 0
|
| 134 |
+
for scenario, locs, cut, eol in gen:
|
| 135 |
+
print('load-scenario')
|
| 136 |
+
cut = cut.reset_index()
|
| 137 |
+
cut['device_id'] = cut['device_id'].astype(str) + scenario['name']
|
| 138 |
+
cut = cut.set_index(['device_id', 'end_time'])
|
| 139 |
+
cut_dfs.append(cut)
|
| 140 |
+
plan_start = pandas.Timestamp(scenario['start_time'])
|
| 141 |
+
unobserved_rul = 120
|
| 142 |
+
|
| 143 |
+
# Convert EOL datetimes to RUL in days relative to planning time
|
| 144 |
+
rul_days = (eol - plan_start) / pandas.Timedelta(days=1)
|
| 145 |
+
rul_days.index = pandas.Series(rul_days.index) + scenario['name']
|
| 146 |
+
rul_days = rul_days.fillna(unobserved_rul)
|
| 147 |
+
cut_ruls.append(rul_days)
|
| 148 |
+
|
| 149 |
+
assert set(rul_days.index) == set(cut.index.get_level_values('device_id'))
|
| 150 |
+
|
| 151 |
+
if limit_scenarios is not None:
|
| 152 |
+
if scenarios_loaded > limit_scenarios:
|
| 153 |
+
break
|
| 154 |
+
scenarios_loaded += 1
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# Train a RUL prediction model
|
| 158 |
+
rul_model = DummyRULModel()
|
| 159 |
+
X = pandas.concat(cut_dfs)
|
| 160 |
+
Y = pandas.concat(cut_ruls)
|
| 161 |
+
|
| 162 |
+
print(X.head())
|
| 163 |
+
print(Y.head())
|
| 164 |
+
|
| 165 |
+
rul_model.fit(X, Y)
|
| 166 |
+
|
| 167 |
+
# FIXME: do model selection
|
| 168 |
+
|
| 169 |
+
return rul_model
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
class Config(BaseSettings):
|
| 173 |
+
"""
|
| 174 |
+
Automatically provides command-line argument support for specified fields
|
| 175 |
+
"""
|
| 176 |
+
model_config = SettingsConfigDict(
|
| 177 |
+
env_prefix="",
|
| 178 |
+
cli_parse_args=True,
|
| 179 |
+
cli_ignore_unknown_args=True,
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
dataset_path: Optional[Path] = None
|
| 183 |
+
split : str = 'train'
|
| 184 |
+
|
| 185 |
+
def main():
|
| 186 |
+
cfg = Config()
|
| 187 |
+
|
| 188 |
+
if cfg.dataset_path is None:
|
| 189 |
+
dataset_path = os.environ.get('BATTERYSWAP_DATASET_PATH', None)
|
| 190 |
+
assert dataset_path
|
| 191 |
+
dataset_path = Path(dataset_path)
|
| 192 |
+
else:
|
| 193 |
+
dataset_path = cfg.dataset_path
|
| 194 |
+
|
| 195 |
+
split_path = dataset_path / cfg.split
|
| 196 |
+
locations, timeseries, eol_times, scenarios = load_dataset(split_path)
|
| 197 |
+
|
| 198 |
+
log.info('evaluate-load-data', path=dataset_path)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
# Prediction model training
|
| 202 |
+
#rul_model = DummyRULModel()
|
| 203 |
+
rul_model = train_rul_model(locations, timeseries, eol_times, scenarios, limit_scenarios=1)
|
| 204 |
+
log.info('train-done')
|
| 205 |
+
|
| 206 |
+
log.info('evaluate')
|
| 207 |
+
# Evaluate on the planning scenarios
|
| 208 |
+
gen = iterate_scenarios(locations, timeseries, eol_times, scenarios)
|
| 209 |
+
for scenario, locs, cut, eol in gen:
|
| 210 |
+
scenario_name = scenario['name']
|
| 211 |
+
travel_costs = scenario['travel_costs']
|
| 212 |
+
settings = scenario['settings']
|
| 213 |
+
|
| 214 |
+
planner = OrderedPlanner(rul_model)
|
| 215 |
+
plan = planner.plan(cut, locs, travel_costs, settings)
|
| 216 |
+
|
| 217 |
+
start_time = pandas.Timestamp(scenario['start_time'])
|
| 218 |
+
|
| 219 |
+
transitions, daily, overall = evaluate_plan(plan, locs, travel_costs, settings, eol_times=eol, start_time=start_time)
|
| 220 |
+
|
| 221 |
+
print('scores', scenario_name, overall)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
# Save best planner
|
| 225 |
+
planner = OrderedPlanner(rul_model)
|
| 226 |
+
|
| 227 |
+
planner_path = 'batteryswap_example/planners/best.pickle'
|
| 228 |
+
with open(planner_path, "wb") as f:
|
| 229 |
+
pickle.dump(planner, f)
|
| 230 |
+
print('planner-save', planner_path)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
if __name__ == '__main__':
|
| 234 |
+
main()
|
requirements.dev.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Add your custom dependencies here
|
requirements.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The following packages are provided in the competition
|
| 2 |
+
# NOTE: changing these will *not* affect the competition runtime environment
|
| 3 |
+
# To have packages for local development, use requirements.dev.txt
|
| 4 |
+
pandas>=2.3.0
|
| 5 |
+
plotly>=6.7.0
|
| 6 |
+
pydantic-settings>=2.14.1
|
| 7 |
+
structlog>=25.5.0
|
| 8 |
+
requests>=2.34.2
|
| 9 |
+
joblib>=1.5.3
|
| 10 |
+
fastparquet>=2026.5.0
|
| 11 |
+
pyarrow>=24.0.0
|
| 12 |
+
tqdm>=4.67.3
|
| 13 |
+
scikit-learn>=1.7.0
|
| 14 |
+
huggingface_hub>=1.25.1
|
| 15 |
+
batteryswap_public>=0.1.0
|
| 16 |
+
scipy>=1.15.0
|
| 17 |
+
numpy>=2.2.0
|
| 18 |
+
statsmodels>=0.14.0
|
| 19 |
+
polars>=1.43.0
|
| 20 |
+
ortools>=9.15.0
|
| 21 |
+
lifelines>=0.30.0
|
| 22 |
+
torch>=2.13.0
|
script.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
print('import start')
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import pickle
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
# These utilities must be part of submission repo
|
| 9 |
+
from batteryswap_public.utils import make_submissions
|
| 10 |
+
from batteryswap_public.interfaces import Planner
|
| 11 |
+
|
| 12 |
+
# ensure classes that might be referenced in pickle is imported
|
| 13 |
+
from batteryswap_example.train import *
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def pickle_loader(path : str):
|
| 17 |
+
|
| 18 |
+
def load() -> Planner:
|
| 19 |
+
with open(path, "rb") as f:
|
| 20 |
+
return pickle.load(f)
|
| 21 |
+
return load
|
| 22 |
+
|
| 23 |
+
def main():
|
| 24 |
+
print('main start')
|
| 25 |
+
|
| 26 |
+
# Load a trained model from repo
|
| 27 |
+
default_planner_path = 'batteryswap_example/planners/best.pickle'
|
| 28 |
+
planner_path = Path(os.environ.get('BATTERYSWAP_PLANNER_PATH', default_planner_path))
|
| 29 |
+
loader = pickle_loader(planner_path)
|
| 30 |
+
|
| 31 |
+
# NOTE: On HuggingFace the dataset will be in /tmp/data
|
| 32 |
+
dataset_path = Path(os.environ.get('BATTERYSWAP_DATASET_PATH', '/tmp/data'))
|
| 33 |
+
|
| 34 |
+
splits = os.environ.get('BATTERYSWAP_SPLITS', 'public,private').split(',')
|
| 35 |
+
make_submissions(loader, dataset_path=dataset_path, splits=splits)
|
| 36 |
+
|
| 37 |
+
# NOTE: On HuggingFace the output must be submission.csv
|
| 38 |
+
assert os.path.exists('submission.csv')
|
| 39 |
+
|
| 40 |
+
if __name__ == '__main__':
|
| 41 |
+
main()
|
| 42 |
+
|