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.

Files changed (33) hide show
  1. pyconvexity/__init__.py +30 -6
  2. pyconvexity/data/README.md +101 -0
  3. pyconvexity/data/__init__.py +18 -0
  4. pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
  5. pyconvexity/data/loaders/__init__.py +3 -0
  6. pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
  7. pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
  8. pyconvexity/data/loaders/cache.py +212 -0
  9. pyconvexity/data/sources/__init__.py +5 -0
  10. pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
  11. pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
  12. pyconvexity/data/sources/gem.py +412 -0
  13. pyconvexity/io/__init__.py +32 -0
  14. pyconvexity/io/excel_exporter.py +991 -0
  15. pyconvexity/io/excel_importer.py +1112 -0
  16. pyconvexity/io/netcdf_exporter.py +192 -0
  17. pyconvexity/io/netcdf_importer.py +599 -0
  18. pyconvexity/models/__init__.py +7 -0
  19. pyconvexity/models/components.py +3 -0
  20. pyconvexity/models/scenarios.py +177 -0
  21. pyconvexity/solvers/__init__.py +29 -0
  22. pyconvexity/solvers/pypsa/__init__.py +24 -0
  23. pyconvexity/solvers/pypsa/api.py +398 -0
  24. pyconvexity/solvers/pypsa/batch_loader.py +311 -0
  25. pyconvexity/solvers/pypsa/builder.py +656 -0
  26. pyconvexity/solvers/pypsa/constraints.py +321 -0
  27. pyconvexity/solvers/pypsa/solver.py +1255 -0
  28. pyconvexity/solvers/pypsa/storage.py +2207 -0
  29. {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.3.dist-info}/METADATA +5 -2
  30. pyconvexity-0.1.3.dist-info/RECORD +45 -0
  31. pyconvexity-0.1.2.dist-info/RECORD +0 -20
  32. {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.3.dist-info}/WHEEL +0 -0
  33. {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