Save and reload a fleet¶
Persist a fleet — or a filtered subset of one — to a self-contained bundle on disk, then reload it later without re-querying the full cache.
ev-flow on PyPI, pev_synth in Python
You pip install ev-flow and then import pev_synth — same
project, two names (the scikit-learn / sklearn convention). The
command-line tool is ev-flow; the importable Python package is
pev_synth.
This tutorial needs a built cache
The wheel ships only the Python code, not the cached fleet data. Build
the local cache once with ev-flow bootstrap before running anything
below — see the Quickstart's
Bootstrap the data cache
step. Fleet.save(...) writes a new bundle from data the cache already
holds, so the cache must exist first.
1. Generate a fleet¶
Start from a residential Bay Area fleet, exactly as in the other tutorials:
import pev_synth as ps
fleet = ps.generate_profiles('residential', n=500, region='bay_area', seed=42)
fleet
# Fleet(profile_type='residential', region='bay_area', n=500, data_root=...)
2. (Optional) take a subset with filter¶
You rarely need to persist a whole fleet. Fleet.filter
returns a new Fleet containing only the EVs matching its predicates
(predicates are AND-combined), and the result saves and reloads just like a full
fleet. Here we keep the battery-electric vehicles with a reasonably fast home
connection:
bevs = fleet.filter(powertrain='BEV', max_charge_kw_gte=7.0)
len(bevs), len(fleet)
# (e.g. 318, 500) -> the subset is smaller
bevs.profile_ids()[:5] # the ev_id ints carried into the subset
Supported filter keys include archetype, powertrain, cluster_z,
evse_brand, evse_connector, and numeric thresholds such as
battery_kwh_gte / battery_kwh_lt and max_charge_kw_gte /
max_charge_kw_lt. An unsupported key raises KeyError listing the valid ones.
3. Save to disk¶
Fleet.save writes a self-contained bundle under a
<profile_type>_fleet/ subdirectory of the path you give it, and returns the
Path to that inner bundle directory. The bundle is a subset of the cache's
parquet artifacts (the static fleet.parquet, the plug-status matrices, the
sessions and mobility tables, plug-in events when present) plus a meta.json
recording the profile type, region, timezone, and the exact ev_ids.
from pathlib import Path
out = bevs.save('exports/bay_area_run')
out
# PosixPath('exports/bay_area_run/residential_fleet')
# What got written:
sorted(p.name for p in Path(out).iterdir())
# ['fleet.parquet', 'meta.json', 'mobility.parquet',
# 'plug_in_events.parquet', 'plug_status_15min.parquet',
# 'plug_status_hourly.parquet', 'sessions.parquet']
One bundle per profile type per directory
The bundle directory is named after the fleet's profile_type
(residential_fleet or workplace_fleet). Saving two fleets of the same
profile type to the same path overwrites the earlier bundle — give each
its own path (or save a residential and a workplace fleet side by side
in one directory, since their bundle names differ).
4. Reload it¶
Fleet.load is the inverse of save. It accepts either
the inner bundle directory (the Path that save returned) or its parent
directory (a convenience — it finds the single *_fleet bundle inside):
# Reload from the inner bundle directory returned by save:
reloaded = ps.Fleet.load(out)
# ...or from the parent path you passed to save (equivalent here):
reloaded = ps.Fleet.load('exports/bay_area_run')
reloaded
# Fleet(profile_type='residential', region='bay_area', n=318, data_root=...)
The reloaded fleet is backed by the saved bundle, not by the original cache, so
it is fully portable: copy the residential_fleet/ directory to another machine
(no PEV_SYNTH_DATA_ROOT, no re-bootstrap) and Fleet.load it there.
5. Confirm the round-trip¶
The reloaded fleet exposes the same API as the original and carries the same EVs. Check that the membership and a downstream query match:
# Same EVs, same order:
reloaded.profile_ids() == bevs.profile_ids()
# True
# A downstream query returns identical results from the saved bundle:
window = ('2001-06-04', '2001-06-11')
orig_kw = bevs.aggregate_load(*window, freq='1h')
new_kw = reloaded.aggregate_load(*window, freq='1h')
new_kw.equals(orig_kw)
# True
# The bundle's metadata is available via the .meta property:
reloaded.meta['profile_type'], reloaded.meta['region'], reloaded.meta['n']
# ('residential', 'bay_area', 318)
What you should see¶
savecreatesexports/bay_area_run/residential_fleet/containing the parquet artifacts and ameta.json, and returns that inner directory's path.Fleet.loadreconstructs aFleetwhoseprofile_type,region, and memberev_idsmatch what you saved —len(reloaded)equals the subset size (318 in the example above, not the original 500).- Re-running a query such as
aggregate_loadagainst the reloaded fleet returns results identical to the original, because the bundle contains the same underlying session and plug-status rows for those EVs.
Why save a subset?
A saved bundle is the natural unit to hand to a collaborator, attach to a
paper's reproducibility artifact, or load in a downstream notebook — it is
small, self-describing (via meta.json), and needs no NHTS download or
cache rebuild to reopen. Subsetting with filter first keeps the bundle to
just the EVs your analysis actually uses.
Next steps¶
- Residential aggregate load — build load curves from a fleet (or a reloaded bundle).
- Comparing regions — save one bundle per region and reload them for a side-by-side comparison.
- The API Reference documents
Fleet.save,Fleet.load,Fleet.filter, and the fullFleet/Profilesurface.