Model Training#
If you have your own ground truth energy data, you can train a custom RouteE powertrain model.
You'll want to make sure you've installed the proper dependencies that are not installed by default when you do a pip install.
In this example, we'll use the scikit-learn based estimators which you can install by doing:
pip install routee.powertrain[scikit]
RouteE Powertrain v2 ships three trainer pipelines:
SklearnRandomForestTrainer(extra:scikit) — produces anONNXEstimatorviaskl2onnx.NGBoostTrainer(extra:ngboost) — probabilistic estimator that emits both a point estimate and a per-link standard deviation.CNNTrainer(extra:pytorch) — sequence-aware 1D CNN that exports to ONNX with anInputSpecdescribing the sliding window.
We'll use the Random Forest trainer below.
import routee.powertrain as pt
from routee.powertrain.trainers.sklearn_random_forest import SklearnRandomForestTrainer
For demonstration purposes, we'll use a very small set of training data. You can access this dataset yourself here
import pandas as pd
df = pd.read_csv("../../tests/routee-powertrain-test-data/sample_train_data.csv")
df.head()
| speed_mph | grade_dec | miles | gallons_fastsim | trip_id | road_class | |
|---|---|---|---|---|---|---|
| 0 | 7.632068 | -0.008963 | 0.015469 | 0.000813 | 1 | 3 |
| 1 | 6.329613 | -0.047001 | 0.003516 | 0.000149 | 1 | 3 |
| 2 | 12.248512 | 0.000000 | 0.003402 | 0.000074 | 1 | 4 |
| 3 | 23.752604 | -0.000463 | 0.019768 | 0.002194 | 1 | 1 |
| 4 | 46.024926 | -0.004641 | 0.038378 | 0.000970 | 1 | 0 |
This dataframe represents a set of road network links (i.e. roads) in which we've already computed the energy consumption over. In this case, we've use the Fastsim software to simulate a vehicle driving over a high resolution drive cycle and then have aggregated everything up to the link level. We also have link level attributes like average driving speed in mile per hour (speed), road gradient as a decimal (grade), road distance in miles (miles) and road classification as a integer category (road_class). Lastly, we have a trip identifier column (trip_id) which is only 1 in this case, represeting a single trip taken by this vehicle.
Ok, onto setting up the training pipeline.
First, we need to tell the trainer what features we want to use for the internal estimator (a Random Forest in this case).
We define a single FeatureSet that describes all the features the model will be trained on. In this case, we'll use speed_mph and grade_dec.
feature_set = [
pt.DataColumn(name="speed_mph", units="mph"),
pt.DataColumn(name="grade_dec", units="decimal"),
]
features = feature_set
Note that we didn't include the distance column in the feature set. RouteE Powertrain always requires distance information, so we provide a separate designation for it in the training configuration. Let's define our distance column:
distance = pt.DataColumn(name="miles", units="miles")
Now, we need to define our energy target which is gallons of gasoline simualted by Fastsim:
energy_target = pt.DataColumn(
name="gallons_fastsim",
units="gallons_gasoline",
)
We also need to decide how we want to predict the energy. We have two options: "rate" or "raw". "rate" will take our energy values and divide them by the distance column to arrive at and energy rate. Then, the estimator will be trained to predict the rate value (without using distance as a feature) and then the model will multiply the rate value by the incoming link distance to give a final raw energy value. This can be useful in your training data is sparse as it allows the model to be flexible to distance. "raw" will tell the estimator to predict the energy on the link directly, using distance as an explicit feature. This can be more robust for situations where the energy rate on a link might vary with respect to distance but can lead to weird results if there are not a good representation of different distance values in the training dataset. In our case we'll use "rate" since our training data is very sparse.
predict_method = "rate"
Next, it's worth recording where the training data came from. Six months from now, "which FASTSim version produced this?" is a question you'll want the model itself to answer.
training_source is a tagged union — pick the variant that matches how the data was produced, and every field on it is optional, so record what you know and leave the rest. Our sample data came out of a FASTSim simulation, so we use FastSimSource:
training_source = pt.FastSimSource(
# the vehicle in https://github.com/NatLabRockies/fastsim-vehicles
fastsim_vehicle_id="v1/fastsim-3/conv/toyota/camry-4cyl-2wd/2016/base/r1",
# a git tag or commit sha pinning that repo
fastsim_vehicles_ref="v1.2.0",
fastsim_version="3.1.0",
# the pipeline, and the id of the training run that produced this model
pipeline_version="0.4.1",
pipeline_run_id="local-run",
dataset_run_ids=["ptd-2026-07-14-001", "ptd-2026-07-14-002"],
data_sources=["wm1"],
)
All of this lands in the provenance section of the saved metadata.json. None of it feeds the model digest, so you can correct a version or backfill a run id on an already-published model without changing its identity.
Finally, we can build a model configuration that we can pass to the trainer. This will also include things like the vehicle powertrain type and a model name
config = pt.ModelConfig(
vehicle_description="Test Vehicle",
powertrain_type=pt.PowertrainType.ICE,
feature_set=features,
distance=distance,
target=energy_target,
make="test",
model="vehicle",
year=2024,
test_size=0.2,
predict_method=predict_method,
training_source=training_source,
)
Now we build the random forest trainer and give it the desired parameters
trainer = SklearnRandomForestTrainer(
max_depth=10, min_samples_split=10, n_estimators=20, cores=4
)
All trainers have a train method on them which will return a trained vehicle model
test_vehicle = trainer.train(df, config)
With the model trained, we can inspect the errors for each estimator type and energy target (note, it's possible that we could have given multiple energy targets to the trainer, like gasoline and electricity for a plug-in hybrid vehicle)
test_vehicle.metadata.errors
| Estimator Errors | |
| Target | gallons_fastsim |
| Link RMSE | 0.00138 |
| Link Norm RMSE | 0.87020 |
| Link Weighted RPD | 0.61434 |
| Net Error | -0.16459 |
| Actual Dist/Energy | 18.87243 |
| Predicted Dist/Energy | 22.59067 |
| Real World Predicted Dist/Energy | 19.37450 |
| Trip RPD | 0.17935 |
| Trip Weighted RPD | 0.17935 |
| Trip RMSE | 0.00677 |
| Trip Norm RMSE | 0.16459 |
While this training dataset is far too small to draw real conclusions, these metrics can give you an idea of how well the model performed on a holdout test set (20% of the training data as we specificed by the test_size parameter in the configuration.
The provenance we supplied is on the trained model too, alongside the training hyperparameters and the date the model was trained:
test_vehicle.metadata.provenance
Provenance(source=FastSimSource(method=<TrainingMethod.FASTSIM_SIMULATION: 'fastsim_simulation'>, fastsim_vehicle_id='v1/fastsim-3/conv/toyota/camry-4cyl-2wd/2016/base/r1', fastsim_vehicles_ref='v1.2.0', fastsim_version='3.1.0', pipeline_version='0.4.1', pipeline_run_id='local-run', pipeline_repo_ref=None, dataset_run_ids=['ptd-2026-07-14-001', 'ptd-2026-07-14-002'], data_sources=['wm1'], notes=None), training=TrainingConfig(test_size=0.2, validation_size=None, random_seed=42, trip_column='trip_id', trained_date='2026-07-31'))
Now, we can write the model to a .zip archive, .tar.gz archive, or a flat directory (auto-detected from the path's suffix):
test_vehicle.to_file("Test_Vehicle.zip") # ZIP archive
test_vehicle.to_file("Test_Vehicle.tar.gz") # tar archive
test_vehicle.to_file("Test_Vehicle/") # flat directory
The saved artifact contains a metadata.json and a binary estimator file (e.g. model.onnx). Reload it with pt.load_model("Test_Vehicle.zip").