enerdatasynth 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- enerdatasynth-0.4.1/LICENSE +21 -0
- enerdatasynth-0.4.1/PKG-INFO +237 -0
- enerdatasynth-0.4.1/README.md +197 -0
- enerdatasynth-0.4.1/pyproject.toml +64 -0
- enerdatasynth-0.4.1/setup.cfg +4 -0
- enerdatasynth-0.4.1/src/enerdatasynth/__init__.py +71 -0
- enerdatasynth-0.4.1/src/enerdatasynth/cli.py +214 -0
- enerdatasynth-0.4.1/src/enerdatasynth/data/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/data/preprocess.py +447 -0
- enerdatasynth-0.4.1/src/enerdatasynth/data/temperature.py +386 -0
- enerdatasynth-0.4.1/src/enerdatasynth/diffusion/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/diffusion/ddpm.py +120 -0
- enerdatasynth-0.4.1/src/enerdatasynth/diffusion/schedules.py +38 -0
- enerdatasynth-0.4.1/src/enerdatasynth/evaluation/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/evaluation/baselines.py +72 -0
- enerdatasynth-0.4.1/src/enerdatasynth/evaluation/calibration.py +140 -0
- enerdatasynth-0.4.1/src/enerdatasynth/evaluation/failure_analysis.py +192 -0
- enerdatasynth-0.4.1/src/enerdatasynth/evaluation/metrics.py +58 -0
- enerdatasynth-0.4.1/src/enerdatasynth/evaluation/results_report.py +123 -0
- enerdatasynth-0.4.1/src/enerdatasynth/generators/__init__.py +13 -0
- enerdatasynth-0.4.1/src/enerdatasynth/generators/base.py +59 -0
- enerdatasynth-0.4.1/src/enerdatasynth/generators/load.py +165 -0
- enerdatasynth-0.4.1/src/enerdatasynth/models/__init__.py +54 -0
- enerdatasynth-0.4.1/src/enerdatasynth/models/denoiser.py +87 -0
- enerdatasynth-0.4.1/src/enerdatasynth/models/transformer.py +96 -0
- enerdatasynth-0.4.1/src/enerdatasynth/sampling/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/sampling/generate.py +91 -0
- enerdatasynth-0.4.1/src/enerdatasynth/sdk.py +281 -0
- enerdatasynth-0.4.1/src/enerdatasynth/training/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/training/sweep.py +224 -0
- enerdatasynth-0.4.1/src/enerdatasynth/training/train.py +183 -0
- enerdatasynth-0.4.1/src/enerdatasynth/utils/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/utils/config.py +183 -0
- enerdatasynth-0.4.1/src/enerdatasynth/visualization/__init__.py +0 -0
- enerdatasynth-0.4.1/src/enerdatasynth/visualization/plots.py +218 -0
- enerdatasynth-0.4.1/src/enerdatasynth.egg-info/PKG-INFO +237 -0
- enerdatasynth-0.4.1/src/enerdatasynth.egg-info/SOURCES.txt +48 -0
- enerdatasynth-0.4.1/src/enerdatasynth.egg-info/dependency_links.txt +1 -0
- enerdatasynth-0.4.1/src/enerdatasynth.egg-info/entry_points.txt +2 -0
- enerdatasynth-0.4.1/src/enerdatasynth.egg-info/requires.txt +22 -0
- enerdatasynth-0.4.1/src/enerdatasynth.egg-info/top_level.txt +1 -0
- enerdatasynth-0.4.1/tests/test_calibration.py +73 -0
- enerdatasynth-0.4.1/tests/test_config.py +102 -0
- enerdatasynth-0.4.1/tests/test_diffusion.py +66 -0
- enerdatasynth-0.4.1/tests/test_generators.py +99 -0
- enerdatasynth-0.4.1/tests/test_metrics.py +38 -0
- enerdatasynth-0.4.1/tests/test_models.py +55 -0
- enerdatasynth-0.4.1/tests/test_preprocessing.py +131 -0
- enerdatasynth-0.4.1/tests/test_public_api.py +117 -0
- enerdatasynth-0.4.1/tests/test_sampling.py +58 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sonakshi Sharma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: enerdatasynth
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: A research SDK for synthetic PJM energy resource profiles (electric load today; a common interface for future resource types)
|
|
5
|
+
Author-email: Sonakshi Sharma <s.sonakshi2006@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: diffusion-model,synthetic-data,electric-load,pjm,time-series,energy,generative-model,research-sdk
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: pandas
|
|
21
|
+
Requires-Dist: torch
|
|
22
|
+
Requires-Dist: scikit-learn
|
|
23
|
+
Requires-Dist: statsmodels
|
|
24
|
+
Requires-Dist: holidays
|
|
25
|
+
Requires-Dist: matplotlib
|
|
26
|
+
Requires-Dist: requests
|
|
27
|
+
Requires-Dist: tqdm
|
|
28
|
+
Requires-Dist: pyarrow
|
|
29
|
+
Requires-Dist: pyyaml
|
|
30
|
+
Provides-Extra: viz
|
|
31
|
+
Requires-Dist: seaborn>=0.13; extra == "viz"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy; extra == "dev"
|
|
37
|
+
Requires-Dist: build; extra == "dev"
|
|
38
|
+
Requires-Dist: twine; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
|
|
43
|
+
# ⚡ enerdatasynth
|
|
44
|
+
|
|
45
|
+
### A research SDK for synthetic PJM energy resource profiles
|
|
46
|
+
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
[](pyproject.toml)
|
|
49
|
+
[](tests/)
|
|
50
|
+
[](MIGRATION.md)
|
|
51
|
+
|
|
52
|
+
**Generate realistic synthetic electric-load profiles from a fitted diffusion model — no ML expertise required.**
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import enerdatasynth
|
|
56
|
+
|
|
57
|
+
load = enerdatasynth.model("pjm_load", zone="PS")
|
|
58
|
+
load.set(month=7, Tmax=86, Tmin=70, weekday=True)
|
|
59
|
+
samples = load.sample(n=100) # (100, 24) — real MW, ready to use
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
[Quick Start](#-quick-start) · [Documentation](#-documentation) · [Model Card](MODEL_CARD.md) · [Benchmark Results](#-benchmark-results) · [Citation](#-citation)
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<br>
|
|
67
|
+
|
|
68
|
+
<div align="center">
|
|
69
|
+
<img src="data/research_history/reports/week7/profiles_headtohead.png" alt="Mean daily load profile: enerdatasynth's transformer baseline vs. classical time-series baselines vs. real data" width="720">
|
|
70
|
+
|
|
71
|
+
<sub>Mean daily profile — the validated transformer architecture (data/research_history/) vs. classical baselines vs. real PJM load. See <a href="#-benchmark-results">Benchmark Results</a> for numbers from the currently-shipped pipeline.</sub>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<br>
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Why this exists
|
|
79
|
+
|
|
80
|
+
Utilities, grid operators, and energy researchers frequently need realistic synthetic load data — for privacy-preserving data sharing, stress-testing forecasting models, or scenario planning — without exposing real customer or grid data. `enerdatasynth` wraps a validated conditional diffusion pipeline behind an SDK that hides every ML implementation detail: you describe a day (season, temperature, calendar type), and get back a realistic profile.
|
|
81
|
+
|
|
82
|
+
## ✨ Features
|
|
83
|
+
|
|
84
|
+
- **A fluent, implementation-hiding API** — `model()` → `.set()` → `.sample()`. No tensors, no checkpoint paths, no architecture classes ever surface.
|
|
85
|
+
- **A validated transformer architecture** — selected via a controlled 4-way architecture comparison (MLP/CNN/attention/transformer), not the first thing that was tried. See [Research Summary](#-research-summary).
|
|
86
|
+
- **A corrected diffusion sampler** — an earlier DDIM-style sampler was found to silently corrupt every result it touched; the fix is the only sampler shipped today.
|
|
87
|
+
- **Automatic bias calibration** — a systematic energy-error offset, diagnosed and corrected, applied automatically at generation time.
|
|
88
|
+
- **A common generator interface** (`BaseGenerator`) designed so a future resource type (e.g. wind) can be added through the same SDK contract — see [Extending](docs/extending.md).
|
|
89
|
+
- **Config-driven, YAML-first** hyperparameters (`data/configs/`), a real test suite (`tests/`, ~2–20s), and a full audit trail of every design decision ([MIGRATION.md](MIGRATION.md)) — nothing here is asserted without a file backing it.
|
|
90
|
+
|
|
91
|
+
## 🔌 Supported Generators
|
|
92
|
+
|
|
93
|
+
| Resource | Status | Zones | Entry point |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| Electric load | ✅ Available | `PS` (PJM/PSE&G, NJ) | `enerdatasynth.model("pjm_load", zone="PS")` |
|
|
96
|
+
| Wind | 🧩 Interface-ready, not implemented | — | See [`docs/extending.md`](docs/extending.md) — no wind data, model, or results exist in this project |
|
|
97
|
+
|
|
98
|
+
## 📦 Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install . # from a local clone
|
|
102
|
+
# or, once built:
|
|
103
|
+
pip install dist/*.whl
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Both install paths — plus `pip install -e .` for development — are verified end-to-end (see [MIGRATION.md](MIGRATION.md) for what "verified" means here, including a real packaging bug that was only caught by actually installing the built wheel).
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install ".[viz,dev]" # + seaborn, + pytest/ruff/mypy/build/twine
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 🚀 Quick Start
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import enerdatasynth
|
|
116
|
+
|
|
117
|
+
load = enerdatasynth.model("pjm_load", zone="PS")
|
|
118
|
+
load.set(month=7, Tmax=86, Tmin=70, weekday=True, holiday=False)
|
|
119
|
+
samples = load.sample(n=100, seed=0) # (100, 24) ndarray, MW
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That's the whole interface for generation. Training a new model, if you want to reproduce or extend the results below:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
enerdatasynth preprocess # raw CSVs -> tensors
|
|
126
|
+
enerdatasynth train # transformer, validated defaults
|
|
127
|
+
enerdatasynth calibrate --run-id transformer_T1000_H64_L2
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
See [`docs/quickstart.md`](docs/quickstart.md) for the full walkthrough and [`examples/`](examples/) for runnable scripts.
|
|
131
|
+
|
|
132
|
+
## 🏗️ Architecture
|
|
133
|
+
|
|
134
|
+
```mermaid
|
|
135
|
+
flowchart LR
|
|
136
|
+
A[Raw PJM load CSVs\n+ NOAA temperature] --> B[Preprocessing\nclean · pivot · normalize]
|
|
137
|
+
B --> C["Conditioning vector z\n(month, dow, holiday, Tmax, Tmin, spread)"]
|
|
138
|
+
C --> D["Transformer denoiser\n(DDPM, corrected ancestral sampler)"]
|
|
139
|
+
D --> E[Scalar bias\ncalibration]
|
|
140
|
+
E --> F["ResourceModel.sample()\nreal MW output"]
|
|
141
|
+
|
|
142
|
+
style D fill:#4c6ef5,color:#fff
|
|
143
|
+
style E fill:#f59f00,color:#fff
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Full detail — why transformer over MLP/CNN/attention, why the ancestral sampler over DDIM, why calibration is scalar not per-hour — is in [`docs/architecture.md`](docs/architecture.md), with every claim traced to a specific result file.
|
|
147
|
+
|
|
148
|
+
## 📊 Evaluation Pipeline
|
|
149
|
+
|
|
150
|
+
Every comparison in this project uses the same 7-metric suite (`Hourly`, `FirstDiff`, `ACF`, `RMSE`, `Peak`, `Energy`, `Ramp` — all lower-is-better, defined in [`docs/evaluation.md`](docs/evaluation.md)), computed by `enerdatasynth.evaluation.metrics.compute_metrics`. The same suite backs the automatic failure-mode detector (`evaluation.failure_analysis`) and the benchmark script below.
|
|
151
|
+
|
|
152
|
+
<div align="center">
|
|
153
|
+
<img src="data/reports/figures/hourly_distribution_comparison_T100_H256_L4.png" alt="Hourly distribution comparison, real vs generated, currently-shipped model" width="600">
|
|
154
|
+
<br><sub>Per-hour distribution, real vs. generated — the currently auto-discovered default model (see Benchmark Results).</sub>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
## 🏆 Benchmark Results
|
|
158
|
+
|
|
159
|
+
Run yourself: `python examples/run_benchmark.py --run-id <run_id>`. Numbers below are a real run against the full 600-day validation set, using the model `enerdatasynth.model("pjm_load")` actually auto-discovers today (the best currently-trained checkpoint under this package's own preprocessing pipeline):
|
|
160
|
+
|
|
161
|
+
| Model | Hourly↓ | FirstDiff↓ | ACF↓ | RMSE↓ | Peak↓ | Energy↓ | Ramp↓ |
|
|
162
|
+
|---|---|---|---|---|---|---|---|
|
|
163
|
+
| AR | 1.3533 | 0.4216 | 0.1458 | 0.1802 | 0.7557 | 4.2780 | 0.1242 |
|
|
164
|
+
| ARMA | 1.3581 | 0.4218 | **0.1081** | 0.1864 | 0.7557 | 4.3916 | 0.1239 |
|
|
165
|
+
| ARIMA | 1.3540 | 0.1698 | 0.3125 | 0.1813 | 0.3451 | 4.2945 | **0.0074** |
|
|
166
|
+
| SARIMA | 1.3625 | 0.6389 | 0.2489 | 0.1894 | 0.6834 | 4.4981 | 0.2969 |
|
|
167
|
+
| **enerdatasynth (default)** | **0.0590** | **0.0792** | 0.1347 | **0.0484** | **0.0362** | **0.8099** | 0.0647 |
|
|
168
|
+
|
|
169
|
+
Wins outright on 5 of 7 metrics against every classical baseline. ARMA edges it out on ACF, ARIMA on Ramp — reported honestly rather than rounded up to "wins everything." A separate, larger comparison against the validated transformer architecture (trained on a different conditioning scheme — see the caveat in [`docs/architecture.md`](docs/architecture.md)) shows an even larger margin; see [`docs/research_notes.md`](docs/research_notes.md).
|
|
170
|
+
|
|
171
|
+
## 📚 Documentation
|
|
172
|
+
|
|
173
|
+
| Page | Contents |
|
|
174
|
+
|---|---|
|
|
175
|
+
| [`docs/installation.md`](docs/installation.md) | Install paths, extras, reproducing exact results, GPU |
|
|
176
|
+
| [`docs/quickstart.md`](docs/quickstart.md) | Full walkthrough: preprocess → train → calibrate → sample |
|
|
177
|
+
| [`docs/architecture.md`](docs/architecture.md) | Model, sampler, schedule, calibration — with evidence |
|
|
178
|
+
| [`docs/api_reference.md`](docs/api_reference.md) | The full `model()`/`.set()`/`.sample()` contract |
|
|
179
|
+
| [`docs/evaluation.md`](docs/evaluation.md) | The 7-metric suite, calibration, benchmarking |
|
|
180
|
+
| [`docs/temperature_data.md`](docs/temperature_data.md) | NOAA temperature ingestion, zone-station mapping |
|
|
181
|
+
| [`docs/training.md`](docs/training.md) | Training new checkpoints, sweeps, configs |
|
|
182
|
+
| [`docs/packaging.md`](docs/packaging.md) | How this package is built, versioned, and verified |
|
|
183
|
+
| [`docs/developer_guide.md`](docs/developer_guide.md) | Project layout, testing, contribution standards |
|
|
184
|
+
| [`docs/extending.md`](docs/extending.md) | Adding a new resource type (e.g. wind) |
|
|
185
|
+
| [`docs/faq.md`](docs/faq.md) | Common questions |
|
|
186
|
+
| [`MODEL_CARD.md`](MODEL_CARD.md) | Training data, limitations, intended/out-of-scope use |
|
|
187
|
+
| [`MIGRATION.md`](MIGRATION.md) | Full audit trail behind every default in this package |
|
|
188
|
+
|
|
189
|
+
## 🧪 Examples
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
python examples/generate_samples.py # LoadDiffusionModel round trip
|
|
193
|
+
python examples/run_full_pipeline.py # tiny end-to-end demo
|
|
194
|
+
python examples/exploratory_data_analysis.py # inspecting the processed dataset
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 🔬 Research Summary
|
|
198
|
+
|
|
199
|
+
- **A critical sampler bug, found and fixed.** An earlier sampler silently corrupted every diffusion result (values pinned near clamp bounds). Diagnosed and fixed; the corrected ancestral sampler is the only one shipped.
|
|
200
|
+
- **Architecture selected by controlled comparison, not assumption.** MLP vs CNN vs attention vs transformer, identical data/conditioning/schedule/seed. Transformer won on generated-profile quality at ~6x fewer parameters than the MLP it replaced (110,401 vs 688,408).
|
|
201
|
+
- **A methodological finding:** in that comparison, the *lowest validation loss* (CNN) was not the *best generated quality* (transformer) — a caution against judging generative models by training loss alone.
|
|
202
|
+
- **Calibration, evidence-checked rather than assumed better.** A more elaborate per-hour bias correction was tested and found *worse* on two metrics than the simpler scalar correction that's actually shipped.
|
|
203
|
+
|
|
204
|
+
Full detail, entirely traceable to specific files, is in [`docs/research_notes.md`](docs/research_notes.md) and [`MIGRATION.md`](MIGRATION.md).
|
|
205
|
+
|
|
206
|
+
## 📄 Citation
|
|
207
|
+
|
|
208
|
+
No accompanying paper has been published yet. If you use this software, please cite the repository:
|
|
209
|
+
|
|
210
|
+
```bibtex
|
|
211
|
+
@software{enerdatasynth,
|
|
212
|
+
title = {enerdatasynth: A Research SDK for Synthetic PJM Energy Resource Profiles},
|
|
213
|
+
author = {Sharma, Sonakshi},
|
|
214
|
+
year = {2026},
|
|
215
|
+
note = {Software}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## ⚖️ License
|
|
220
|
+
|
|
221
|
+
[MIT](LICENSE) © 2026 Sonakshi Sharma
|
|
222
|
+
|
|
223
|
+
## 🗺️ Roadmap
|
|
224
|
+
|
|
225
|
+
- [ ] Re-validate the transformer/calibration pipeline under the package's current one-hot conditioning (see the caveat in [`docs/architecture.md`](docs/architecture.md))
|
|
226
|
+
- [ ] Multi-zone support (17 zones explored in `data/research_history/scripts/zone_expansion.py`, not yet promoted)
|
|
227
|
+
- [ ] A friendlier `set()` for `spread` (currently defaults to the training-set mean; see [`docs/api_reference.md`](docs/api_reference.md))
|
|
228
|
+
- [ ] Wind generation, once real data/research exists to back it (see [`docs/extending.md`](docs/extending.md))
|
|
229
|
+
- [ ] Re-sweep hyperparameters for the transformer architecture specifically (the current defaults are inherited from `data/research_history/`, never re-swept against this package's own pipeline)
|
|
230
|
+
|
|
231
|
+
Tracked in detail in [`data/reports/future_experiments.md`](data/reports/future_experiments.md) and [`MIGRATION.md`](MIGRATION.md).
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
<div align="center">
|
|
236
|
+
<sub>Implementation details, folder structure, and the full conditioning-vector spec have moved to <a href="docs/developer_guide.md">docs/developer_guide.md</a> — this file is a landing page, not a developer reference.</sub>
|
|
237
|
+
</div>
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ enerdatasynth
|
|
4
|
+
|
|
5
|
+
### A research SDK for synthetic PJM energy resource profiles
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](pyproject.toml)
|
|
9
|
+
[](tests/)
|
|
10
|
+
[](MIGRATION.md)
|
|
11
|
+
|
|
12
|
+
**Generate realistic synthetic electric-load profiles from a fitted diffusion model — no ML expertise required.**
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import enerdatasynth
|
|
16
|
+
|
|
17
|
+
load = enerdatasynth.model("pjm_load", zone="PS")
|
|
18
|
+
load.set(month=7, Tmax=86, Tmin=70, weekday=True)
|
|
19
|
+
samples = load.sample(n=100) # (100, 24) — real MW, ready to use
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
[Quick Start](#-quick-start) · [Documentation](#-documentation) · [Model Card](MODEL_CARD.md) · [Benchmark Results](#-benchmark-results) · [Citation](#-citation)
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<br>
|
|
27
|
+
|
|
28
|
+
<div align="center">
|
|
29
|
+
<img src="data/research_history/reports/week7/profiles_headtohead.png" alt="Mean daily load profile: enerdatasynth's transformer baseline vs. classical time-series baselines vs. real data" width="720">
|
|
30
|
+
|
|
31
|
+
<sub>Mean daily profile — the validated transformer architecture (data/research_history/) vs. classical baselines vs. real PJM load. See <a href="#-benchmark-results">Benchmark Results</a> for numbers from the currently-shipped pipeline.</sub>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<br>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why this exists
|
|
39
|
+
|
|
40
|
+
Utilities, grid operators, and energy researchers frequently need realistic synthetic load data — for privacy-preserving data sharing, stress-testing forecasting models, or scenario planning — without exposing real customer or grid data. `enerdatasynth` wraps a validated conditional diffusion pipeline behind an SDK that hides every ML implementation detail: you describe a day (season, temperature, calendar type), and get back a realistic profile.
|
|
41
|
+
|
|
42
|
+
## ✨ Features
|
|
43
|
+
|
|
44
|
+
- **A fluent, implementation-hiding API** — `model()` → `.set()` → `.sample()`. No tensors, no checkpoint paths, no architecture classes ever surface.
|
|
45
|
+
- **A validated transformer architecture** — selected via a controlled 4-way architecture comparison (MLP/CNN/attention/transformer), not the first thing that was tried. See [Research Summary](#-research-summary).
|
|
46
|
+
- **A corrected diffusion sampler** — an earlier DDIM-style sampler was found to silently corrupt every result it touched; the fix is the only sampler shipped today.
|
|
47
|
+
- **Automatic bias calibration** — a systematic energy-error offset, diagnosed and corrected, applied automatically at generation time.
|
|
48
|
+
- **A common generator interface** (`BaseGenerator`) designed so a future resource type (e.g. wind) can be added through the same SDK contract — see [Extending](docs/extending.md).
|
|
49
|
+
- **Config-driven, YAML-first** hyperparameters (`data/configs/`), a real test suite (`tests/`, ~2–20s), and a full audit trail of every design decision ([MIGRATION.md](MIGRATION.md)) — nothing here is asserted without a file backing it.
|
|
50
|
+
|
|
51
|
+
## 🔌 Supported Generators
|
|
52
|
+
|
|
53
|
+
| Resource | Status | Zones | Entry point |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| Electric load | ✅ Available | `PS` (PJM/PSE&G, NJ) | `enerdatasynth.model("pjm_load", zone="PS")` |
|
|
56
|
+
| Wind | 🧩 Interface-ready, not implemented | — | See [`docs/extending.md`](docs/extending.md) — no wind data, model, or results exist in this project |
|
|
57
|
+
|
|
58
|
+
## 📦 Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install . # from a local clone
|
|
62
|
+
# or, once built:
|
|
63
|
+
pip install dist/*.whl
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Both install paths — plus `pip install -e .` for development — are verified end-to-end (see [MIGRATION.md](MIGRATION.md) for what "verified" means here, including a real packaging bug that was only caught by actually installing the built wheel).
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install ".[viz,dev]" # + seaborn, + pytest/ruff/mypy/build/twine
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 🚀 Quick Start
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import enerdatasynth
|
|
76
|
+
|
|
77
|
+
load = enerdatasynth.model("pjm_load", zone="PS")
|
|
78
|
+
load.set(month=7, Tmax=86, Tmin=70, weekday=True, holiday=False)
|
|
79
|
+
samples = load.sample(n=100, seed=0) # (100, 24) ndarray, MW
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That's the whole interface for generation. Training a new model, if you want to reproduce or extend the results below:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
enerdatasynth preprocess # raw CSVs -> tensors
|
|
86
|
+
enerdatasynth train # transformer, validated defaults
|
|
87
|
+
enerdatasynth calibrate --run-id transformer_T1000_H64_L2
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
See [`docs/quickstart.md`](docs/quickstart.md) for the full walkthrough and [`examples/`](examples/) for runnable scripts.
|
|
91
|
+
|
|
92
|
+
## 🏗️ Architecture
|
|
93
|
+
|
|
94
|
+
```mermaid
|
|
95
|
+
flowchart LR
|
|
96
|
+
A[Raw PJM load CSVs\n+ NOAA temperature] --> B[Preprocessing\nclean · pivot · normalize]
|
|
97
|
+
B --> C["Conditioning vector z\n(month, dow, holiday, Tmax, Tmin, spread)"]
|
|
98
|
+
C --> D["Transformer denoiser\n(DDPM, corrected ancestral sampler)"]
|
|
99
|
+
D --> E[Scalar bias\ncalibration]
|
|
100
|
+
E --> F["ResourceModel.sample()\nreal MW output"]
|
|
101
|
+
|
|
102
|
+
style D fill:#4c6ef5,color:#fff
|
|
103
|
+
style E fill:#f59f00,color:#fff
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Full detail — why transformer over MLP/CNN/attention, why the ancestral sampler over DDIM, why calibration is scalar not per-hour — is in [`docs/architecture.md`](docs/architecture.md), with every claim traced to a specific result file.
|
|
107
|
+
|
|
108
|
+
## 📊 Evaluation Pipeline
|
|
109
|
+
|
|
110
|
+
Every comparison in this project uses the same 7-metric suite (`Hourly`, `FirstDiff`, `ACF`, `RMSE`, `Peak`, `Energy`, `Ramp` — all lower-is-better, defined in [`docs/evaluation.md`](docs/evaluation.md)), computed by `enerdatasynth.evaluation.metrics.compute_metrics`. The same suite backs the automatic failure-mode detector (`evaluation.failure_analysis`) and the benchmark script below.
|
|
111
|
+
|
|
112
|
+
<div align="center">
|
|
113
|
+
<img src="data/reports/figures/hourly_distribution_comparison_T100_H256_L4.png" alt="Hourly distribution comparison, real vs generated, currently-shipped model" width="600">
|
|
114
|
+
<br><sub>Per-hour distribution, real vs. generated — the currently auto-discovered default model (see Benchmark Results).</sub>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
## 🏆 Benchmark Results
|
|
118
|
+
|
|
119
|
+
Run yourself: `python examples/run_benchmark.py --run-id <run_id>`. Numbers below are a real run against the full 600-day validation set, using the model `enerdatasynth.model("pjm_load")` actually auto-discovers today (the best currently-trained checkpoint under this package's own preprocessing pipeline):
|
|
120
|
+
|
|
121
|
+
| Model | Hourly↓ | FirstDiff↓ | ACF↓ | RMSE↓ | Peak↓ | Energy↓ | Ramp↓ |
|
|
122
|
+
|---|---|---|---|---|---|---|---|
|
|
123
|
+
| AR | 1.3533 | 0.4216 | 0.1458 | 0.1802 | 0.7557 | 4.2780 | 0.1242 |
|
|
124
|
+
| ARMA | 1.3581 | 0.4218 | **0.1081** | 0.1864 | 0.7557 | 4.3916 | 0.1239 |
|
|
125
|
+
| ARIMA | 1.3540 | 0.1698 | 0.3125 | 0.1813 | 0.3451 | 4.2945 | **0.0074** |
|
|
126
|
+
| SARIMA | 1.3625 | 0.6389 | 0.2489 | 0.1894 | 0.6834 | 4.4981 | 0.2969 |
|
|
127
|
+
| **enerdatasynth (default)** | **0.0590** | **0.0792** | 0.1347 | **0.0484** | **0.0362** | **0.8099** | 0.0647 |
|
|
128
|
+
|
|
129
|
+
Wins outright on 5 of 7 metrics against every classical baseline. ARMA edges it out on ACF, ARIMA on Ramp — reported honestly rather than rounded up to "wins everything." A separate, larger comparison against the validated transformer architecture (trained on a different conditioning scheme — see the caveat in [`docs/architecture.md`](docs/architecture.md)) shows an even larger margin; see [`docs/research_notes.md`](docs/research_notes.md).
|
|
130
|
+
|
|
131
|
+
## 📚 Documentation
|
|
132
|
+
|
|
133
|
+
| Page | Contents |
|
|
134
|
+
|---|---|
|
|
135
|
+
| [`docs/installation.md`](docs/installation.md) | Install paths, extras, reproducing exact results, GPU |
|
|
136
|
+
| [`docs/quickstart.md`](docs/quickstart.md) | Full walkthrough: preprocess → train → calibrate → sample |
|
|
137
|
+
| [`docs/architecture.md`](docs/architecture.md) | Model, sampler, schedule, calibration — with evidence |
|
|
138
|
+
| [`docs/api_reference.md`](docs/api_reference.md) | The full `model()`/`.set()`/`.sample()` contract |
|
|
139
|
+
| [`docs/evaluation.md`](docs/evaluation.md) | The 7-metric suite, calibration, benchmarking |
|
|
140
|
+
| [`docs/temperature_data.md`](docs/temperature_data.md) | NOAA temperature ingestion, zone-station mapping |
|
|
141
|
+
| [`docs/training.md`](docs/training.md) | Training new checkpoints, sweeps, configs |
|
|
142
|
+
| [`docs/packaging.md`](docs/packaging.md) | How this package is built, versioned, and verified |
|
|
143
|
+
| [`docs/developer_guide.md`](docs/developer_guide.md) | Project layout, testing, contribution standards |
|
|
144
|
+
| [`docs/extending.md`](docs/extending.md) | Adding a new resource type (e.g. wind) |
|
|
145
|
+
| [`docs/faq.md`](docs/faq.md) | Common questions |
|
|
146
|
+
| [`MODEL_CARD.md`](MODEL_CARD.md) | Training data, limitations, intended/out-of-scope use |
|
|
147
|
+
| [`MIGRATION.md`](MIGRATION.md) | Full audit trail behind every default in this package |
|
|
148
|
+
|
|
149
|
+
## 🧪 Examples
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python examples/generate_samples.py # LoadDiffusionModel round trip
|
|
153
|
+
python examples/run_full_pipeline.py # tiny end-to-end demo
|
|
154
|
+
python examples/exploratory_data_analysis.py # inspecting the processed dataset
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 🔬 Research Summary
|
|
158
|
+
|
|
159
|
+
- **A critical sampler bug, found and fixed.** An earlier sampler silently corrupted every diffusion result (values pinned near clamp bounds). Diagnosed and fixed; the corrected ancestral sampler is the only one shipped.
|
|
160
|
+
- **Architecture selected by controlled comparison, not assumption.** MLP vs CNN vs attention vs transformer, identical data/conditioning/schedule/seed. Transformer won on generated-profile quality at ~6x fewer parameters than the MLP it replaced (110,401 vs 688,408).
|
|
161
|
+
- **A methodological finding:** in that comparison, the *lowest validation loss* (CNN) was not the *best generated quality* (transformer) — a caution against judging generative models by training loss alone.
|
|
162
|
+
- **Calibration, evidence-checked rather than assumed better.** A more elaborate per-hour bias correction was tested and found *worse* on two metrics than the simpler scalar correction that's actually shipped.
|
|
163
|
+
|
|
164
|
+
Full detail, entirely traceable to specific files, is in [`docs/research_notes.md`](docs/research_notes.md) and [`MIGRATION.md`](MIGRATION.md).
|
|
165
|
+
|
|
166
|
+
## 📄 Citation
|
|
167
|
+
|
|
168
|
+
No accompanying paper has been published yet. If you use this software, please cite the repository:
|
|
169
|
+
|
|
170
|
+
```bibtex
|
|
171
|
+
@software{enerdatasynth,
|
|
172
|
+
title = {enerdatasynth: A Research SDK for Synthetic PJM Energy Resource Profiles},
|
|
173
|
+
author = {Sharma, Sonakshi},
|
|
174
|
+
year = {2026},
|
|
175
|
+
note = {Software}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## ⚖️ License
|
|
180
|
+
|
|
181
|
+
[MIT](LICENSE) © 2026 Sonakshi Sharma
|
|
182
|
+
|
|
183
|
+
## 🗺️ Roadmap
|
|
184
|
+
|
|
185
|
+
- [ ] Re-validate the transformer/calibration pipeline under the package's current one-hot conditioning (see the caveat in [`docs/architecture.md`](docs/architecture.md))
|
|
186
|
+
- [ ] Multi-zone support (17 zones explored in `data/research_history/scripts/zone_expansion.py`, not yet promoted)
|
|
187
|
+
- [ ] A friendlier `set()` for `spread` (currently defaults to the training-set mean; see [`docs/api_reference.md`](docs/api_reference.md))
|
|
188
|
+
- [ ] Wind generation, once real data/research exists to back it (see [`docs/extending.md`](docs/extending.md))
|
|
189
|
+
- [ ] Re-sweep hyperparameters for the transformer architecture specifically (the current defaults are inherited from `data/research_history/`, never re-swept against this package's own pipeline)
|
|
190
|
+
|
|
191
|
+
Tracked in detail in [`data/reports/future_experiments.md`](data/reports/future_experiments.md) and [`MIGRATION.md`](MIGRATION.md).
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
<div align="center">
|
|
196
|
+
<sub>Implementation details, folder structure, and the full conditioning-vector spec have moved to <a href="docs/developer_guide.md">docs/developer_guide.md</a> — this file is a landing page, not a developer reference.</sub>
|
|
197
|
+
</div>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "enerdatasynth"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A research SDK for synthetic PJM energy resource profiles (electric load today; a common interface for future resource types)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Sonakshi Sharma", email = "s.sonakshi2006@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
keywords = ["diffusion-model", "synthetic-data", "electric-load", "pjm", "time-series", "energy", "generative-model", "research-sdk"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy",
|
|
29
|
+
"pandas",
|
|
30
|
+
"torch",
|
|
31
|
+
"scikit-learn",
|
|
32
|
+
"statsmodels",
|
|
33
|
+
"holidays",
|
|
34
|
+
"matplotlib",
|
|
35
|
+
"requests",
|
|
36
|
+
"tqdm",
|
|
37
|
+
"pyarrow",
|
|
38
|
+
"pyyaml",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
viz = [
|
|
43
|
+
"seaborn>=0.13",
|
|
44
|
+
]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest",
|
|
47
|
+
"pytest-cov",
|
|
48
|
+
"ruff",
|
|
49
|
+
"mypy",
|
|
50
|
+
"build",
|
|
51
|
+
"twine",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
enerdatasynth = "enerdatasynth.cli:main"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.dynamic]
|
|
58
|
+
version = { attr = "enerdatasynth.__version__" }
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
enerdatasynth
|
|
3
|
+
================
|
|
4
|
+
A research SDK for synthetic PJM energy resource profiles. Today that
|
|
5
|
+
means electric load; the public interface is designed so a future
|
|
6
|
+
resource type (e.g. wind) can be added through the same contract —
|
|
7
|
+
see `docs/extending.md`.
|
|
8
|
+
|
|
9
|
+
Primary entry point — you should not need anything else for everyday
|
|
10
|
+
generation:
|
|
11
|
+
|
|
12
|
+
import enerdatasynth
|
|
13
|
+
|
|
14
|
+
load = enerdatasynth.model("pjm_load", zone="PS")
|
|
15
|
+
load.set(month=7, Tmax=86, Tmin=70, weekday=True, holiday=False)
|
|
16
|
+
samples = load.sample(n=100) # (100, 24) array, MW
|
|
17
|
+
|
|
18
|
+
`enerdatasynth.model(...)` never exposes tensors, architecture
|
|
19
|
+
classes, checkpoint paths, or the fact that a DDPM/transformer is
|
|
20
|
+
involved at all. See `docs/api_reference.md` for the full contract.
|
|
21
|
+
|
|
22
|
+
For research/development use — training new checkpoints, computing
|
|
23
|
+
evaluation metrics, running calibration — this top-level namespace
|
|
24
|
+
also re-exports the lower-level building blocks:
|
|
25
|
+
|
|
26
|
+
from enerdatasynth import RunConfig, train_one_config, LoadDiffusionModel
|
|
27
|
+
|
|
28
|
+
For anything not re-exported here, import directly from the relevant
|
|
29
|
+
submodule (`enerdatasynth.data`, `enerdatasynth.models`,
|
|
30
|
+
`enerdatasynth.diffusion`, `enerdatasynth.training`,
|
|
31
|
+
`enerdatasynth.sampling`, `enerdatasynth.evaluation`,
|
|
32
|
+
`enerdatasynth.visualization`, `enerdatasynth.generators`,
|
|
33
|
+
`enerdatasynth.utils`).
|
|
34
|
+
|
|
35
|
+
Production code in this package never imports from `research_history/`
|
|
36
|
+
— that directory exists only for reproducibility/provenance and is
|
|
37
|
+
not installed as part of the package.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from enerdatasynth.sdk import model, ResourceModel
|
|
41
|
+
from enerdatasynth.utils.config import RunConfig, set_seed, DEFAULT_ARCHITECTURE
|
|
42
|
+
from enerdatasynth.models import build_denoiser, TransformerDenoiser, SimpleMLPDenoiser
|
|
43
|
+
from enerdatasynth.diffusion.ddpm import DDPM
|
|
44
|
+
from enerdatasynth.data.preprocess import run_preprocessing
|
|
45
|
+
from enerdatasynth.training.train import train_one_config
|
|
46
|
+
from enerdatasynth.sampling.generate import generate_for_run
|
|
47
|
+
from enerdatasynth.evaluation.metrics import compute_metrics
|
|
48
|
+
from enerdatasynth.evaluation.calibration import calibrate_checkpoint
|
|
49
|
+
from enerdatasynth.generators import BaseGenerator, LoadDiffusionModel
|
|
50
|
+
|
|
51
|
+
__version__ = "0.4.1"
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"__version__",
|
|
55
|
+
"model",
|
|
56
|
+
"ResourceModel",
|
|
57
|
+
"BaseGenerator",
|
|
58
|
+
"LoadDiffusionModel",
|
|
59
|
+
"RunConfig",
|
|
60
|
+
"set_seed",
|
|
61
|
+
"DEFAULT_ARCHITECTURE",
|
|
62
|
+
"build_denoiser",
|
|
63
|
+
"TransformerDenoiser",
|
|
64
|
+
"SimpleMLPDenoiser",
|
|
65
|
+
"DDPM",
|
|
66
|
+
"run_preprocessing",
|
|
67
|
+
"train_one_config",
|
|
68
|
+
"generate_for_run",
|
|
69
|
+
"calibrate_checkpoint",
|
|
70
|
+
"compute_metrics",
|
|
71
|
+
]
|