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.
Files changed (48) hide show
  1. pyconvexity/__init__.py +87 -46
  2. pyconvexity/_version.py +1 -1
  3. pyconvexity/core/__init__.py +3 -5
  4. pyconvexity/core/database.py +111 -103
  5. pyconvexity/core/errors.py +16 -10
  6. pyconvexity/core/types.py +61 -54
  7. pyconvexity/data/__init__.py +0 -1
  8. pyconvexity/data/loaders/cache.py +65 -64
  9. pyconvexity/data/schema/01_core_schema.sql +134 -234
  10. pyconvexity/data/schema/02_data_metadata.sql +38 -168
  11. pyconvexity/data/schema/03_validation_data.sql +327 -264
  12. pyconvexity/data/sources/gem.py +169 -139
  13. pyconvexity/io/__init__.py +4 -10
  14. pyconvexity/io/excel_exporter.py +694 -480
  15. pyconvexity/io/excel_importer.py +817 -545
  16. pyconvexity/io/netcdf_exporter.py +66 -61
  17. pyconvexity/io/netcdf_importer.py +850 -619
  18. pyconvexity/models/__init__.py +109 -59
  19. pyconvexity/models/attributes.py +197 -178
  20. pyconvexity/models/carriers.py +70 -67
  21. pyconvexity/models/components.py +260 -236
  22. pyconvexity/models/network.py +202 -284
  23. pyconvexity/models/results.py +65 -55
  24. pyconvexity/models/scenarios.py +58 -88
  25. pyconvexity/solvers/__init__.py +5 -5
  26. pyconvexity/solvers/pypsa/__init__.py +3 -3
  27. pyconvexity/solvers/pypsa/api.py +150 -134
  28. pyconvexity/solvers/pypsa/batch_loader.py +165 -162
  29. pyconvexity/solvers/pypsa/builder.py +390 -291
  30. pyconvexity/solvers/pypsa/constraints.py +184 -162
  31. pyconvexity/solvers/pypsa/solver.py +968 -666
  32. pyconvexity/solvers/pypsa/storage.py +1377 -671
  33. pyconvexity/timeseries.py +63 -60
  34. pyconvexity/validation/__init__.py +14 -6
  35. pyconvexity/validation/rules.py +95 -84
  36. pyconvexity-0.4.1.dist-info/METADATA +46 -0
  37. pyconvexity-0.4.1.dist-info/RECORD +42 -0
  38. pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
  39. pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
  40. pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
  41. pyconvexity/data/schema/04_scenario_schema.sql +0 -122
  42. pyconvexity/data/schema/migrate_add_geometries.sql +0 -73
  43. pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
  44. pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
  45. pyconvexity-0.3.8.post7.dist-info/METADATA +0 -138
  46. pyconvexity-0.3.8.post7.dist-info/RECORD +0 -49
  47. {pyconvexity-0.3.8.post7.dist-info → pyconvexity-0.4.1.dist-info}/WHEEL +0 -0
  48. {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
- network_id: int,
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, network_id, network, scenario_id)
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
- 'n.optimize.create_model()',
63
- 'm.variables',
64
- 'm.add_constraints',
65
- 'gen_p =',
66
- 'constraint_expr =',
67
- 'LinearExpression',
68
- 'linopy',
69
- 'Generator-p',
70
- 'lhs <=',
71
- 'constraint_expr =',
72
- 'model.variables',
73
- 'model.add_constraints'
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
- 'n.generators.loc',
79
- 'n.add(',
80
- 'n.buses.',
81
- 'n.lines.',
82
- 'network.generators.loc',
83
- 'network.add(',
84
- 'network.buses.',
85
- 'network.lines.'
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, network_id, 'CONSTRAINT')
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(conn, constraint.id, 'is_active', scenario_id)
121
- priority = get_attribute(conn, constraint.id, 'priority', scenario_id)
122
- constraint_code = get_attribute(conn, constraint.id, 'constraint_code', scenario_id)
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["String"]
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(priority.static_value.data["Float"])
145
-
146
+ priority_val = int(
147
+ priority.static_value.data["Float"]
148
+ )
149
+
146
150
  constraint_dict = {
147
- 'id': constraint.id,
148
- 'name': constraint.name,
149
- 'priority': priority_val,
150
- 'code': code_val,
151
- 'constraint_code': code_val # For compatibility
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(f"Detected model constraint: {constraint.name}")
162
+ logger.info(
163
+ f"Detected model constraint: {constraint.name}"
164
+ )
159
165
  else:
160
166
  network_constraints.append(constraint_dict)
161
- logger.info(f"Detected network constraint: {constraint.name}")
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(f"Constraint breakdown: {len(model_constraints)} model constraints, {len(network_constraints)} network constraints")
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['priority'])
185
+ network_constraints.sort(key=lambda x: x["priority"])
176
186
  for constraint in network_constraints:
177
187
  try:
178
- logger.info(f"Executing network constraint '{constraint['name']}' (priority {constraint['priority']})")
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
- 'n': network,
183
- 'network': network,
184
- 'pd': pd,
185
- 'np': np,
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['code'], exec_globals)
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(f"Skipping {len(model_constraints)} model constraints - will be applied during solve")
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: 'pypsa.Network', constraints_dsl: str):
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
- 'n': network,
220
- 'network': network,
221
- 'pd': pd,
222
- 'np': np,
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
- network_id: ID of the network
243
- scenario_id: Optional scenario ID
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, network_id, 'CONSTRAINT')
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(conn, constraint.id, 'is_active', scenario_id)
261
- priority = get_attribute(conn, constraint.id, 'priority', scenario_id)
262
- constraint_code = get_attribute(conn, constraint.id, 'constraint_code', scenario_id)
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["String"]
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(priority.static_value.data["Float"])
285
-
286
- optimization_constraints.append({
287
- 'id': constraint.id,
288
- 'name': constraint.name,
289
- 'priority': priority_val,
290
- 'constraint_code': code_val, # Use consistent key name
291
- 'code': code_val # Keep both for compatibility
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['priority'])
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(f"Applying optimization constraint '{constraint['name']}' (priority {constraint['priority']})")
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
- 'net': network,
329
- 'network': network,
330
- 'n': network,
331
- 'snapshots': snapshots,
332
- 'pd': pd,
333
- 'np': np,
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['code'], exec_globals)
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(f"Failed to apply optimization constraints: {e}", exc_info=True)
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(f"Applying optimization constraint '{constraint.get('name', 'unknown')}' (priority {constraint.get('priority', 0)})")
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
- 'net': network,
369
- 'network': network,
370
- 'n': network,
371
- 'snapshots': snapshots,
372
- 'pd': pd,
373
- 'np': np,
374
- 'xr': __import__('xarray'),
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('code', ''), exec_globals)
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