pyconvexity 0.4.3__py3-none-any.whl
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.
Potentially problematic release.
This version of pyconvexity might be problematic. Click here for more details.
- pyconvexity/__init__.py +226 -0
- pyconvexity/_version.py +1 -0
- pyconvexity/core/__init__.py +60 -0
- pyconvexity/core/database.py +485 -0
- pyconvexity/core/errors.py +106 -0
- pyconvexity/core/types.py +400 -0
- pyconvexity/data/README.md +101 -0
- pyconvexity/data/__init__.py +17 -0
- pyconvexity/data/loaders/__init__.py +3 -0
- pyconvexity/data/loaders/cache.py +213 -0
- pyconvexity/data/schema/01_core_schema.sql +420 -0
- pyconvexity/data/schema/02_data_metadata.sql +120 -0
- pyconvexity/data/schema/03_validation_data.sql +506 -0
- pyconvexity/data/sources/__init__.py +5 -0
- pyconvexity/data/sources/gem.py +442 -0
- pyconvexity/io/__init__.py +26 -0
- pyconvexity/io/excel_exporter.py +1226 -0
- pyconvexity/io/excel_importer.py +1381 -0
- pyconvexity/io/netcdf_exporter.py +197 -0
- pyconvexity/io/netcdf_importer.py +1833 -0
- pyconvexity/models/__init__.py +195 -0
- pyconvexity/models/attributes.py +730 -0
- pyconvexity/models/carriers.py +159 -0
- pyconvexity/models/components.py +611 -0
- pyconvexity/models/network.py +503 -0
- pyconvexity/models/results.py +148 -0
- pyconvexity/models/scenarios.py +234 -0
- pyconvexity/solvers/__init__.py +29 -0
- pyconvexity/solvers/pypsa/__init__.py +24 -0
- pyconvexity/solvers/pypsa/api.py +460 -0
- pyconvexity/solvers/pypsa/batch_loader.py +307 -0
- pyconvexity/solvers/pypsa/builder.py +675 -0
- pyconvexity/solvers/pypsa/constraints.py +405 -0
- pyconvexity/solvers/pypsa/solver.py +1509 -0
- pyconvexity/solvers/pypsa/storage.py +2048 -0
- pyconvexity/timeseries.py +330 -0
- pyconvexity/validation/__init__.py +25 -0
- pyconvexity/validation/rules.py +312 -0
- pyconvexity-0.4.3.dist-info/METADATA +47 -0
- pyconvexity-0.4.3.dist-info/RECORD +42 -0
- pyconvexity-0.4.3.dist-info/WHEEL +5 -0
- pyconvexity-0.4.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core data types for PyConvexity.
|
|
3
|
+
|
|
4
|
+
These types mirror the Rust implementation while providing Python-specific
|
|
5
|
+
enhancements and better type safety.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Dict, Any, Optional, List, Union
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StaticValue:
|
|
14
|
+
"""
|
|
15
|
+
Represents a static (non-time-varying) attribute value.
|
|
16
|
+
|
|
17
|
+
Mirrors the Rust StaticValue enum while providing Python conveniences.
|
|
18
|
+
Supports float, int, bool, and string values with proper type conversion.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, value: Union[float, int, bool, str]):
|
|
22
|
+
# Check bool before int since bool is subclass of int in Python
|
|
23
|
+
if isinstance(value, bool):
|
|
24
|
+
self.data = {"Boolean": value}
|
|
25
|
+
elif isinstance(value, float):
|
|
26
|
+
self.data = {"Float": value}
|
|
27
|
+
elif isinstance(value, int):
|
|
28
|
+
self.data = {"Integer": value}
|
|
29
|
+
elif isinstance(value, str):
|
|
30
|
+
self.data = {"String": value}
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError(f"Unsupported value type: {type(value)}")
|
|
33
|
+
|
|
34
|
+
def to_json(self) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Return raw value as JSON to match Rust serialization format.
|
|
37
|
+
|
|
38
|
+
Rust stores: 123.45, 42, true, "hello"
|
|
39
|
+
Not: {"Float": 123.45}, {"Integer": 42}, etc.
|
|
40
|
+
"""
|
|
41
|
+
import math
|
|
42
|
+
|
|
43
|
+
if "Float" in self.data:
|
|
44
|
+
float_val = self.data["Float"]
|
|
45
|
+
# Ensure finite values only
|
|
46
|
+
if not math.isfinite(float_val):
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Cannot serialize non-finite float value: {float_val}"
|
|
49
|
+
)
|
|
50
|
+
return json.dumps(float_val)
|
|
51
|
+
elif "Integer" in self.data:
|
|
52
|
+
return json.dumps(self.data["Integer"])
|
|
53
|
+
elif "Boolean" in self.data:
|
|
54
|
+
return json.dumps(self.data["Boolean"])
|
|
55
|
+
elif "String" in self.data:
|
|
56
|
+
return json.dumps(self.data["String"])
|
|
57
|
+
else:
|
|
58
|
+
# Fallback to original format if unknown
|
|
59
|
+
return json.dumps(self.data)
|
|
60
|
+
|
|
61
|
+
def data_type(self) -> str:
|
|
62
|
+
"""Get data type name - mirrors Rust implementation"""
|
|
63
|
+
if "Float" in self.data:
|
|
64
|
+
return "float"
|
|
65
|
+
elif "Integer" in self.data:
|
|
66
|
+
return "int"
|
|
67
|
+
elif "Boolean" in self.data:
|
|
68
|
+
return "boolean"
|
|
69
|
+
elif "String" in self.data:
|
|
70
|
+
return "string"
|
|
71
|
+
else:
|
|
72
|
+
return "unknown"
|
|
73
|
+
|
|
74
|
+
def as_f64(self) -> float:
|
|
75
|
+
"""Convert to float, mirroring Rust implementation"""
|
|
76
|
+
if "Float" in self.data:
|
|
77
|
+
return self.data["Float"]
|
|
78
|
+
elif "Integer" in self.data:
|
|
79
|
+
return float(self.data["Integer"])
|
|
80
|
+
elif "Boolean" in self.data:
|
|
81
|
+
return 1.0 if self.data["Boolean"] else 0.0
|
|
82
|
+
else:
|
|
83
|
+
try:
|
|
84
|
+
return float(self.data["String"])
|
|
85
|
+
except ValueError:
|
|
86
|
+
return 0.0
|
|
87
|
+
|
|
88
|
+
def value(self) -> Union[float, int, bool, str]:
|
|
89
|
+
"""Get the raw Python value"""
|
|
90
|
+
if "Float" in self.data:
|
|
91
|
+
return self.data["Float"]
|
|
92
|
+
elif "Integer" in self.data:
|
|
93
|
+
return self.data["Integer"]
|
|
94
|
+
elif "Boolean" in self.data:
|
|
95
|
+
return self.data["Boolean"]
|
|
96
|
+
elif "String" in self.data:
|
|
97
|
+
return self.data["String"]
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError("Unknown data type in StaticValue")
|
|
100
|
+
|
|
101
|
+
def __repr__(self) -> str:
|
|
102
|
+
return f"StaticValue({self.value()})"
|
|
103
|
+
|
|
104
|
+
def __eq__(self, other) -> bool:
|
|
105
|
+
if isinstance(other, StaticValue):
|
|
106
|
+
return self.data == other.data
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class Timeseries:
|
|
112
|
+
"""
|
|
113
|
+
Efficient timeseries data structure matching the new Rust implementation.
|
|
114
|
+
|
|
115
|
+
Stores values as a flat array for maximum performance, matching the
|
|
116
|
+
unified Rust Timeseries struct.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
values: List[float]
|
|
120
|
+
length: int
|
|
121
|
+
start_index: int
|
|
122
|
+
data_type: str
|
|
123
|
+
unit: Optional[str]
|
|
124
|
+
is_input: bool
|
|
125
|
+
|
|
126
|
+
def __post_init__(self):
|
|
127
|
+
# Ensure length matches values array
|
|
128
|
+
self.length = len(self.values)
|
|
129
|
+
# Ensure all values are float32-compatible
|
|
130
|
+
self.values = [float(v) for v in self.values]
|
|
131
|
+
|
|
132
|
+
def get_value(self, index: int) -> Optional[float]:
|
|
133
|
+
"""Get value at specific index."""
|
|
134
|
+
if 0 <= index < len(self.values):
|
|
135
|
+
return self.values[index]
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def get_range(self, start: int, end: int) -> List[float]:
|
|
139
|
+
"""Get a range of values efficiently."""
|
|
140
|
+
end = min(end, len(self.values))
|
|
141
|
+
start = min(start, end)
|
|
142
|
+
return self.values[start:end]
|
|
143
|
+
|
|
144
|
+
def sample(self, max_points: int) -> "Timeseries":
|
|
145
|
+
"""Apply sampling if the timeseries is too large."""
|
|
146
|
+
if len(self.values) <= max_points:
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
step = len(self.values) // max_points
|
|
150
|
+
sampled_values = []
|
|
151
|
+
|
|
152
|
+
for i in range(0, len(self.values), max(1, step)):
|
|
153
|
+
sampled_values.append(self.values[i])
|
|
154
|
+
|
|
155
|
+
# Always include the last point if not already included
|
|
156
|
+
if self.values and sampled_values[-1] != self.values[-1]:
|
|
157
|
+
sampled_values.append(self.values[-1])
|
|
158
|
+
|
|
159
|
+
return Timeseries(
|
|
160
|
+
values=sampled_values,
|
|
161
|
+
length=len(sampled_values),
|
|
162
|
+
start_index=self.start_index,
|
|
163
|
+
data_type=self.data_type,
|
|
164
|
+
unit=self.unit,
|
|
165
|
+
is_input=self.is_input,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def slice(self, start_index: int, end_index: int) -> "Timeseries":
|
|
169
|
+
"""Apply range filtering."""
|
|
170
|
+
start = max(0, start_index - self.start_index)
|
|
171
|
+
end = max(0, end_index - self.start_index)
|
|
172
|
+
end = min(end, len(self.values))
|
|
173
|
+
start = min(start, end)
|
|
174
|
+
|
|
175
|
+
return Timeseries(
|
|
176
|
+
values=self.values[start:end],
|
|
177
|
+
length=end - start,
|
|
178
|
+
start_index=self.start_index + start,
|
|
179
|
+
data_type=self.data_type,
|
|
180
|
+
unit=self.unit,
|
|
181
|
+
is_input=self.is_input,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class TimeseriesMetadata:
|
|
187
|
+
"""
|
|
188
|
+
Metadata about a timeseries without loading the full data.
|
|
189
|
+
|
|
190
|
+
Mirrors Rust TimeseriesMetadata struct.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
length: int
|
|
194
|
+
start_time: int
|
|
195
|
+
end_time: int
|
|
196
|
+
start_index: int
|
|
197
|
+
end_index: int
|
|
198
|
+
data_type: str
|
|
199
|
+
unit: Optional[str]
|
|
200
|
+
is_input: bool
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass
|
|
204
|
+
class TimePeriod:
|
|
205
|
+
"""
|
|
206
|
+
Represents a time period in the network's time axis.
|
|
207
|
+
|
|
208
|
+
Mirrors Rust TimePeriod structure.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
timestamp: int
|
|
212
|
+
period_index: int
|
|
213
|
+
formatted_time: str
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@dataclass
|
|
217
|
+
class TimeseriesValidationResult:
|
|
218
|
+
"""
|
|
219
|
+
Result of validating timeseries alignment with network time periods.
|
|
220
|
+
|
|
221
|
+
Mirrors Rust TimeseriesValidationResult.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
is_valid: bool
|
|
225
|
+
missing_periods: List[int]
|
|
226
|
+
extra_periods: List[int]
|
|
227
|
+
total_network_periods: int
|
|
228
|
+
provided_periods: int
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass
|
|
232
|
+
class ValidationRule:
|
|
233
|
+
"""
|
|
234
|
+
Validation rule for component attributes.
|
|
235
|
+
|
|
236
|
+
Mirrors Rust ValidationRule with all fields.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
component_type: str
|
|
240
|
+
attribute_name: str
|
|
241
|
+
data_type: str
|
|
242
|
+
unit: Optional[str]
|
|
243
|
+
default_value_string: Optional[str]
|
|
244
|
+
allowed_storage_types: str
|
|
245
|
+
allows_static: bool
|
|
246
|
+
allows_timeseries: bool
|
|
247
|
+
is_required: bool
|
|
248
|
+
is_input: bool
|
|
249
|
+
description: Optional[str]
|
|
250
|
+
default_value: Optional[StaticValue]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class AttributeValue:
|
|
254
|
+
"""
|
|
255
|
+
Represents either a static value or timeseries data for a component attribute.
|
|
256
|
+
|
|
257
|
+
Uses efficient Timeseries format for optimal performance.
|
|
258
|
+
Mirrors Rust AttributeValue enum.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
def __init__(self, value: Union[StaticValue, Timeseries]):
|
|
262
|
+
if isinstance(value, StaticValue):
|
|
263
|
+
self.variant = "Static"
|
|
264
|
+
self.static_value = value
|
|
265
|
+
self.timeseries_value = None
|
|
266
|
+
elif isinstance(value, Timeseries):
|
|
267
|
+
self.variant = "Timeseries"
|
|
268
|
+
self.static_value = None
|
|
269
|
+
self.timeseries_value = value
|
|
270
|
+
else:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"AttributeValue must be StaticValue or Timeseries, got {type(value)}"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def static(cls, value: StaticValue) -> "AttributeValue":
|
|
277
|
+
"""Create a static attribute value"""
|
|
278
|
+
return cls(value)
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def timeseries(cls, timeseries: Timeseries) -> "AttributeValue":
|
|
282
|
+
"""Create a timeseries attribute value (new format)"""
|
|
283
|
+
return cls(timeseries)
|
|
284
|
+
|
|
285
|
+
def is_static(self) -> bool:
|
|
286
|
+
"""Check if this is a static value"""
|
|
287
|
+
return self.variant == "Static"
|
|
288
|
+
|
|
289
|
+
def is_timeseries(self) -> bool:
|
|
290
|
+
"""Check if this is a timeseries value"""
|
|
291
|
+
return self.variant == "Timeseries"
|
|
292
|
+
|
|
293
|
+
def as_timeseries(self) -> Optional[Timeseries]:
|
|
294
|
+
"""Get the timeseries data in new format"""
|
|
295
|
+
return self.timeseries_value if self.is_timeseries() else None
|
|
296
|
+
|
|
297
|
+
def __repr__(self) -> str:
|
|
298
|
+
if self.is_static():
|
|
299
|
+
return f"AttributeValue.static({self.static_value})"
|
|
300
|
+
else:
|
|
301
|
+
length = len(self.timeseries_value.values) if self.timeseries_value else 0
|
|
302
|
+
return f"AttributeValue.timeseries({length} points)"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class Component:
|
|
307
|
+
"""
|
|
308
|
+
Represents a component in the energy system model (single network per database).
|
|
309
|
+
|
|
310
|
+
Mirrors Rust Component struct.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
id: int
|
|
314
|
+
component_type: str
|
|
315
|
+
name: str
|
|
316
|
+
longitude: Optional[float] = None
|
|
317
|
+
latitude: Optional[float] = None
|
|
318
|
+
carrier_id: Optional[int] = None
|
|
319
|
+
bus_id: Optional[int] = None
|
|
320
|
+
bus0_id: Optional[int] = None
|
|
321
|
+
bus1_id: Optional[int] = None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@dataclass
|
|
325
|
+
class Network:
|
|
326
|
+
"""
|
|
327
|
+
Represents a network/model in the system.
|
|
328
|
+
|
|
329
|
+
Enhanced version of network information with additional metadata.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
id: int
|
|
333
|
+
name: str
|
|
334
|
+
description: Optional[str] = None
|
|
335
|
+
time_start: Optional[str] = None
|
|
336
|
+
time_end: Optional[str] = None
|
|
337
|
+
time_interval: Optional[str] = None
|
|
338
|
+
created_at: Optional[str] = None
|
|
339
|
+
updated_at: Optional[str] = None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@dataclass
|
|
343
|
+
class CreateComponentRequest:
|
|
344
|
+
"""
|
|
345
|
+
Request structure for creating a new component (single network per database).
|
|
346
|
+
|
|
347
|
+
Mirrors Rust CreateComponentRequest.
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
component_type: str
|
|
351
|
+
name: str
|
|
352
|
+
description: Optional[str] = None
|
|
353
|
+
longitude: Optional[float] = None
|
|
354
|
+
latitude: Optional[float] = None
|
|
355
|
+
carrier_id: Optional[int] = None
|
|
356
|
+
bus_id: Optional[int] = None
|
|
357
|
+
bus0_id: Optional[int] = None
|
|
358
|
+
bus1_id: Optional[int] = None
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@dataclass
|
|
362
|
+
class CreateNetworkRequest:
|
|
363
|
+
"""
|
|
364
|
+
Request structure for creating a new network.
|
|
365
|
+
|
|
366
|
+
Mirrors Rust CreateNetworkRequest.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
name: str
|
|
370
|
+
description: Optional[str] = None
|
|
371
|
+
time_resolution: Optional[str] = None
|
|
372
|
+
start_time: Optional[str] = None
|
|
373
|
+
end_time: Optional[str] = None
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@dataclass
|
|
377
|
+
class Carrier:
|
|
378
|
+
"""
|
|
379
|
+
Represents an energy carrier (e.g., electricity, heat, gas).
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
id: int
|
|
383
|
+
name: str
|
|
384
|
+
co2_emissions: float = 0.0
|
|
385
|
+
color: Optional[str] = None
|
|
386
|
+
nice_name: Optional[str] = None
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@dataclass
|
|
390
|
+
class Scenario:
|
|
391
|
+
"""
|
|
392
|
+
Represents a scenario within a network.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
id: int
|
|
396
|
+
name: str
|
|
397
|
+
description: Optional[str] = None
|
|
398
|
+
is_master: bool = False
|
|
399
|
+
created_at: Optional[str] = None
|
|
400
|
+
updated_at: Optional[str] = None
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# PyConvexity Data Module
|
|
2
|
+
|
|
3
|
+
The `pyconvexity.data` module provides functions for loading external energy data and integrating it with PyConvexity models. This is a simple, expert-friendly toolbox for working with real-world energy data.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install PyConvexity with data dependencies:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pyconvexity[data]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Current Data Sources
|
|
14
|
+
|
|
15
|
+
### Global Energy Monitor (GEM)
|
|
16
|
+
|
|
17
|
+
Load power plant data from GEM's Global Integrated Power dataset.
|
|
18
|
+
|
|
19
|
+
**Setup:**
|
|
20
|
+
1. Download the GEM Excel file: `Global-Integrated-Power-August-2025.xlsx`
|
|
21
|
+
2. Place it in a `data/raw/global-energy-monitor/` directory, or set the path manually
|
|
22
|
+
|
|
23
|
+
**Usage:**
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import pyconvexity as px
|
|
27
|
+
|
|
28
|
+
# Load generators for a specific country
|
|
29
|
+
generators = px.data.get_generators_from_gem(
|
|
30
|
+
country="USA", # ISO 3-letter country code
|
|
31
|
+
technology_types=["solar", "wind", "nuclear"], # Optional filter
|
|
32
|
+
min_capacity_mw=100.0 # Optional minimum capacity
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Create a network and add generators
|
|
36
|
+
px.create_database_with_schema("my_model.db")
|
|
37
|
+
|
|
38
|
+
with px.database_context("my_model.db") as conn:
|
|
39
|
+
network_id = px.create_network(conn, network_req)
|
|
40
|
+
|
|
41
|
+
# Create carriers
|
|
42
|
+
carriers = {}
|
|
43
|
+
for carrier_name in generators['carrier'].unique():
|
|
44
|
+
carriers[carrier_name] = px.create_carrier(conn, network_id, carrier_name)
|
|
45
|
+
|
|
46
|
+
# Add generators to network
|
|
47
|
+
generator_ids = px.data.add_gem_generators_to_network(
|
|
48
|
+
conn, network_id, generators, carrier_mapping=carriers
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Data Output Format
|
|
53
|
+
|
|
54
|
+
The `get_generators_from_gem()` function returns a pandas DataFrame with these columns:
|
|
55
|
+
|
|
56
|
+
- `plant_name`: Name of the power plant
|
|
57
|
+
- `country_iso_3`: ISO 3-letter country code
|
|
58
|
+
- `category`: Energy category (nuclear, thermal, renewables, storage, etc.)
|
|
59
|
+
- `carrier`: Energy carrier (coal, gas, solar, wind, nuclear, etc.)
|
|
60
|
+
- `type`: Technology type (subcritical, combined-cycle, photovoltaic, etc.)
|
|
61
|
+
- `capacity_mw`: Capacity in megawatts
|
|
62
|
+
- `start_year`: Year the plant started operation
|
|
63
|
+
- `latitude`: Latitude coordinate
|
|
64
|
+
- `longitude`: Longitude coordinate
|
|
65
|
+
|
|
66
|
+
## Technology Mapping
|
|
67
|
+
|
|
68
|
+
GEM technologies are automatically mapped to a standardized schema:
|
|
69
|
+
|
|
70
|
+
- **Nuclear**: pressurized-water-reactor, boiling-water-reactor, small-modular-reactor
|
|
71
|
+
- **Thermal**: subcritical, supercritical, combined-cycle, gas-turbine
|
|
72
|
+
- **Renewables**: photovoltaic, thermal (solar), onshore/offshore (wind), run-of-river (hydro)
|
|
73
|
+
- **Storage**: lithium-ion (battery), pumped-hydro
|
|
74
|
+
- **Bioenergy**: biomass, biogas
|
|
75
|
+
|
|
76
|
+
## Caching
|
|
77
|
+
|
|
78
|
+
Data is automatically cached for 7 days to improve performance. You can:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# Disable caching
|
|
82
|
+
generators = px.data.get_generators_from_gem(country="USA", use_cache=False)
|
|
83
|
+
|
|
84
|
+
# Clear cache
|
|
85
|
+
cache = px.data.DataCache()
|
|
86
|
+
cache.clear_cache('gem_generators')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Examples
|
|
90
|
+
|
|
91
|
+
See `examples/gem_data_example.py` for a complete working example.
|
|
92
|
+
|
|
93
|
+
## Future Data Sources
|
|
94
|
+
|
|
95
|
+
The framework is designed to be extensible. Planned additions include:
|
|
96
|
+
|
|
97
|
+
- IRENA Global Energy Atlas (renewable resource data)
|
|
98
|
+
- World Bank energy statistics
|
|
99
|
+
- IEA World Energy Outlook data
|
|
100
|
+
- OpenStreetMap transmission infrastructure
|
|
101
|
+
- NASA weather data for renewable profiles
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyConvexity Data Module
|
|
3
|
+
|
|
4
|
+
Provides functions for loading external energy data and integrating it with PyConvexity models.
|
|
5
|
+
This module offers a simple, expert-friendly toolbox for working with real-world energy data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .sources.gem import get_generators_from_gem, add_gem_generators_to_network
|
|
9
|
+
from .loaders.cache import DataCache
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
# GEM (Global Energy Monitor) functions
|
|
13
|
+
"get_generators_from_gem",
|
|
14
|
+
"add_gem_generators_to_network",
|
|
15
|
+
# Caching utilities
|
|
16
|
+
"DataCache",
|
|
17
|
+
]
|