Demand Data¶
File Formats¶
Excel (.xlsx)¶
Single worksheet with pure numeric data:
| Column 0 | Column 1 | Column 2 | |
|---|---|---|---|
| Row 1 | 150.5 | 80.2 | 45.0 |
| Row 2 | 148.3 | 79.1 | 44.5 |
| Row 3 | 145.0 | 77.5 | 43.8 |
| ... | ... | ... | ... |
| Row 8760 | 155.0 | 82.0 | 46.0 |
CSV (.csv)¶
Same structure, comma-separated, no header row:
Format Rules¶
| Rule | Description |
|---|---|
| Rows | One per time step (typically 8,760 rows per year at hourly resolution) |
| Columns | One per node, matching the number of nodes in the system configuration |
| Values | Electrical demand in MW (positive real numbers) |
| No header | Data starts from the first row; no column headers or index column |
| No timestamps | Time alignment is determined by date_start in the configuration |
Example: 3-Node Hourly CSV¶
120.5,65.3,38.2
118.0,63.1,36.9
115.2,61.0,35.5
113.0,59.8,34.2
112.5,58.5,33.8
114.0,60.0,34.5
125.0,68.0,40.0
145.0,78.5,46.2
160.0,86.0,51.0
165.0,89.0,53.0
168.0,90.5,54.2
170.0,91.0,55.0
172.0,92.0,55.5
170.5,91.5,55.0
168.0,90.0,54.0
165.0,88.5,53.0
162.0,87.0,52.0
158.0,85.0,50.5
150.0,81.0,48.0
145.0,78.0,46.0
140.0,75.5,44.5
135.0,73.0,43.0
128.0,69.0,41.0
122.0,66.0,39.0
Multi-Year Demand¶
Provide all years in a single file by vertically concatenating hourly data.
Row Counts¶
| Duration | Standard Year (8,760 h) | Leap Year (8,784 h) |
|---|---|---|
| 1 year | 8,760 rows | 8,784 rows |
| 5 years | 43,800 rows | varies per year |
| 10 years | 87,600 rows | varies per year |
| 25 years | 219,000 rows | varies per year |
date_start determines row-to-year mapping:
Year boundaries are detected automatically from the start date, accounting for leap years.
Single-Year Files with Demand Growth¶
If the file contains only one year but the simulation spans multiple years, the growth rate projects future demand:
Year y demand:
Example with 170 MW base peak and 2% growth:
| Year | Peak Demand (MW) |
|---|---|
| 1 | 170.0 |
| 5 | 184.1 |
| 10 | 203.0 |
| 15 | 224.1 |
| 20 | 247.4 |
| 25 | 273.2 |
Demand Scaling¶
demand_scale applies uniformly at load time, before growth:
Useful for sensitivity analysis without modifying the original data file.
Sectoral Distribution¶
Total demand decomposes into sectors with per-sector criticality for differentiated load shedding.
Configuration¶
electric_demand:
residential:
criticality: 0.7 # 0 = fully flexible, 1 = critical
flexibility: 0.3 # Fraction of demand that can be shifted
industrial:
criticality: 0.9 # Higher criticality = shed last
flexibility: 0.1
commercial:
criticality: 0.5
flexibility: 0.5
sector_distribution:
0: # Node 0
residential: 0.40 # 40% residential
industrial: 0.35 # 35% industrial
commercial: 0.25 # 25% commercial
1: # Node 1
residential: 0.50
industrial: 0.30
commercial: 0.20
2: # Node 2
residential: 0.45
industrial: 0.25
commercial: 0.30
Validation Rules¶
| Rule | Description |
|---|---|
| Fractions must sum to 1.0 per node | A tolerance of 0.01 is applied; if the sum deviates, proportions are normalized automatically |
All sectors defined in electric_demand should appear in sector_distribution |
Missing sectors receive zero allocation |
| Node indices must be valid | If a node index in sector_distribution does not exist, node 0 proportions are used as fallback |
criticality range |
Must be between 0.0 and 1.0 |
flexibility range |
Must be between 0.0 and 1.0 |
How Sectoral Demand Is Used¶
- Priority-based load shedding: High-criticality sectors (industrial) are preserved; low-criticality (commercial lighting) shed first
- Demand flexibility: Flexible fractions can be time-shifted by the optimizer, acting as virtual storage
- Reporting: Unserved energy broken down by sector
EV Demand Integration¶
EV charging demand is generated separately using an S-curve growth model.
When EV Optimization Is Enabled¶
The optimizer decides charging and V2G schedules. EV demand is NOT added to total_demand (avoiding double-counting); EV constraints are included directly in the model.
When EV Optimization Is Disabled¶
EV charging demand is added to base demand:
Profile generation depends on fleet size (ev_quantity), battery specs (ev_categories), driving patterns (base_patterns), and an S-curve growth model. See EV Model for details.
Python API¶
Loading Demand Data¶
from esfex.io.demand import load_demand_data
# Load all years from the file
demand, hours, num_nodes, years, time_index = load_demand_data(
"demand.xlsx",
date_start="01/01/2025 00:00"
)
print(f"Shape: {demand.shape}") # (219000, 3) for 25 years, 3 nodes
print(f"Years: {years}") # [2025, 2026, ..., 2049]
print(f"Peak demand: {demand.max():.1f} MW")
# Load only a specific year (memory efficient)
demand_2030, hours, num_nodes, years, time_index = load_demand_data(
"demand.xlsx",
date_start="01/01/2025 00:00",
year_to_load=2030
)
print(f"Year 2030 shape: {demand_2030.shape}") # (8760, 3)
Creating Sectoral Demand¶
from esfex.io.demand import create_sectoral_demand
sector_distribution = {
0: {"residential": 0.40, "industrial": 0.35, "commercial": 0.25},
1: {"residential": 0.50, "industrial": 0.30, "commercial": 0.20},
}
sectoral = create_sectoral_demand(demand, sector_distribution)
# sectoral = {"residential": array(8760, 2), "industrial": array(8760, 2), ...}
for sector, data in sectoral.items():
print(f"{sector}: mean={data.mean():.1f} MW, peak={data.max():.1f} MW")
Using DemandDataManager for Large Files¶
Converts Excel to HDF5 for faster year-by-year random access:
from esfex.io.demand import DemandDataManager
mgr = DemandDataManager("demand.xlsx", date_start="01/01/2025 00:00")
mgr.prepare_hdf5_storage() # One-time conversion
# Fast year-by-year loading
for year in range(2025, 2050):
demand, hours, num_nodes, time_idx = mgr.load_year_data(year)
print(f"{year}: {hours} hours, peak={demand.max():.1f} MW")
mgr.cleanup() # Remove temporary HDF5 file
Troubleshooting¶
| Error | Cause | Solution |
|---|---|---|
FileNotFoundError: Demand file not found |
The demand_path does not point to a valid file |
Check the file path; it is relative to the YAML config directory |
Shape mismatch: expected N columns |
Number of columns in demand file does not match num_nodes |
Ensure the demand file has exactly one column per node |
Empty demand file |
The file exists but contains no data | Check that the file is not empty and is in the correct format |
Sector proportions sum to X |
sector_distribution fractions do not sum to 1.0 |
Adjust fractions; a warning is logged and values are normalized |
Best Practices¶
-
Consistency: Ensure demand file resolution matches
temporal.resolution_hours. If your demand file is hourly andresolution_hoursis 1, no aggregation is needed. If using sub-hourly demand (e.g., 15-minute), setresolution_hoursaccordingly. -
Units: All demand values must be in MW (megawatts), not kW or GW.
-
Missing data: Do not leave cells empty or use placeholder values like
-1. All values must be non-negative real numbers. -
File size: For 25-year simulations with many nodes, Excel files can become slow to load. Consider using CSV format, which loads significantly faster for large files.