pyconvexity 0.3.8.post7__py3-none-any.whl → 0.4.1__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.
- pyconvexity/__init__.py +87 -46
- pyconvexity/_version.py +1 -1
- pyconvexity/core/__init__.py +3 -5
- pyconvexity/core/database.py +111 -103
- pyconvexity/core/errors.py +16 -10
- pyconvexity/core/types.py +61 -54
- pyconvexity/data/__init__.py +0 -1
- pyconvexity/data/loaders/cache.py +65 -64
- pyconvexity/data/schema/01_core_schema.sql +134 -234
- pyconvexity/data/schema/02_data_metadata.sql +38 -168
- pyconvexity/data/schema/03_validation_data.sql +327 -264
- pyconvexity/data/sources/gem.py +169 -139
- pyconvexity/io/__init__.py +4 -10
- pyconvexity/io/excel_exporter.py +694 -480
- pyconvexity/io/excel_importer.py +817 -545
- pyconvexity/io/netcdf_exporter.py +66 -61
- pyconvexity/io/netcdf_importer.py +850 -619
- pyconvexity/models/__init__.py +109 -59
- pyconvexity/models/attributes.py +197 -178
- pyconvexity/models/carriers.py +70 -67
- pyconvexity/models/components.py +260 -236
- pyconvexity/models/network.py +202 -284
- pyconvexity/models/results.py +65 -55
- pyconvexity/models/scenarios.py +58 -88
- pyconvexity/solvers/__init__.py +5 -5
- pyconvexity/solvers/pypsa/__init__.py +3 -3
- pyconvexity/solvers/pypsa/api.py +150 -134
- pyconvexity/solvers/pypsa/batch_loader.py +165 -162
- pyconvexity/solvers/pypsa/builder.py +390 -291
- pyconvexity/solvers/pypsa/constraints.py +184 -162
- pyconvexity/solvers/pypsa/solver.py +968 -666
- pyconvexity/solvers/pypsa/storage.py +1377 -671
- pyconvexity/timeseries.py +63 -60
- pyconvexity/validation/__init__.py +14 -6
- pyconvexity/validation/rules.py +95 -84
- pyconvexity-0.4.1.dist-info/METADATA +46 -0
- pyconvexity-0.4.1.dist-info/RECORD +42 -0
- pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
- pyconvexity/data/schema/04_scenario_schema.sql +0 -122
- pyconvexity/data/schema/migrate_add_geometries.sql +0 -73
- pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
- pyconvexity-0.3.8.post7.dist-info/METADATA +0 -138
- pyconvexity-0.3.8.post7.dist-info/RECORD +0 -49
- {pyconvexity-0.3.8.post7.dist-info → pyconvexity-0.4.1.dist-info}/WHEEL +0 -0
- {pyconvexity-0.3.8.post7.dist-info → pyconvexity-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -17,74 +17,72 @@ logger = logging.getLogger(__name__)
|
|
|
17
17
|
class ConstraintApplicator:
|
|
18
18
|
"""
|
|
19
19
|
Handles loading and applying custom constraints to PyPSA networks.
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
This class manages both pre-optimization constraints (applied to network structure)
|
|
22
22
|
and optimization-time constraints (applied during solving via extra_functionality).
|
|
23
23
|
"""
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
def apply_constraints(
|
|
26
26
|
self,
|
|
27
27
|
conn,
|
|
28
|
-
|
|
29
|
-
network: 'pypsa.Network',
|
|
28
|
+
network: "pypsa.Network",
|
|
30
29
|
scenario_id: Optional[int] = None,
|
|
31
|
-
constraints_dsl: Optional[str] = None
|
|
30
|
+
constraints_dsl: Optional[str] = None,
|
|
32
31
|
):
|
|
33
32
|
"""
|
|
34
|
-
Apply all constraints to the network.
|
|
35
|
-
|
|
33
|
+
Apply all constraints to the network (single network per database).
|
|
34
|
+
|
|
36
35
|
Args:
|
|
37
36
|
conn: Database connection
|
|
38
|
-
network_id: ID of the network
|
|
39
37
|
network: PyPSA Network object
|
|
40
|
-
scenario_id: Optional scenario ID
|
|
38
|
+
scenario_id: Optional scenario ID (NULL for base network)
|
|
41
39
|
constraints_dsl: Optional DSL constraints string
|
|
42
40
|
"""
|
|
43
41
|
# Apply database constraints
|
|
44
|
-
self._apply_database_constraints(conn,
|
|
45
|
-
|
|
42
|
+
self._apply_database_constraints(conn, network, scenario_id)
|
|
43
|
+
|
|
46
44
|
# Apply DSL constraints if provided
|
|
47
45
|
if constraints_dsl:
|
|
48
46
|
self._apply_dsl_constraints(network, constraints_dsl)
|
|
49
|
-
|
|
47
|
+
|
|
50
48
|
def _detect_constraint_type(self, constraint_code: str) -> str:
|
|
51
49
|
"""
|
|
52
50
|
Detect if constraint is network-modification or model-constraint type.
|
|
53
|
-
|
|
51
|
+
|
|
54
52
|
Args:
|
|
55
53
|
constraint_code: The constraint code to analyze
|
|
56
|
-
|
|
54
|
+
|
|
57
55
|
Returns:
|
|
58
56
|
"model_constraint" or "network_modification"
|
|
59
57
|
"""
|
|
60
58
|
# Type 2 indicators (model constraints) - need access to optimization model
|
|
61
59
|
model_indicators = [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
"n.optimize.create_model()",
|
|
61
|
+
"m.variables",
|
|
62
|
+
"m.add_constraints",
|
|
63
|
+
"gen_p =",
|
|
64
|
+
"constraint_expr =",
|
|
65
|
+
"LinearExpression",
|
|
66
|
+
"linopy",
|
|
67
|
+
"Generator-p",
|
|
68
|
+
"lhs <=",
|
|
69
|
+
"constraint_expr =",
|
|
70
|
+
"model.variables",
|
|
71
|
+
"model.add_constraints",
|
|
74
72
|
]
|
|
75
|
-
|
|
73
|
+
|
|
76
74
|
# Type 1 indicators (network modifications) - modify network directly
|
|
77
75
|
network_indicators = [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
"n.generators.loc",
|
|
77
|
+
"n.add(",
|
|
78
|
+
"n.buses.",
|
|
79
|
+
"n.lines.",
|
|
80
|
+
"network.generators.loc",
|
|
81
|
+
"network.add(",
|
|
82
|
+
"network.buses.",
|
|
83
|
+
"network.lines.",
|
|
86
84
|
]
|
|
87
|
-
|
|
85
|
+
|
|
88
86
|
# Check for model constraint indicators first (more specific)
|
|
89
87
|
if any(indicator in constraint_code for indicator in model_indicators):
|
|
90
88
|
return "model_constraint"
|
|
@@ -95,117 +93,133 @@ class ConstraintApplicator:
|
|
|
95
93
|
return "network_modification"
|
|
96
94
|
|
|
97
95
|
def _apply_database_constraints(
|
|
98
|
-
self,
|
|
99
|
-
conn,
|
|
100
|
-
network_id: int,
|
|
101
|
-
network: 'pypsa.Network',
|
|
102
|
-
scenario_id: Optional[int]
|
|
96
|
+
self, conn, network: "pypsa.Network", scenario_id: Optional[int]
|
|
103
97
|
):
|
|
104
|
-
"""Load and apply custom constraints from the database in priority order."""
|
|
98
|
+
"""Load and apply custom constraints from the database in priority order (single network per database)."""
|
|
105
99
|
try:
|
|
106
100
|
# Load all constraints for this network
|
|
107
|
-
constraints = list_components_by_type(conn,
|
|
108
|
-
|
|
101
|
+
constraints = list_components_by_type(conn, "CONSTRAINT")
|
|
102
|
+
|
|
109
103
|
if not constraints:
|
|
110
104
|
return
|
|
111
|
-
|
|
105
|
+
|
|
112
106
|
# Load constraint attributes and filter active ones
|
|
113
107
|
active_constraints = []
|
|
114
108
|
model_constraints = []
|
|
115
109
|
network_constraints = []
|
|
116
|
-
|
|
110
|
+
|
|
117
111
|
for constraint in constraints:
|
|
118
112
|
try:
|
|
119
113
|
# Get constraint attributes
|
|
120
|
-
is_active = get_attribute(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
is_active = get_attribute(
|
|
115
|
+
conn, constraint.id, "is_active", scenario_id
|
|
116
|
+
)
|
|
117
|
+
priority = get_attribute(
|
|
118
|
+
conn, constraint.id, "priority", scenario_id
|
|
119
|
+
)
|
|
120
|
+
constraint_code = get_attribute(
|
|
121
|
+
conn, constraint.id, "constraint_code", scenario_id
|
|
122
|
+
)
|
|
123
|
+
|
|
124
124
|
# Check if constraint is active
|
|
125
125
|
if is_active.variant == "Static":
|
|
126
126
|
# Extract boolean value from StaticValue
|
|
127
127
|
is_active_bool = False
|
|
128
128
|
if "Boolean" in is_active.static_value.data:
|
|
129
129
|
is_active_bool = is_active.static_value.data["Boolean"]
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
if is_active_bool:
|
|
132
132
|
# Extract code value
|
|
133
133
|
code_val = ""
|
|
134
134
|
if constraint_code.variant == "Static":
|
|
135
135
|
if "String" in constraint_code.static_value.data:
|
|
136
|
-
code_val = constraint_code.static_value.data[
|
|
137
|
-
|
|
136
|
+
code_val = constraint_code.static_value.data[
|
|
137
|
+
"String"
|
|
138
|
+
]
|
|
139
|
+
|
|
138
140
|
# Extract priority value
|
|
139
141
|
priority_val = 0
|
|
140
142
|
if priority.variant == "Static":
|
|
141
143
|
if "Integer" in priority.static_value.data:
|
|
142
144
|
priority_val = priority.static_value.data["Integer"]
|
|
143
145
|
elif "Float" in priority.static_value.data:
|
|
144
|
-
priority_val = int(
|
|
145
|
-
|
|
146
|
+
priority_val = int(
|
|
147
|
+
priority.static_value.data["Float"]
|
|
148
|
+
)
|
|
149
|
+
|
|
146
150
|
constraint_dict = {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
"id": constraint.id,
|
|
152
|
+
"name": constraint.name,
|
|
153
|
+
"priority": priority_val,
|
|
154
|
+
"code": code_val,
|
|
155
|
+
"constraint_code": code_val, # For compatibility
|
|
152
156
|
}
|
|
153
|
-
|
|
157
|
+
|
|
154
158
|
# Detect constraint type and separate them
|
|
155
159
|
constraint_type = self._detect_constraint_type(code_val)
|
|
156
160
|
if constraint_type == "model_constraint":
|
|
157
161
|
model_constraints.append(constraint_dict)
|
|
158
|
-
logger.info(
|
|
162
|
+
logger.info(
|
|
163
|
+
f"Detected model constraint: {constraint.name}"
|
|
164
|
+
)
|
|
159
165
|
else:
|
|
160
166
|
network_constraints.append(constraint_dict)
|
|
161
|
-
logger.info(
|
|
162
|
-
|
|
167
|
+
logger.info(
|
|
168
|
+
f"Detected network constraint: {constraint.name}"
|
|
169
|
+
)
|
|
170
|
+
|
|
163
171
|
except Exception as e:
|
|
164
172
|
logger.warning(f"Failed to load constraint {constraint.name}: {e}")
|
|
165
173
|
continue
|
|
166
|
-
|
|
174
|
+
|
|
167
175
|
if not model_constraints and not network_constraints:
|
|
168
176
|
return
|
|
169
|
-
|
|
170
|
-
logger.info(
|
|
171
|
-
|
|
177
|
+
|
|
178
|
+
logger.info(
|
|
179
|
+
f"Constraint breakdown: {len(model_constraints)} model constraints, {len(network_constraints)} network constraints"
|
|
180
|
+
)
|
|
181
|
+
|
|
172
182
|
# Apply network constraints ONLY (they modify the network structure before solve)
|
|
173
183
|
# Model constraints will be applied later by the solver via extra_functionality
|
|
174
184
|
if network_constraints:
|
|
175
|
-
network_constraints.sort(key=lambda x: x[
|
|
185
|
+
network_constraints.sort(key=lambda x: x["priority"])
|
|
176
186
|
for constraint in network_constraints:
|
|
177
187
|
try:
|
|
178
|
-
logger.info(
|
|
179
|
-
|
|
188
|
+
logger.info(
|
|
189
|
+
f"Executing network constraint '{constraint['name']}' (priority {constraint['priority']})"
|
|
190
|
+
)
|
|
191
|
+
|
|
180
192
|
# Execute the constraint code in the normal Python environment
|
|
181
193
|
exec_globals = {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
194
|
+
"n": network,
|
|
195
|
+
"network": network,
|
|
196
|
+
"pd": pd,
|
|
197
|
+
"np": np,
|
|
186
198
|
}
|
|
187
|
-
|
|
199
|
+
|
|
188
200
|
# Execute the constraint code
|
|
189
|
-
exec(constraint[
|
|
190
|
-
|
|
201
|
+
exec(constraint["code"], exec_globals)
|
|
202
|
+
|
|
191
203
|
except Exception as e:
|
|
192
204
|
error_msg = f"Failed to execute network constraint '{constraint['name']}': {e}"
|
|
193
205
|
logger.error(error_msg, exc_info=True)
|
|
194
206
|
# Continue with other constraints instead of failing the entire solve
|
|
195
207
|
continue
|
|
196
|
-
|
|
208
|
+
|
|
197
209
|
# Skip model constraints here - they will be applied by the solver during optimization
|
|
198
210
|
# via extra_functionality to ensure they have access to the actual optimization model
|
|
199
211
|
if model_constraints:
|
|
200
|
-
logger.info(
|
|
201
|
-
|
|
212
|
+
logger.info(
|
|
213
|
+
f"Skipping {len(model_constraints)} model constraints - will be applied during solve"
|
|
214
|
+
)
|
|
215
|
+
|
|
202
216
|
except Exception as e:
|
|
203
217
|
logger.error(f"Failed to apply custom constraints: {e}", exc_info=True)
|
|
204
|
-
|
|
205
|
-
def _apply_dsl_constraints(self, network:
|
|
218
|
+
|
|
219
|
+
def _apply_dsl_constraints(self, network: "pypsa.Network", constraints_dsl: str):
|
|
206
220
|
"""
|
|
207
221
|
Apply DSL constraints to the network.
|
|
208
|
-
|
|
222
|
+
|
|
209
223
|
Args:
|
|
210
224
|
network: PyPSA Network object
|
|
211
225
|
constraints_dsl: DSL constraints string
|
|
@@ -213,105 +227,110 @@ class ConstraintApplicator:
|
|
|
213
227
|
try:
|
|
214
228
|
logger.info("Applying DSL constraints")
|
|
215
229
|
logger.debug(f"DSL Code: {constraints_dsl}")
|
|
216
|
-
|
|
230
|
+
|
|
217
231
|
# Execute DSL constraints
|
|
218
232
|
exec_globals = {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
"n": network,
|
|
234
|
+
"network": network,
|
|
235
|
+
"pd": pd,
|
|
236
|
+
"np": np,
|
|
223
237
|
}
|
|
224
|
-
|
|
238
|
+
|
|
225
239
|
exec(constraints_dsl, exec_globals)
|
|
226
|
-
|
|
240
|
+
|
|
227
241
|
except Exception as e:
|
|
228
242
|
logger.error(f"Failed to apply DSL constraints: {e}", exc_info=True)
|
|
229
|
-
|
|
243
|
+
|
|
230
244
|
def get_optimization_constraints(
|
|
231
|
-
self,
|
|
232
|
-
conn,
|
|
233
|
-
network_id: int,
|
|
234
|
-
scenario_id: Optional[int] = None
|
|
245
|
+
self, conn, scenario_id: Optional[int] = None
|
|
235
246
|
) -> List[Dict[str, Any]]:
|
|
236
247
|
"""
|
|
237
|
-
Get ALL active constraints for optimization-time application.
|
|
248
|
+
Get ALL active constraints for optimization-time application (single network per database).
|
|
238
249
|
The solver will determine which are model constraints vs network constraints.
|
|
239
|
-
|
|
250
|
+
|
|
240
251
|
Args:
|
|
241
252
|
conn: Database connection
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
scenario_id: Optional scenario ID (NULL for base network)
|
|
254
|
+
|
|
245
255
|
Returns:
|
|
246
256
|
List of all active constraints
|
|
247
257
|
"""
|
|
248
258
|
try:
|
|
249
259
|
# Load all constraints for this network
|
|
250
|
-
constraints = list_components_by_type(conn,
|
|
251
|
-
|
|
260
|
+
constraints = list_components_by_type(conn, "CONSTRAINT")
|
|
261
|
+
|
|
252
262
|
if not constraints:
|
|
253
263
|
return []
|
|
254
|
-
|
|
264
|
+
|
|
255
265
|
# Load constraint attributes and filter active ones
|
|
256
266
|
optimization_constraints = []
|
|
257
267
|
for constraint in constraints:
|
|
258
268
|
try:
|
|
259
269
|
# Get constraint attributes
|
|
260
|
-
is_active = get_attribute(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
is_active = get_attribute(
|
|
271
|
+
conn, constraint.id, "is_active", scenario_id
|
|
272
|
+
)
|
|
273
|
+
priority = get_attribute(
|
|
274
|
+
conn, constraint.id, "priority", scenario_id
|
|
275
|
+
)
|
|
276
|
+
constraint_code = get_attribute(
|
|
277
|
+
conn, constraint.id, "constraint_code", scenario_id
|
|
278
|
+
)
|
|
279
|
+
|
|
264
280
|
# Check if constraint is active
|
|
265
281
|
if is_active.variant == "Static":
|
|
266
282
|
# Extract boolean value from StaticValue
|
|
267
283
|
is_active_bool = False
|
|
268
284
|
if "Boolean" in is_active.static_value.data:
|
|
269
285
|
is_active_bool = is_active.static_value.data["Boolean"]
|
|
270
|
-
|
|
286
|
+
|
|
271
287
|
if is_active_bool:
|
|
272
288
|
# Extract code value
|
|
273
289
|
code_val = ""
|
|
274
290
|
if constraint_code.variant == "Static":
|
|
275
291
|
if "String" in constraint_code.static_value.data:
|
|
276
|
-
code_val = constraint_code.static_value.data[
|
|
277
|
-
|
|
292
|
+
code_val = constraint_code.static_value.data[
|
|
293
|
+
"String"
|
|
294
|
+
]
|
|
295
|
+
|
|
278
296
|
# Extract priority value
|
|
279
297
|
priority_val = 0
|
|
280
298
|
if priority.variant == "Static":
|
|
281
299
|
if "Integer" in priority.static_value.data:
|
|
282
300
|
priority_val = priority.static_value.data["Integer"]
|
|
283
301
|
elif "Float" in priority.static_value.data:
|
|
284
|
-
priority_val = int(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
302
|
+
priority_val = int(
|
|
303
|
+
priority.static_value.data["Float"]
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
optimization_constraints.append(
|
|
307
|
+
{
|
|
308
|
+
"id": constraint.id,
|
|
309
|
+
"name": constraint.name,
|
|
310
|
+
"priority": priority_val,
|
|
311
|
+
"constraint_code": code_val, # Use consistent key name
|
|
312
|
+
"code": code_val, # Keep both for compatibility
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
|
|
294
316
|
except Exception as e:
|
|
295
317
|
logger.warning(f"Failed to load constraint {constraint.name}: {e}")
|
|
296
318
|
continue
|
|
297
|
-
|
|
319
|
+
|
|
298
320
|
# Sort constraints by priority (lower numbers first)
|
|
299
|
-
optimization_constraints.sort(key=lambda x: x[
|
|
321
|
+
optimization_constraints.sort(key=lambda x: x["priority"])
|
|
300
322
|
return optimization_constraints
|
|
301
|
-
|
|
323
|
+
|
|
302
324
|
except Exception as e:
|
|
303
325
|
logger.error(f"Failed to get optimization constraints: {e}", exc_info=True)
|
|
304
326
|
return []
|
|
305
|
-
|
|
327
|
+
|
|
306
328
|
def apply_optimization_constraints(
|
|
307
|
-
self,
|
|
308
|
-
network: 'pypsa.Network',
|
|
309
|
-
snapshots,
|
|
310
|
-
constraints: List[Dict[str, Any]]
|
|
329
|
+
self, network: "pypsa.Network", snapshots, constraints: List[Dict[str, Any]]
|
|
311
330
|
):
|
|
312
331
|
"""
|
|
313
332
|
Apply constraints during optimization (called via extra_functionality).
|
|
314
|
-
|
|
333
|
+
|
|
315
334
|
Args:
|
|
316
335
|
network: PyPSA Network object
|
|
317
336
|
snapshots: Network snapshots
|
|
@@ -320,64 +339,67 @@ class ConstraintApplicator:
|
|
|
320
339
|
try:
|
|
321
340
|
for constraint in constraints:
|
|
322
341
|
try:
|
|
323
|
-
logger.info(
|
|
342
|
+
logger.info(
|
|
343
|
+
f"Applying optimization constraint '{constraint['name']}' (priority {constraint['priority']})"
|
|
344
|
+
)
|
|
324
345
|
logger.debug(f"Code: {constraint['code']}")
|
|
325
|
-
|
|
346
|
+
|
|
326
347
|
# Execute the constraint code with network and snapshots available
|
|
327
348
|
exec_globals = {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
349
|
+
"net": network,
|
|
350
|
+
"network": network,
|
|
351
|
+
"n": network,
|
|
352
|
+
"snapshots": snapshots,
|
|
353
|
+
"pd": pd,
|
|
354
|
+
"np": np,
|
|
334
355
|
}
|
|
335
|
-
|
|
356
|
+
|
|
336
357
|
# Execute the constraint code
|
|
337
|
-
exec(constraint[
|
|
338
|
-
|
|
358
|
+
exec(constraint["code"], exec_globals)
|
|
359
|
+
|
|
339
360
|
except Exception as e:
|
|
340
361
|
error_msg = f"Failed to execute optimization constraint '{constraint['name']}': {e}"
|
|
341
362
|
logger.error(error_msg, exc_info=True)
|
|
342
363
|
# Continue with other constraints instead of failing the entire solve
|
|
343
364
|
continue
|
|
344
|
-
|
|
365
|
+
|
|
345
366
|
except Exception as e:
|
|
346
|
-
logger.error(
|
|
347
|
-
|
|
367
|
+
logger.error(
|
|
368
|
+
f"Failed to apply optimization constraints: {e}", exc_info=True
|
|
369
|
+
)
|
|
370
|
+
|
|
348
371
|
def apply_optimization_constraint(
|
|
349
|
-
self,
|
|
350
|
-
network: 'pypsa.Network',
|
|
351
|
-
snapshots,
|
|
352
|
-
constraint: Dict[str, Any]
|
|
372
|
+
self, network: "pypsa.Network", snapshots, constraint: Dict[str, Any]
|
|
353
373
|
):
|
|
354
374
|
"""
|
|
355
375
|
Apply a single optimization constraint during solve.
|
|
356
|
-
|
|
376
|
+
|
|
357
377
|
Args:
|
|
358
378
|
network: PyPSA Network object
|
|
359
379
|
snapshots: Network snapshots
|
|
360
380
|
constraint: Single constraint dictionary
|
|
361
381
|
"""
|
|
362
382
|
try:
|
|
363
|
-
logger.info(
|
|
383
|
+
logger.info(
|
|
384
|
+
f"Applying optimization constraint '{constraint.get('name', 'unknown')}' (priority {constraint.get('priority', 0)})"
|
|
385
|
+
)
|
|
364
386
|
logger.debug(f"Code: {constraint.get('code', '')}")
|
|
365
|
-
|
|
387
|
+
|
|
366
388
|
# Execute the constraint code with network and snapshots available
|
|
367
389
|
exec_globals = {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
390
|
+
"net": network,
|
|
391
|
+
"network": network,
|
|
392
|
+
"n": network,
|
|
393
|
+
"snapshots": snapshots,
|
|
394
|
+
"pd": pd,
|
|
395
|
+
"np": np,
|
|
396
|
+
"xr": __import__("xarray"),
|
|
375
397
|
}
|
|
376
|
-
|
|
398
|
+
|
|
377
399
|
# Execute the constraint code
|
|
378
|
-
exec(constraint.get(
|
|
379
|
-
|
|
400
|
+
exec(constraint.get("code", ""), exec_globals)
|
|
401
|
+
|
|
380
402
|
except Exception as e:
|
|
381
403
|
error_msg = f"Failed to execute optimization constraint '{constraint.get('name', 'unknown')}': {e}"
|
|
382
404
|
logger.error(error_msg, exc_info=True)
|
|
383
|
-
raise # Re-raise so solver can handle it
|
|
405
|
+
raise # Re-raise so solver can handle it
|