EV Integration¶
Scenario¶
An island power system with growing EV adoption over a 15-year planning horizon. Three vehicle categories — private sedans, commercial delivery vans, and public transit buses — each have distinct charging patterns and V2G participation rates. The optimizer schedules EV charging and determines whether V2G discharge can reduce peak generation needs.
Prerequisites¶
- A working ESFEX installation with Julia backend configured
- A base system configuration YAML file (e.g., from the Getting Started tutorial)
- Demand and availability profile files for the system
S-Curve Growth Model¶
ESFEX models EV fleet growth using a logistic (S-curve) function [53]: slow initial uptake, rapid mid-period growth, and eventual saturation.
The fleet size at year t is calculated as:
Where:
initial_fleet: Number of vehicles at the base year (fromev_quantity)max_adoption: Maximum growth multiplier on the initial fleet (e.g., 30 means up to 30x)growth_rate: Controls how fast adoption accelerates (higher = steeper curve)mid_point_year: Year at which growth is fastest (inflection point)mid_point_fraction: Position of the inflection point within the planning horizon (0.5 = midpoint)
Growth Rate Examples¶
growth_rate |
Adoption Speed | Typical Use Case |
|---|---|---|
| 0.05 | Very slow | Conservative policy, no subsidies |
| 0.12 | Moderate | Gradual transition with incentives |
| 0.20 | Fast | Aggressive policy, strong subsidies |
| 0.30 | Very fast | Mandated transition, ban on ICE sales |
Max Adoption Examples¶
max_adoption |
Meaning | Scenario |
|---|---|---|
| 5 | 5x initial fleet | Low electrification |
| 15 | 15x initial fleet | Moderate electrification |
| 30 | 30x initial fleet | Near-full electrification |
| 50 | 50x initial fleet | Growth beyond current vehicle stock |
Configuration¶
Complete YAML Example¶
systems:
island:
# ... (nodes, generators, batteries, etc.)
# ── EV Categories ──────────────────────────────────────────────
ev_categories:
sedan:
battery_capacity_kwh: 60.0
max_charge_power_kw: 11.0 # Level 2 AC charging
max_discharge_power_kw: 7.0 # V2G discharge limit
charging_power: 11.0 # Used for profile generation (kW)
v2g_power: 7.0 # V2G power per vehicle (kW)
v2g_participation: 0.30 # 30% of parked sedans participate in V2G
charge_efficiency: 0.95
discharge_efficiency: 0.92
min_soc: 0.20 # Never discharge below 20%
max_soc: 0.90 # Never charge above 90%
v2g_compensation: 0.05 # $/kWh compensation to EV owners
max_adoption: 30.0 # Up to 30x initial fleet
growth_rate: 0.12 # Moderate adoption speed
mid_point_fraction: 0.5 # Inflection at year 7-8 of 15
commercial_van:
battery_capacity_kwh: 80.0
max_charge_power_kw: 22.0 # Level 2 fast AC
max_discharge_power_kw: 15.0
charging_power: 22.0
v2g_power: 15.0
v2g_participation: 0.15 # Lower V2G -- commercial use priority
charge_efficiency: 0.94
discharge_efficiency: 0.91
min_soc: 0.25 # Higher minimum for delivery reliability
max_soc: 0.90
v2g_compensation: 0.06
max_adoption: 20.0
growth_rate: 0.10
mid_point_fraction: 0.55 # Slightly later adoption than sedans
electric_bus:
battery_capacity_kwh: 300.0
max_charge_power_kw: 50.0 # DC fast charging at depot
max_discharge_power_kw: 30.0
charging_power: 50.0
v2g_power: 30.0
v2g_participation: 0.50 # High V2G -- centrally managed fleets
charge_efficiency: 0.93
discharge_efficiency: 0.90
min_soc: 0.30 # Higher reserve for public service
max_soc: 0.85
v2g_compensation: 0.08
max_adoption: 15.0 # Public transit grows more slowly
growth_rate: 0.08
mid_point_fraction: 0.45 # Earlier adoption (government-led)
# ── Fleet Quantities (initial, per node) ───────────────────────
ev_quantity:
sedan: [500, 300, 200] # Nodes 0, 1, 2
commercial_van: [80, 50, 30]
electric_bus: [20, 10, 5]
# ── Initial State of Charge ────────────────────────────────────
EV_initial_soc: [0.6, 0.6, 0.6] # 60% SOC at start, per node
# ── 24-Hour Base Charging Patterns ─────────────────────────────
base_patterns:
sedan:
# Private vehicles: charge after work/overnight
- [0.05, 0.05, 0.05, 0.05, 0.05, 0.10, # 00-05: overnight trickle
0.15, 0.10, 0.05, 0.05, 0.05, 0.05, # 06-11: most at work
0.05, 0.05, 0.10, 0.15, 0.20, 0.80, # 12-17: return home
0.90, 0.85, 0.70, 0.50, 0.30, 0.10] # 18-23: evening charge peak
commercial_van:
# Commercial: charge overnight at depot, operate during day
- [0.70, 0.70, 0.65, 0.60, 0.50, 0.20, # 00-05: depot charging
0.05, 0.0, 0.0, 0.0, 0.0, 0.0, # 06-11: on delivery routes
0.0, 0.0, 0.0, 0.0, 0.05, 0.10, # 12-17: partial return
0.20, 0.40, 0.60, 0.70, 0.70, 0.70] # 18-23: return to depot
electric_bus:
# Public transit: charge at night depot, operate dawn-to-dusk
- [0.80, 0.80, 0.80, 0.70, 0.50, 0.10, # 00-05: depot charging
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, # 06-11: morning routes
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, # 12-17: afternoon routes
0.05, 0.10, 0.30, 0.50, 0.70, 0.80] # 18-23: return to depot
Pattern Design Guidelines¶
Base patterns represent the fraction of vehicles plugged in and charging at each hour (0.0 to 1.0):
| Factor | Private Sedans | Commercial Vans | Public Buses |
|---|---|---|---|
| Peak charging hours | 18:00-22:00 | 00:00-05:00 | 00:00-05:00 |
| Operating hours | 07:00-17:00 | 06:00-18:00 | 06:00-21:00 |
| Charging location | Home/work | Depot | Depot |
| Pattern variability | High (diverse users) | Medium | Low (scheduled) |
V2G Configuration Details¶
V2G [51] allows EVs to discharge back to the grid during peak demand or high-price periods [52]. Key parameters:
v2g_participation: Fraction of parked vehicles willing to provide V2G (0.0 to 1.0). Centrally managed fleets (buses) have higher participation than private vehicles.v2g_power: Maximum discharge power per vehicle in kW. Typically lower than charge power to preserve battery health.v2g_compensation: Payment to vehicle owners per kWh discharged ($/kWh). Must be high enough to offset battery degradation costs.min_soc: Minimum state of charge — the optimizer cannot discharge below this level, ensuring vehicles retain enough range for their next trip.discharge_efficiency: Round-trip losses for V2G (typically 90-92%).
The effective V2G capacity at any hour is:
Where availability comes from the complement of the charging pattern (vehicles that are parked but not charging can provide V2G).
Running the Simulation¶
# Run with verbose output
esfex run -c ev_system.yaml --years 15 -v
# Run with specific output directory
esfex run -c ev_system.yaml --years 15 -o results/ev_study/ -v
Expected console output during execution:
[INFO] Loading configuration: ev_system.yaml
[INFO] Generating EV profiles for 3 categories, 3 nodes, 15 years
[INFO] sedan: 1000 initial vehicles, S-curve max_adoption=30.0, growth_rate=0.12
[INFO] commercial_van: 160 initial vehicles, S-curve max_adoption=20.0, growth_rate=0.10
[INFO] electric_bus: 35 initial vehicles, S-curve max_adoption=15.0, growth_rate=0.08
[INFO] EV demand added to total demand (optimizer handles V2G scheduling)
[INFO] Solving master problem (year 1/15)...
...
Results Analysis¶
EV Charging Profiles Over Time¶
import h5py
import numpy as np
with h5py.File("results/ev_study/output.h5", "r") as f:
print(f"{'Year':>6} {'Peak Charge (MW)':>18} {'Peak V2G (MW)':>16} "
f"{'Total Charge (GWh)':>20} {'Total V2G (GWh)':>18}")
print("-" * 82)
for yr in [1, 3, 5, 8, 10, 12, 15]:
grp = f"detailed_results/island/year_{yr:03d}"
charging = f[f"{grp}/ev_charging"][:]
v2g = f[f"{grp}/ev_v2g"][:]
print(f"{yr:>6} {charging.max():>18.1f} {v2g.max():>16.1f} "
f"{charging.sum() / 1000:>20.1f} {v2g.sum() / 1000:>18.1f}")
Expected output (approximate values for a 3-node island):
Year Peak Charge (MW) Peak V2G (MW) Total Charge (GWh) Total V2G (GWh)
----------------------------------------------------------------------------------
1 4.2 1.1 12.3 2.8
3 6.8 1.9 20.1 4.6
5 14.5 4.2 45.2 10.1
8 38.2 11.0 118.5 27.3
10 62.1 18.5 195.0 45.8
12 78.4 23.1 248.2 58.0
15 85.6 25.0 271.5 63.2
Note the S-curve shape: slow growth in years 1-3, rapid acceleration in years 5-10, and saturation by year 12-15.
Fleet Growth Impact on System Demand¶
with h5py.File("results/ev_study/output.h5", "r") as f:
base = f["demand/island/base_demand"][:]
ev = f["demand/island/ev_demand"][:]
total = f["demand/island/total_demand"][:]
print(f"Base peak demand: {base.max():.1f} MW")
print(f"EV peak demand: {ev.max():.1f} MW")
print(f"Total peak demand: {total.max():.1f} MW")
print(f"EV share of peak: {ev.max() / total.max():.1%}")
print(f"EV share of energy: {ev.sum() / total.sum():.1%}")
V2G Revenue and Grid Contribution¶
with h5py.File("results/ev_study/output.h5", "r") as f:
for yr in [5, 10, 15]:
grp = f"detailed_results/island/year_{yr:03d}"
v2g = f[f"{grp}/ev_v2g"][:]
prices = f[f"{grp}/prices"][:]
# V2G revenue (price * discharge volume)
revenue = np.sum(v2g * prices)
# V2G contribution to peak shaving
peak_hour = np.argmax(prices)
v2g_at_peak = v2g[peak_hour] if peak_hour < len(v2g) else 0
print(f"Year {yr}:")
print(f" V2G revenue: ${revenue:>12,.0f}")
print(f" V2G at system peak: {v2g_at_peak:>8.1f} MW")
print(f" Total V2G energy: {v2g.sum():>8.0f} MWh")
Hourly Charging Profile Visualization¶
import numpy as np
with h5py.File("results/ev_study/output.h5", "r") as f:
# Extract a typical day (day 180, summer) for year 10
grp = "detailed_results/island/year_010"
charging = f[f"{grp}/ev_charging"][:]
# Hours 4320 to 4344 = day 180
day_start = 180 * 24
day_end = day_start + 24
day_profile = charging[day_start:day_end]
print("Hour | Charging (MW) | Bar")
print("-" * 50)
for h in range(24):
bar = "#" * int(day_profile[h] / 2)
print(f" {h:02d} | {day_profile[h]:>12.1f} | {bar}")
Cost Implications of EV Integration¶
| Cost Component | Effect of EVs | Direction |
|---|---|---|
| Generation capacity | More peak capacity needed for charging | Increases cost |
| RE investment | More solar/wind to serve EV load | Increases investment |
| Battery storage | V2G reduces need for grid batteries | Decreases investment |
| Fuel cost | Higher demand increases fossil fuel use | Increases cost |
| Curtailment | EVs can absorb excess RE (smart charging) | Decreases waste |
| Grid reinforcement | Higher peak loads stress transmission | Increases cost |
To quantify cost impacts, compare runs with and without EV:
scenarios = {
"No EV": "results/no_ev/output.h5",
"With EV": "results/ev_study/output.h5",
}
for name, path in scenarios.items():
with h5py.File(path, "r") as f:
cost = f["summary_results/objectives"][:].sum()
re_pen = f["summary_results/re_penetration"][-1]
gen_inv = f["summary_results/gen_investment_power"][:].sum()
bat_inv = f["summary_results/bat_investment_power"][:].sum()
print(f"\n{name}:")
print(f" Total NPV: ${cost:>14,.0f}")
print(f" Final RE penetration: {re_pen:>8.1%}")
print(f" Total gen investment: {gen_inv:>8.1f} MW")
print(f" Total bat investment: {bat_inv:>8.1f} MW")
Practical Tips¶
-
Start small: Test with a single EV category (sedan) before adding commercial and bus categories. This simplifies debugging.
-
Avoid double-counting EV demand: When EV optimization is enabled, the optimizer schedules EV charging. Do not manually add
ev_demandtototal_demand— the runner handles this automatically. -
Pattern sensitivity: Charging patterns strongly influence peak demand. Run sensitivity analysis on pattern shapes if uncertain about real-world behavior.
-
V2G economics: Set
v2g_compensationabove the estimated battery degradation cost (typically 0.03-0.08 $/kWh) to ensure realistic participation rates. -
Node distribution: Distribute EV quantities across nodes proportionally to population or vehicle registration data. Uneven distribution reveals localized grid stress.
-
Growth rate calibration: Compare your S-curve parameters against historical EV adoption data from comparable regions. Norway (fast), EU average (moderate), and developing economies (slow) provide useful benchmarks.
-
Computational note: EV profiles are generated once at simulation startup and stored in memory. They do not add significant computation time beyond the additional demand they create.
Key Takeaways¶
- S-curve adoption [53]: Fleet growth starts slow, accelerates in mid-years, then saturates — matching real-world technology diffusion patterns.
- Demand impact: EV charging becomes a significant fraction of total demand by year 10-15, potentially 15-30% of peak load.
- V2G value: Bidirectional charging provides peak shaving and reserve services, partially offsetting the cost of serving EV demand.
- Charging patterns: Off-peak charging (buses at night) is naturally aligned with low-cost hours and can absorb excess renewable generation.
- Storage synergy: EV batteries complement stationary storage, potentially reducing grid-scale battery investment needs by 10-25%.
- Category diversity: Different vehicle types (private, commercial, transit) have complementary charging patterns, smoothing aggregate demand.