Temperature Models Example

Temperature Models Example#

This example demonstrates how to use models with temperature as a feature in the Routee Powertrain library.

import routee.powertrain as pt
import pandas as pd
import matplotlib.pyplot as plt

To demonstrate the use of temperature models, we will load a standard Tesla Model 3 RWD model (without temperature as a feature) and two temperature models (steady-state and transient) for the same vehicle. We will then predict energy consumption over a sample route at different ambient temperatures (72°F and 32°F) and compare the results.

It's important to understand the distinction between steady-state and transient temperature models:

  • Steady-State Temperature Models: These models assume that the vehicle's thermal conditions have stabilized to a constant state. They are typically used for longer trips where the vehicle has had sufficient time for the control systems to have stabilized the thermal environment. In this example, we will use the steady-state model for the portion of the trip after the first 5 minutes at 32°F.

  • Transient Temperature Models: These models account for the period when the vehicle is still adjusting to the ambient temperature. For example, when a vehicle starts a trip in cold weather and has been sitting outside, it takes some time for the battery and cabin to warm up.

tesla = pt.load_model("tesla/model_3_rwd/2022/rf_c3326385/v1")
tesla_with_temp_steady = pt.load_model(
    "tesla/model_3_rwd/2022/rf_steady_thermal_ab1db342/v1"
)
tesla_with_temp_transient = pt.load_model(
    "tesla/model_3_rwd/2022/rf_transient_thermal_ab1db342/v1"
)
/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/utils/_http.py:403, in hf_raise_for_status(response, endpoint_name)
    402 try:
--> 403     response.raise_for_status()
    404 except HTTPError as e:

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/requests/models.py:1167, in Response.raise_for_status(self)
   1166 if http_error_msg:
-> 1167     raise HTTPError(http_error_msg, response=self)

HTTPError: 404 Client Error: Not Found for url: https://huggingface.co/NatLabRockies/routee-powertrain-model-library/resolve/main/v2/tesla/model_3_rwd/2022/rf_c3326385/v1/metadata.json

The above exception was the direct cause of the following exception:

EntryNotFoundError                        Traceback (most recent call last)
Cell In[2], line 1
----> 1 tesla = pt.load_model("tesla/model_3_rwd/2022/rf_c3326385/v1")
      2 tesla_with_temp_steady = pt.load_model(
      3     "tesla/model_3_rwd/2022/rf_steady_thermal_ab1db342/v1"
      4 )
      5 tesla_with_temp_transient = pt.load_model(
      6     "tesla/model_3_rwd/2022/rf_transient_thermal_ab1db342/v1"
      7 )

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/routee/powertrain/io/load.py:272, in load_model(name_or_path, registry)
    270 if isinstance(name_or_path, (str, Path)):
    271     mid = _resolve_load_target(str(name_or_path), registry)
--> 272     return registry.load(mid)
    274 raise ValueError(
    275     f"Could not load model: {name_or_path}. "
    276     "Provide a valid local file/directory or a valid ModelId/string with a registry."
    277 )

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/routee/powertrain/registry/hf.py:230, in HFRegistry.load(self, model_id)
    228 # Fetch metadata to learn the model filename
    229 meta_path = f"{dir_path}/{METADATA_FILENAME}"
--> 230 meta_bytes = self._fetch_bytes(meta_path)
    231 metadata_dict = json.loads(meta_bytes)
    233 model_filename = _model_filename(metadata_dict)

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/routee/powertrain/registry/hf.py:123, in HFRegistry._fetch_bytes(self, path)
    117 """Download one file from the repository and return its bytes.
    118 
    119 The file is fetched into the local HuggingFace cache, so repeat calls
    120 for the same revision do not re-download.
    121 """
    122 client = self._get_client()
--> 123 local_path = client.hf_hub_download(
    124     repo_id=self.repo_id,
    125     filename=path,
    126     repo_type=self.repo_type,
    127     revision=self.revision,
    128 )
    129 return Path(local_path).read_bytes()

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py:114, in validate_hf_hub_args.<locals>._inner_fn(*args, **kwargs)
    111 if check_use_auth_token:
    112     kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
