pyconvexity 0.1.2__py3-none-any.whl → 0.1.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 +30 -6
- pyconvexity/data/README.md +101 -0
- pyconvexity/data/__init__.py +18 -0
- pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/__init__.py +3 -0
- pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/cache.py +212 -0
- pyconvexity/data/sources/__init__.py +5 -0
- pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
- pyconvexity/data/sources/gem.py +412 -0
- pyconvexity/io/__init__.py +32 -0
- pyconvexity/io/excel_exporter.py +991 -0
- pyconvexity/io/excel_importer.py +1112 -0
- pyconvexity/io/netcdf_exporter.py +192 -0
- pyconvexity/io/netcdf_importer.py +599 -0
- pyconvexity/models/__init__.py +7 -0
- pyconvexity/models/components.py +3 -0
- pyconvexity/models/scenarios.py +177 -0
- pyconvexity/solvers/__init__.py +29 -0
- pyconvexity/solvers/pypsa/__init__.py +24 -0
- pyconvexity/solvers/pypsa/api.py +398 -0
- pyconvexity/solvers/pypsa/batch_loader.py +311 -0
- pyconvexity/solvers/pypsa/builder.py +656 -0
- pyconvexity/solvers/pypsa/constraints.py +321 -0
- pyconvexity/solvers/pypsa/solver.py +1255 -0
- pyconvexity/solvers/pypsa/storage.py +2207 -0
- {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.3.dist-info}/METADATA +5 -2
- pyconvexity-0.1.3.dist-info/RECORD +45 -0
- pyconvexity-0.1.2.dist-info/RECORD +0 -20
- {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.3.dist-info}/WHEEL +0 -0
- {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constraint application functionality for PyPSA networks.
|
|
3
|
+
|
|
4
|
+
Handles loading and applying custom constraints from the database.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import numpy as np
|
|
10
|
+
from typing import Dict, Any, Optional, List
|
|
11
|
+
|
|
12
|
+
from pyconvexity.models import list_components_by_type, get_attribute
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConstraintApplicator:
|
|
18
|
+
"""
|
|
19
|
+
Handles loading and applying custom constraints to PyPSA networks.
|
|
20
|
+
|
|
21
|
+
This class manages both pre-optimization constraints (applied to network structure)
|
|
22
|
+
and optimization-time constraints (applied during solving via extra_functionality).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def apply_constraints(
|
|
26
|
+
self,
|
|
27
|
+
conn,
|
|
28
|
+
network_id: int,
|
|
29
|
+
network: 'pypsa.Network',
|
|
30
|
+
scenario_id: Optional[int] = None,
|
|
31
|
+
constraints_dsl: Optional[str] = None
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Apply all constraints to the network.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
conn: Database connection
|
|
38
|
+
network_id: ID of the network
|
|
39
|
+
network: PyPSA Network object
|
|
40
|
+
scenario_id: Optional scenario ID
|
|
41
|
+
constraints_dsl: Optional DSL constraints string
|
|
42
|
+
"""
|
|
43
|
+
# Apply database constraints
|
|
44
|
+
self._apply_database_constraints(conn, network_id, network, scenario_id)
|
|
45
|
+
|
|
46
|
+
# Apply DSL constraints if provided
|
|
47
|
+
if constraints_dsl:
|
|
48
|
+
self._apply_dsl_constraints(network, constraints_dsl)
|
|
49
|
+
|
|
50
|
+
def _apply_database_constraints(
|
|
51
|
+
self,
|
|
52
|
+
conn,
|
|
53
|
+
network_id: int,
|
|
54
|
+
network: 'pypsa.Network',
|
|
55
|
+
scenario_id: Optional[int]
|
|
56
|
+
):
|
|
57
|
+
"""Load and apply custom constraints from the database in priority order."""
|
|
58
|
+
try:
|
|
59
|
+
# Load all constraints for this network
|
|
60
|
+
constraints = list_components_by_type(conn, network_id, 'CONSTRAINT')
|
|
61
|
+
|
|
62
|
+
if not constraints:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Load constraint attributes and filter active ones
|
|
66
|
+
active_constraints = []
|
|
67
|
+
for constraint in constraints:
|
|
68
|
+
try:
|
|
69
|
+
# Get constraint attributes
|
|
70
|
+
is_active = get_attribute(conn, constraint.id, 'is_active', scenario_id)
|
|
71
|
+
priority = get_attribute(conn, constraint.id, 'priority', scenario_id)
|
|
72
|
+
constraint_code = get_attribute(conn, constraint.id, 'constraint_code', scenario_id)
|
|
73
|
+
|
|
74
|
+
# Check if constraint is active
|
|
75
|
+
if is_active.variant == "Static":
|
|
76
|
+
# Extract boolean value from StaticValue
|
|
77
|
+
is_active_bool = False
|
|
78
|
+
if "Boolean" in is_active.static_value.data:
|
|
79
|
+
is_active_bool = is_active.static_value.data["Boolean"]
|
|
80
|
+
|
|
81
|
+
if is_active_bool:
|
|
82
|
+
# Extract code value first to check if it's an optimization constraint
|
|
83
|
+
code_val = ""
|
|
84
|
+
if constraint_code.variant == "Static":
|
|
85
|
+
if "String" in constraint_code.static_value.data:
|
|
86
|
+
code_val = constraint_code.static_value.data["String"]
|
|
87
|
+
|
|
88
|
+
# Skip optimization-time constraints in pre-optimization phase
|
|
89
|
+
if 'net.model' in code_val or 'network.model' in code_val or 'n.model' in code_val:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Extract priority value
|
|
93
|
+
priority_val = 0
|
|
94
|
+
if priority.variant == "Static":
|
|
95
|
+
if "Integer" in priority.static_value.data:
|
|
96
|
+
priority_val = priority.static_value.data["Integer"]
|
|
97
|
+
elif "Float" in priority.static_value.data:
|
|
98
|
+
priority_val = int(priority.static_value.data["Float"])
|
|
99
|
+
|
|
100
|
+
active_constraints.append({
|
|
101
|
+
'id': constraint.id,
|
|
102
|
+
'name': constraint.name,
|
|
103
|
+
'priority': priority_val,
|
|
104
|
+
'code': code_val
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.warning(f"Failed to load constraint {constraint.name}: {e}")
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
if not active_constraints:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
# Sort constraints by priority (lower numbers first)
|
|
115
|
+
active_constraints.sort(key=lambda x: x['priority'])
|
|
116
|
+
|
|
117
|
+
# Execute constraints in order
|
|
118
|
+
for constraint in active_constraints:
|
|
119
|
+
try:
|
|
120
|
+
logger.info(f"Executing constraint '{constraint['name']}' (priority {constraint['priority']})")
|
|
121
|
+
logger.debug(f"Code: {constraint['code']}")
|
|
122
|
+
|
|
123
|
+
# Execute the constraint code in the normal Python environment
|
|
124
|
+
# The network object 'n' is available in the global scope
|
|
125
|
+
exec_globals = {
|
|
126
|
+
'n': network,
|
|
127
|
+
'pd': pd,
|
|
128
|
+
'np': np,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Execute the constraint code
|
|
132
|
+
exec(constraint['code'], exec_globals)
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
error_msg = f"Failed to execute constraint '{constraint['name']}': {e}"
|
|
136
|
+
logger.error(error_msg, exc_info=True)
|
|
137
|
+
# Continue with other constraints instead of failing the entire solve
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Failed to apply custom constraints: {e}", exc_info=True)
|
|
142
|
+
|
|
143
|
+
def _apply_dsl_constraints(self, network: 'pypsa.Network', constraints_dsl: str):
|
|
144
|
+
"""
|
|
145
|
+
Apply DSL constraints to the network.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
network: PyPSA Network object
|
|
149
|
+
constraints_dsl: DSL constraints string
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
logger.info("Applying DSL constraints")
|
|
153
|
+
logger.debug(f"DSL Code: {constraints_dsl}")
|
|
154
|
+
|
|
155
|
+
# Execute DSL constraints
|
|
156
|
+
exec_globals = {
|
|
157
|
+
'n': network,
|
|
158
|
+
'network': network,
|
|
159
|
+
'pd': pd,
|
|
160
|
+
'np': np,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
exec(constraints_dsl, exec_globals)
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"Failed to apply DSL constraints: {e}", exc_info=True)
|
|
167
|
+
|
|
168
|
+
def get_optimization_constraints(
|
|
169
|
+
self,
|
|
170
|
+
conn,
|
|
171
|
+
network_id: int,
|
|
172
|
+
scenario_id: Optional[int] = None
|
|
173
|
+
) -> List[Dict[str, Any]]:
|
|
174
|
+
"""
|
|
175
|
+
Get constraints that need to be applied during optimization (via extra_functionality).
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
conn: Database connection
|
|
179
|
+
network_id: ID of the network
|
|
180
|
+
scenario_id: Optional scenario ID
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of optimization constraints
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
# Load all constraints for this network
|
|
187
|
+
constraints = list_components_by_type(conn, network_id, 'CONSTRAINT')
|
|
188
|
+
|
|
189
|
+
if not constraints:
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
# Load constraint attributes and filter active optimization-time ones
|
|
193
|
+
optimization_constraints = []
|
|
194
|
+
for constraint in constraints:
|
|
195
|
+
try:
|
|
196
|
+
# Get constraint attributes
|
|
197
|
+
is_active = get_attribute(conn, constraint.id, 'is_active', scenario_id)
|
|
198
|
+
priority = get_attribute(conn, constraint.id, 'priority', scenario_id)
|
|
199
|
+
constraint_code = get_attribute(conn, constraint.id, 'constraint_code', scenario_id)
|
|
200
|
+
|
|
201
|
+
# Check if constraint is active
|
|
202
|
+
if is_active.variant == "Static":
|
|
203
|
+
# Extract boolean value from StaticValue
|
|
204
|
+
is_active_bool = False
|
|
205
|
+
if "Boolean" in is_active.static_value.data:
|
|
206
|
+
is_active_bool = is_active.static_value.data["Boolean"]
|
|
207
|
+
|
|
208
|
+
if is_active_bool:
|
|
209
|
+
# Extract code value
|
|
210
|
+
code_val = ""
|
|
211
|
+
if constraint_code.variant == "Static":
|
|
212
|
+
if "String" in constraint_code.static_value.data:
|
|
213
|
+
code_val = constraint_code.static_value.data["String"]
|
|
214
|
+
|
|
215
|
+
# Check if this is an optimization-time constraint
|
|
216
|
+
# (contains model access patterns like 'net.model' or 'network.model')
|
|
217
|
+
if 'net.model' in code_val or 'network.model' in code_val or 'n.model' in code_val:
|
|
218
|
+
# Extract priority value
|
|
219
|
+
priority_val = 0
|
|
220
|
+
if priority.variant == "Static":
|
|
221
|
+
if "Integer" in priority.static_value.data:
|
|
222
|
+
priority_val = priority.static_value.data["Integer"]
|
|
223
|
+
elif "Float" in priority.static_value.data:
|
|
224
|
+
priority_val = int(priority.static_value.data["Float"])
|
|
225
|
+
|
|
226
|
+
optimization_constraints.append({
|
|
227
|
+
'id': constraint.id,
|
|
228
|
+
'name': constraint.name,
|
|
229
|
+
'priority': priority_val,
|
|
230
|
+
'code': code_val
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.warning(f"Failed to load optimization constraint {constraint.name}: {e}")
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
# Sort constraints by priority (lower numbers first)
|
|
238
|
+
optimization_constraints.sort(key=lambda x: x['priority'])
|
|
239
|
+
return optimization_constraints
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
logger.error(f"Failed to get optimization constraints: {e}", exc_info=True)
|
|
243
|
+
return []
|
|
244
|
+
|
|
245
|
+
def apply_optimization_constraints(
|
|
246
|
+
self,
|
|
247
|
+
network: 'pypsa.Network',
|
|
248
|
+
snapshots,
|
|
249
|
+
constraints: List[Dict[str, Any]]
|
|
250
|
+
):
|
|
251
|
+
"""
|
|
252
|
+
Apply constraints during optimization (called via extra_functionality).
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
network: PyPSA Network object
|
|
256
|
+
snapshots: Network snapshots
|
|
257
|
+
constraints: List of constraint dictionaries
|
|
258
|
+
"""
|
|
259
|
+
try:
|
|
260
|
+
for constraint in constraints:
|
|
261
|
+
try:
|
|
262
|
+
logger.info(f"Applying optimization constraint '{constraint['name']}' (priority {constraint['priority']})")
|
|
263
|
+
logger.debug(f"Code: {constraint['code']}")
|
|
264
|
+
|
|
265
|
+
# Execute the constraint code with network and snapshots available
|
|
266
|
+
exec_globals = {
|
|
267
|
+
'net': network,
|
|
268
|
+
'network': network,
|
|
269
|
+
'n': network,
|
|
270
|
+
'snapshots': snapshots,
|
|
271
|
+
'pd': pd,
|
|
272
|
+
'np': np,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Execute the constraint code
|
|
276
|
+
exec(constraint['code'], exec_globals)
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
error_msg = f"Failed to execute optimization constraint '{constraint['name']}': {e}"
|
|
280
|
+
logger.error(error_msg, exc_info=True)
|
|
281
|
+
# Continue with other constraints instead of failing the entire solve
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.error(f"Failed to apply optimization constraints: {e}", exc_info=True)
|
|
286
|
+
|
|
287
|
+
def apply_optimization_constraint(
|
|
288
|
+
self,
|
|
289
|
+
network: 'pypsa.Network',
|
|
290
|
+
snapshots,
|
|
291
|
+
constraint: Dict[str, Any]
|
|
292
|
+
):
|
|
293
|
+
"""
|
|
294
|
+
Apply a single optimization constraint during solve.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
network: PyPSA Network object
|
|
298
|
+
snapshots: Network snapshots
|
|
299
|
+
constraint: Single constraint dictionary
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
logger.info(f"Applying optimization constraint '{constraint.get('name', 'unknown')}' (priority {constraint.get('priority', 0)})")
|
|
303
|
+
logger.debug(f"Code: {constraint.get('code', '')}")
|
|
304
|
+
|
|
305
|
+
# Execute the constraint code with network and snapshots available
|
|
306
|
+
exec_globals = {
|
|
307
|
+
'net': network,
|
|
308
|
+
'network': network,
|
|
309
|
+
'n': network,
|
|
310
|
+
'snapshots': snapshots,
|
|
311
|
+
'pd': pd,
|
|
312
|
+
'np': np,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# Execute the constraint code
|
|
316
|
+
exec(constraint.get('code', ''), exec_globals)
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
error_msg = f"Failed to execute optimization constraint '{constraint.get('name', 'unknown')}': {e}"
|
|
320
|
+
logger.error(error_msg, exc_info=True)
|
|
321
|
+
raise # Re-raise so solver can handle it
|