--> 114 return fn(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/hf_api.py:5467, in HfApi.hf_hub_download(self, repo_id, filename, subfolder, repo_type, revision, cache_dir, local_dir, force_download, proxies, etag_timeout, token, local_files_only, resume_download, force_filename, local_dir_use_symlinks)
   5463 if token is None:
   5464     # Cannot do `token = token or self.token` as token can be `False`.
   5465     token = self.token
-> 5467 return hf_hub_download(
   5468     repo_id=repo_id,
   5469     filename=filename,
   5470     subfolder=subfolder,
   5471     repo_type=repo_type,
   5472     revision=revision,
   5473     endpoint=self.endpoint,
   5474     library_name=self.library_name,
   5475     library_version=self.library_version,
   5476     cache_dir=cache_dir,
   5477     local_dir=local_dir,
   5478     local_dir_use_symlinks=local_dir_use_symlinks,
   5479     user_agent=self.user_agent,
   5480     force_download=force_download,
   5481     force_filename=force_filename,
   5482     proxies=proxies,
   5483     etag_timeout=etag_timeout,
   5484     resume_download=resume_download,
   5485     token=token,
   5486     headers=self.headers,
   5487     local_files_only=local_files_only,
   5488 )

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py:114, in validate_hf_hub_args.<locals>._inner_fn(*args, **kwargs)
    111 if check_use_auth_token:
    112     kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
--> 114 return fn(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/file_download.py:1014, in hf_hub_download(repo_id, filename, subfolder, repo_type, revision, library_name, library_version, cache_dir, local_dir, user_agent, force_download, proxies, etag_timeout, token, local_files_only, headers, endpoint, resume_download, force_filename, local_dir_use_symlinks)
    994     return _hf_hub_download_to_local_dir(
    995         # Destination
    996         local_dir=local_dir,
   (...)
   1011         local_files_only=local_files_only,
   1012     )
   1013 else:
-> 1014     return _hf_hub_download_to_cache_dir(
   1015         # Destination
   1016         cache_dir=cache_dir,
   1017         # File info
   1018         repo_id=repo_id,
   1019         filename=filename,
   1020         repo_type=repo_type,
   1021         revision=revision,
   1022         # HTTP info
   1023         endpoint=endpoint,
   1024         etag_timeout=etag_timeout,
   1025         headers=hf_headers,
   1026         proxies=proxies,
   1027         token=token,
   1028         # Additional options
   1029         local_files_only=local_files_only,
   1030         force_download=force_download,
   1031     )

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/file_download.py:1077, in _hf_hub_download_to_cache_dir(cache_dir, repo_id, filename, repo_type, revision, endpoint, etag_timeout, headers, proxies, token, local_files_only, force_download)
   1073         return pointer_path
   1075 # Try to get metadata (etag, commit_hash, url, size) from the server.
   1076 # If we can't, a HEAD request error is returned.
-> 1077 (url_to_download, etag, commit_hash, expected_size, xet_file_data, head_call_error) = _get_metadata_or_catch_error(
   1078     repo_id=repo_id,
   1079     filename=filename,
   1080     repo_type=repo_type,
   1081     revision=revision,
   1082     endpoint=endpoint,
   1083     proxies=proxies,
   1084     etag_timeout=etag_timeout,
   1085     headers=headers,
   1086     token=token,
   1087     local_files_only=local_files_only,
   1088     storage_folder=storage_folder,
   1089     relative_filename=relative_filename,
   1090 )
   1092 # etag can be None for several reasons:
   1093 # 1. we passed local_files_only.
   1094 # 2. we don't have a connection
   (...)
   1100 # If the specified revision is a commit hash, look inside "snapshots".
   1101 # If the specified revision is a branch or tag, look inside "refs".
   1102 if head_call_error is not None:
   1103     # Couldn't make a HEAD call => let's try to find a local file

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/file_download.py:1550, in _get_metadata_or_catch_error(repo_id, filename, repo_type, revision, endpoint, proxies, etag_timeout, headers, token, local_files_only, relative_filename, storage_folder)
   1548 try:
   1549     try:
-> 1550         metadata = get_hf_file_metadata(
   1551             url=url, proxies=proxies, timeout=etag_timeout, headers=headers, token=token, endpoint=endpoint
   1552         )
   1553     except EntryNotFoundError as http_error:
   1554         if storage_folder is not None and relative_filename is not None:
   1555             # Cache the non-existence of the file

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py:114, in validate_hf_hub_args.<locals>._inner_fn(*args, **kwargs)
    111 if check_use_auth_token:
    112     kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
--> 114 return fn(*args, **kwargs)

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/file_download.py:1467, in get_hf_file_metadata(url, token, proxies, timeout, library_name, library_version, user_agent, headers, endpoint)
   1464 hf_headers["Accept-Encoding"] = "identity"  # prevent any compression => we want to know the real size of the file
   1466 # Retrieve metadata
-> 1467 r = _request_wrapper(
   1468     method="HEAD",
   1469     url=url,
   1470     headers=hf_headers,
   1471     allow_redirects=False,
   1472     follow_relative_redirects=True,
   1473     proxies=proxies,
   1474     timeout=timeout,
   1475 )
   1476 hf_raise_for_status(r)
   1478 # Return

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/file_download.py:283, in _request_wrapper(method, url, follow_relative_redirects, **params)
    281 # Recursively follow relative redirects
    282 if follow_relative_redirects:
--> 283     response = _request_wrapper(
    284         method=method,
    285         url=url,
    286         follow_relative_redirects=False,
    287         **params,
    288     )
    290     # If redirection, we redirect only relative paths.
    291     # This is useful in case of a renamed repository.
    292     if 300 <= response.status_code <= 399:

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/file_download.py:307, in _request_wrapper(method, url, follow_relative_redirects, **params)
    305 # Perform request and return if status_code is not in the retry list.
    306 response = http_backoff(method=method, url=url, **params)
--> 307 hf_raise_for_status(response)
    308 return response

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/huggingface_hub/utils/_http.py:414, in hf_raise_for_status(response, endpoint_name)
    412 elif error_code == "EntryNotFound":
    413     message = f"{response.status_code} Client Error." + "\n\n" + f"Entry Not Found for url: {response.url}."
--> 414     raise _format(EntryNotFoundError, message, response) from e
    416 elif error_code == "GatedRepo":
    417     message = (
    418         f"{response.status_code} Client Error." + "\n\n" + f"Cannot access gated repo for url {response.url}."
    419     )

EntryNotFoundError: 404 Client Error. (Request ID: Root=1-6a6d0a47-34b9fea614ccac8641994961;a9fe20df-06ce-4a12-b969-b784cac7c073)

Entry Not Found for url: https://huggingface.co/NatLabRockies/routee-powertrain-model-library/resolve/main/v2/tesla/model_3_rwd/2022/rf_c3326385/v1/metadata.json.

Load a sample route and prepare it for prediction.

sample_route = pt.load_sample_route()
sample_route["time_minutes"] = (
    sample_route["distance"] / sample_route["speed_mph"]
) * 60
sample_route["cummulative_time_minutes"] = sample_route["time_minutes"].cumsum()
sample_route["cummulative_distance"] = sample_route["distance"].cumsum()

Set the ambient temperature for the route.

sample_route_72F = sample_route.copy()
sample_route_72F["ambient_temp_f"] = 72

sample_route_32F = sample_route.copy()
sample_route_32F["ambient_temp_f"] = 32

For the 32°F route, we will use the transient model for the first 5 minutes and the steady-state model for the remainder of the trip.

sample_route_32F_transient = sample_route_32F[
    sample_route_32F["cummulative_time_minutes"] <= 5
]
sample_route_32F_steady = sample_route_32F[
    sample_route_32F["cummulative_time_minutes"] > 5
]

Predict energy consumption using the different models and ambient temperatures. Each model uses its own configured feature set, so we just need to make sure the input DataFrame contains the columns the model expects.

energy = tesla.predict(sample_route)
energy_with_temp_72F = tesla_with_temp_steady.predict(sample_route_72F)
energy_with_temp_32F_transient = tesla_with_temp_transient.predict(
    sample_route_32F_transient
)
energy_with_temp_32F_steady = tesla_with_temp_steady.predict(sample_route_32F_steady)
energy_with_temp_32F = pd.concat(
    [energy_with_temp_32F_transient, energy_with_temp_32F_steady]
)

Now, we can compare the energy consumption results.

energy["cummulative_energy_kwh"] = energy["kwh"].cumsum()
energy_with_temp_72F["cummulative_energy_kwh"] = energy_with_temp_72F["kwh"].cumsum()
energy_with_temp_32F["cummulative_energy_kwh"] = energy_with_temp_32F["kwh"].cumsum()
plt.plot(
    sample_route["cummulative_distance"],
    energy["cummulative_energy_kwh"],
    label="Tesla without Temperature",
)
plt.plot(
    sample_route["cummulative_distance"],
    energy_with_temp_72F["cummulative_energy_kwh"],
    label="Tesla with Temperature 72F",
)
plt.plot(
    sample_route["cummulative_distance"],
    energy_with_temp_32F["cummulative_energy_kwh"],
    label="Tesla with Temperature 32F",
)
plt.xlabel("Cumulative Distance (miles)")
plt.ylabel("Cumulative Energy (kWh)")
plt.legend()

Notice how the energy consumption for the 32°F route is higher than the other two scenarios, reflecting the increased energy demand in colder temperatures.

Something else to note is that the model that doesn't consider temperature explicitly includes a "real world correction factor" to account for things like temperature on average. This explains why the energy consumption for the 72°F route is slightly lower than the other two scenarios since the model without temperature adjustment is effectively averaging out the impact of temperature. The 72°F condition would be considered the "ideal" case since the vehicle does not have to expand any extra effort to maintain the cabin temperature.

Multi-Vehicle Comparison#

Now let's compare the Tesla Model 3 with other electric vehicles to see how different EVs perform under various temperature conditions. We'll load the Nissan Leaf and Chevrolet Bolt models and compare their energy consumption across different temperatures.

nissan_leaf_steady = pt.load_model(
    "nissan/leaf_30_kwh/2016/rf_steady_thermal_ab1db342/v1"
)
nissan_leaf_transient = pt.load_model(
    "nissan/leaf_30_kwh/2016/rf_transient_thermal_ab1db342/v1"
)

chevy_bolt_steady = pt.load_model(
    "chevrolet/bolt_ev/2020/rf_steady_thermal_ab1db342/v1"
)
chevy_bolt_transient = pt.load_model(
    "chevrolet/bolt_ev/2020/rf_transient_thermal_ab1db342/v1"
)

Temperature Sensitivity Comparison#

Let's compare how each vehicle's energy consumption changes across a range of temperatures (0°F, 15°F, 32°F, 50°F, 72°F, 90°F, 110°F). We'll use steady-state models for this comparison.

# Predict energy for all vehicles at different temperatures
temperatures = [0, 15, 32, 50, 72, 90, 110]
vehicles_data = {
    "Tesla Model 3 RWD": tesla_with_temp_steady,
    "Nissan Leaf 30 kWh": nissan_leaf_steady,
    "Chevrolet Bolt EV": chevy_bolt_steady,
}

total_energy_by_temp = {vehicle: [] for vehicle in vehicles_data.keys()}

for temp in temperatures:
    route_temp = sample_route.copy()
    route_temp["ambient_temp_f"] = temp

    for vehicle_name, model in vehicles_data.items():
        energy_pred = model.predict(route_temp)
        total_energy_by_temp[vehicle_name].append(energy_pred["kwh"].sum())

# Create temperature sensitivity comparison plot
plt.figure(figsize=(10, 6))
for vehicle_name, energies in total_energy_by_temp.items():
    plt.plot(
        temperatures,
        energies,
        marker="o",
        linewidth=2,
        markersize=8,
        label=vehicle_name,
    )

plt.xlabel("Ambient Temperature (°F)")
plt.ylabel("Total Energy Consumption (kWh)")
plt.title("Temperature Sensitivity Comparison Across Vehicles")
plt.legend()
plt.grid(True, alpha=0.3)