pyconvexity 0.3.8__tar.gz → 0.3.8.post1__tar.gz
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-0.3.8 → pyconvexity-0.3.8.post1}/PKG-INFO +1 -1
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/pyproject.toml +2 -2
- pyconvexity-0.3.8.post1/src/pyconvexity/_version.py +1 -0
- pyconvexity-0.3.8.post1/src/pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity-0.3.8.post1/src/pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity-0.3.8.post1/src/pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
- pyconvexity-0.3.8.post1/src/pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity-0.3.8.post1/src/pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/api.py +2 -6
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/constraints.py +178 -43
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/solver.py +75 -4
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity.egg-info/PKG-INFO +1 -1
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity.egg-info/SOURCES.txt +5 -0
- pyconvexity-0.3.8/src/pyconvexity/_version.py +0 -1
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/README.md +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/setup.cfg +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/core/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/core/database.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/core/errors.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/core/types.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/README.md +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/loaders/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/loaders/cache.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/01_core_schema.sql +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/02_data_metadata.sql +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/03_validation_data.sql +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/04_scenario_schema.sql +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/sources/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/sources/gem.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/io/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/io/excel_exporter.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/io/excel_importer.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/io/netcdf_exporter.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/io/netcdf_importer.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/models/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/models/attributes.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/models/components.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/models/network.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/models/scenarios.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/batch_loader.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/builder.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/solvers/pypsa/storage.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/timeseries.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/validation/__init__.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/validation/rules.py +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity.egg-info/dependency_links.txt +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity.egg-info/requires.txt +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity.egg-info/top_level.txt +0 -0
- {pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/tests/test_core_types.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyconvexity"
|
|
7
|
-
version = "0.3.8"
|
|
7
|
+
version = "0.3.8.post1"
|
|
8
8
|
description = "Python library for energy system modeling and optimization with PyPSA"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -81,7 +81,7 @@ profile = "black"
|
|
|
81
81
|
line_length = 100
|
|
82
82
|
|
|
83
83
|
[tool.mypy]
|
|
84
|
-
python_version = "0.3.8"
|
|
84
|
+
python_version = "0.3.8.post1"
|
|
85
85
|
warn_return_any = true
|
|
86
86
|
warn_unused_configs = true
|
|
87
87
|
disallow_untyped_defs = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.8.post1"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -76,14 +76,10 @@ def solve_network(
|
|
|
76
76
|
if progress_callback:
|
|
77
77
|
progress_callback(50, f"Network built: {len(network.buses)} buses, {len(network.generators)} generators")
|
|
78
78
|
|
|
79
|
-
#
|
|
79
|
+
# Create constraint applicator (constraints will be applied during solve via extra_functionality)
|
|
80
80
|
constraint_applicator = ConstraintApplicator()
|
|
81
|
-
if constraints_dsl or progress_callback:
|
|
82
|
-
if progress_callback:
|
|
83
|
-
progress_callback(60, "Applying constraints...")
|
|
84
|
-
constraint_applicator.apply_constraints(conn, network_id, network, scenario_id, constraints_dsl)
|
|
85
81
|
|
|
86
|
-
# Solve network
|
|
82
|
+
# Solve network (constraints are applied during optimization)
|
|
87
83
|
if progress_callback:
|
|
88
84
|
progress_callback(70, f"Solving with {solver_name}...")
|
|
89
85
|
|
|
@@ -47,6 +47,53 @@ class ConstraintApplicator:
|
|
|
47
47
|
if constraints_dsl:
|
|
48
48
|
self._apply_dsl_constraints(network, constraints_dsl)
|
|
49
49
|
|
|
50
|
+
def _detect_constraint_type(self, constraint_code: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Detect if constraint is network-modification or model-constraint type.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
constraint_code: The constraint code to analyze
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
"model_constraint" or "network_modification"
|
|
59
|
+
"""
|
|
60
|
+
# Type 2 indicators (model constraints) - need access to optimization model
|
|
61
|
+
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'
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Type 1 indicators (network modifications) - modify network directly
|
|
77
|
+
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.'
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# Check for model constraint indicators first (more specific)
|
|
89
|
+
if any(indicator in constraint_code for indicator in model_indicators):
|
|
90
|
+
return "model_constraint"
|
|
91
|
+
elif any(indicator in constraint_code for indicator in network_indicators):
|
|
92
|
+
return "network_modification"
|
|
93
|
+
else:
|
|
94
|
+
# Default to network_modification for safety (existing behavior)
|
|
95
|
+
return "network_modification"
|
|
96
|
+
|
|
50
97
|
def _apply_database_constraints(
|
|
51
98
|
self,
|
|
52
99
|
conn,
|
|
@@ -64,6 +111,9 @@ class ConstraintApplicator:
|
|
|
64
111
|
|
|
65
112
|
# Load constraint attributes and filter active ones
|
|
66
113
|
active_constraints = []
|
|
114
|
+
model_constraints = []
|
|
115
|
+
network_constraints = []
|
|
116
|
+
|
|
67
117
|
for constraint in constraints:
|
|
68
118
|
try:
|
|
69
119
|
# Get constraint attributes
|
|
@@ -79,16 +129,12 @@ class ConstraintApplicator:
|
|
|
79
129
|
is_active_bool = is_active.static_value.data["Boolean"]
|
|
80
130
|
|
|
81
131
|
if is_active_bool:
|
|
82
|
-
# Extract code value
|
|
132
|
+
# Extract code value
|
|
83
133
|
code_val = ""
|
|
84
134
|
if constraint_code.variant == "Static":
|
|
85
135
|
if "String" in constraint_code.static_value.data:
|
|
86
136
|
code_val = constraint_code.static_value.data["String"]
|
|
87
137
|
|
|
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
138
|
# Extract priority value
|
|
93
139
|
priority_val = 0
|
|
94
140
|
if priority.variant == "Static":
|
|
@@ -97,48 +143,137 @@ class ConstraintApplicator:
|
|
|
97
143
|
elif "Float" in priority.static_value.data:
|
|
98
144
|
priority_val = int(priority.static_value.data["Float"])
|
|
99
145
|
|
|
100
|
-
|
|
146
|
+
constraint_dict = {
|
|
101
147
|
'id': constraint.id,
|
|
102
148
|
'name': constraint.name,
|
|
103
149
|
'priority': priority_val,
|
|
104
|
-
'code': code_val
|
|
105
|
-
|
|
150
|
+
'code': code_val,
|
|
151
|
+
'constraint_code': code_val # For compatibility
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Detect constraint type and separate them
|
|
155
|
+
constraint_type = self._detect_constraint_type(code_val)
|
|
156
|
+
if constraint_type == "model_constraint":
|
|
157
|
+
model_constraints.append(constraint_dict)
|
|
158
|
+
logger.info(f"Detected model constraint: {constraint.name}")
|
|
159
|
+
else:
|
|
160
|
+
network_constraints.append(constraint_dict)
|
|
161
|
+
logger.info(f"Detected network constraint: {constraint.name}")
|
|
106
162
|
|
|
107
163
|
except Exception as e:
|
|
108
164
|
logger.warning(f"Failed to load constraint {constraint.name}: {e}")
|
|
109
165
|
continue
|
|
110
166
|
|
|
111
|
-
if not
|
|
167
|
+
if not model_constraints and not network_constraints:
|
|
112
168
|
return
|
|
113
169
|
|
|
114
|
-
|
|
115
|
-
|
|
170
|
+
logger.info(f"Constraint breakdown: {len(model_constraints)} model constraints, {len(network_constraints)} network constraints")
|
|
171
|
+
|
|
172
|
+
# Apply network constraints first (they modify the network structure)
|
|
173
|
+
if network_constraints:
|
|
174
|
+
network_constraints.sort(key=lambda x: x['priority'])
|
|
175
|
+
for constraint in network_constraints:
|
|
176
|
+
try:
|
|
177
|
+
logger.info(f"Executing network constraint '{constraint['name']}' (priority {constraint['priority']})")
|
|
178
|
+
|
|
179
|
+
# Execute the constraint code in the normal Python environment
|
|
180
|
+
exec_globals = {
|
|
181
|
+
'n': network,
|
|
182
|
+
'network': network,
|
|
183
|
+
'pd': pd,
|
|
184
|
+
'np': np,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Execute the constraint code
|
|
188
|
+
exec(constraint['code'], exec_globals)
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
error_msg = f"Failed to execute network constraint '{constraint['name']}': {e}"
|
|
192
|
+
logger.error(error_msg, exc_info=True)
|
|
193
|
+
# Continue with other constraints instead of failing the entire solve
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# Apply model constraints (they need access to the optimization model)
|
|
197
|
+
if model_constraints:
|
|
198
|
+
self._apply_model_constraints(network, model_constraints)
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"Failed to apply custom constraints: {e}", exc_info=True)
|
|
202
|
+
|
|
203
|
+
def _apply_model_constraints(self, network: 'pypsa.Network', model_constraints: list):
|
|
204
|
+
"""
|
|
205
|
+
Apply model constraints that need access to the optimization model.
|
|
206
|
+
|
|
207
|
+
This creates the optimization model, applies constraints to it, and then
|
|
208
|
+
replaces PyPSA's solve method to use the pre-constrained model.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
network: PyPSA Network object
|
|
212
|
+
model_constraints: List of model constraint dictionaries
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
logger.info(f"Applying {len(model_constraints)} model constraints...")
|
|
216
|
+
|
|
217
|
+
# Create the optimization model (same as PyPSA would do internally)
|
|
218
|
+
logger.info("Creating optimization model for constraint application...")
|
|
219
|
+
model = network.optimize.create_model()
|
|
220
|
+
logger.info(f"Created optimization model with {len(model.variables)} variable groups")
|
|
116
221
|
|
|
117
|
-
#
|
|
118
|
-
|
|
222
|
+
# Sort constraints by priority
|
|
223
|
+
sorted_constraints = sorted(model_constraints, key=lambda x: x['priority'])
|
|
224
|
+
|
|
225
|
+
# Apply each model constraint
|
|
226
|
+
for constraint in sorted_constraints:
|
|
119
227
|
try:
|
|
120
|
-
|
|
121
|
-
|
|
228
|
+
constraint_code = constraint['constraint_code']
|
|
229
|
+
constraint_name = constraint['name']
|
|
122
230
|
|
|
123
|
-
|
|
124
|
-
|
|
231
|
+
logger.info(f"Applying model constraint '{constraint_name}' (priority {constraint['priority']})")
|
|
232
|
+
|
|
233
|
+
# Create execution environment with network, model, and utilities
|
|
125
234
|
exec_globals = {
|
|
126
235
|
'n': network,
|
|
236
|
+
'network': network,
|
|
237
|
+
'model': model,
|
|
238
|
+
'm': model,
|
|
239
|
+
'snapshots': network.snapshots,
|
|
127
240
|
'pd': pd,
|
|
128
241
|
'np': np,
|
|
242
|
+
'xr': __import__('xarray'), # Import xarray for DataArray operations
|
|
129
243
|
}
|
|
130
244
|
|
|
131
245
|
# Execute the constraint code
|
|
132
|
-
exec(
|
|
246
|
+
exec(constraint_code, exec_globals)
|
|
247
|
+
logger.info(f"Successfully applied model constraint '{constraint_name}'")
|
|
133
248
|
|
|
134
249
|
except Exception as e:
|
|
135
|
-
error_msg = f"Failed to
|
|
250
|
+
error_msg = f"Failed to apply model constraint '{constraint.get('name', 'unknown')}': {e}"
|
|
136
251
|
logger.error(error_msg, exc_info=True)
|
|
137
|
-
# Continue with other constraints instead of failing
|
|
252
|
+
# Continue with other constraints instead of failing
|
|
138
253
|
continue
|
|
139
|
-
|
|
254
|
+
|
|
255
|
+
# Store the constrained model for the solver to use
|
|
256
|
+
# We'll replace PyPSA's solve_model method to use our pre-constrained model
|
|
257
|
+
logger.info("Replacing PyPSA's solve method to use pre-constrained model...")
|
|
258
|
+
|
|
259
|
+
# Store original methods
|
|
260
|
+
original_optimize = network.optimize
|
|
261
|
+
original_solve_model = original_optimize.solve_model
|
|
262
|
+
|
|
263
|
+
# Create a wrapper that uses our pre-constrained model
|
|
264
|
+
def constrained_solve_model(*args, **kwargs):
|
|
265
|
+
"""Use the pre-constrained model instead of creating a new one."""
|
|
266
|
+
logger.info("Using pre-constrained model for solve...")
|
|
267
|
+
return original_solve_model(model, *args, **kwargs)
|
|
268
|
+
|
|
269
|
+
# Replace the solve_model method
|
|
270
|
+
network.optimize.solve_model = constrained_solve_model
|
|
271
|
+
|
|
272
|
+
logger.info(f"Successfully applied {len(model_constraints)} model constraints")
|
|
273
|
+
|
|
140
274
|
except Exception as e:
|
|
141
|
-
logger.error(f"Failed to apply
|
|
275
|
+
logger.error(f"Failed to apply model constraints: {e}", exc_info=True)
|
|
276
|
+
# Don't re-raise - let the solve continue without constraints rather than fail completely
|
|
142
277
|
|
|
143
278
|
def _apply_dsl_constraints(self, network: 'pypsa.Network', constraints_dsl: str):
|
|
144
279
|
"""
|
|
@@ -172,7 +307,8 @@ class ConstraintApplicator:
|
|
|
172
307
|
scenario_id: Optional[int] = None
|
|
173
308
|
) -> List[Dict[str, Any]]:
|
|
174
309
|
"""
|
|
175
|
-
Get
|
|
310
|
+
Get ALL active constraints for optimization-time application.
|
|
311
|
+
The solver will determine which are model constraints vs network constraints.
|
|
176
312
|
|
|
177
313
|
Args:
|
|
178
314
|
conn: Database connection
|
|
@@ -180,7 +316,7 @@ class ConstraintApplicator:
|
|
|
180
316
|
scenario_id: Optional scenario ID
|
|
181
317
|
|
|
182
318
|
Returns:
|
|
183
|
-
List of
|
|
319
|
+
List of all active constraints
|
|
184
320
|
"""
|
|
185
321
|
try:
|
|
186
322
|
# Load all constraints for this network
|
|
@@ -189,7 +325,7 @@ class ConstraintApplicator:
|
|
|
189
325
|
if not constraints:
|
|
190
326
|
return []
|
|
191
327
|
|
|
192
|
-
# Load constraint attributes and filter active
|
|
328
|
+
# Load constraint attributes and filter active ones
|
|
193
329
|
optimization_constraints = []
|
|
194
330
|
for constraint in constraints:
|
|
195
331
|
try:
|
|
@@ -212,26 +348,24 @@ class ConstraintApplicator:
|
|
|
212
348
|
if "String" in constraint_code.static_value.data:
|
|
213
349
|
code_val = constraint_code.static_value.data["String"]
|
|
214
350
|
|
|
215
|
-
#
|
|
216
|
-
|
|
217
|
-
if
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
'code': code_val
|
|
231
|
-
})
|
|
351
|
+
# Extract priority value
|
|
352
|
+
priority_val = 0
|
|
353
|
+
if priority.variant == "Static":
|
|
354
|
+
if "Integer" in priority.static_value.data:
|
|
355
|
+
priority_val = priority.static_value.data["Integer"]
|
|
356
|
+
elif "Float" in priority.static_value.data:
|
|
357
|
+
priority_val = int(priority.static_value.data["Float"])
|
|
358
|
+
|
|
359
|
+
optimization_constraints.append({
|
|
360
|
+
'id': constraint.id,
|
|
361
|
+
'name': constraint.name,
|
|
362
|
+
'priority': priority_val,
|
|
363
|
+
'constraint_code': code_val, # Use consistent key name
|
|
364
|
+
'code': code_val # Keep both for compatibility
|
|
365
|
+
})
|
|
232
366
|
|
|
233
367
|
except Exception as e:
|
|
234
|
-
logger.warning(f"Failed to load
|
|
368
|
+
logger.warning(f"Failed to load constraint {constraint.name}: {e}")
|
|
235
369
|
continue
|
|
236
370
|
|
|
237
371
|
# Sort constraints by priority (lower numbers first)
|
|
@@ -310,6 +444,7 @@ class ConstraintApplicator:
|
|
|
310
444
|
'snapshots': snapshots,
|
|
311
445
|
'pd': pd,
|
|
312
446
|
'np': np,
|
|
447
|
+
'xr': __import__('xarray'),
|
|
313
448
|
}
|
|
314
449
|
|
|
315
450
|
# Execute the constraint code
|
|
@@ -139,13 +139,39 @@ class NetworkSolver:
|
|
|
139
139
|
if conn and network_id:
|
|
140
140
|
self._set_snapshot_weightings_after_multiperiod(conn, network_id, network)
|
|
141
141
|
|
|
142
|
-
# Prepare optimization constraints
|
|
142
|
+
# Prepare optimization constraints with type detection
|
|
143
143
|
extra_functionality = None
|
|
144
|
+
model_constraints = []
|
|
145
|
+
network_constraints = []
|
|
146
|
+
|
|
144
147
|
if conn and network_id and constraint_applicator:
|
|
145
148
|
optimization_constraints = constraint_applicator.get_optimization_constraints(conn, network_id, scenario_id)
|
|
146
149
|
if optimization_constraints:
|
|
147
|
-
logger.info(f"
|
|
148
|
-
|
|
150
|
+
logger.info(f"Found {len(optimization_constraints)} optimization constraints")
|
|
151
|
+
|
|
152
|
+
# Separate constraints by type
|
|
153
|
+
for constraint in optimization_constraints:
|
|
154
|
+
constraint_code = constraint.get('constraint_code', '')
|
|
155
|
+
constraint_type = self._detect_constraint_type(constraint_code)
|
|
156
|
+
constraint_name = constraint.get('name', 'unknown')
|
|
157
|
+
|
|
158
|
+
if constraint_type == "model_constraint":
|
|
159
|
+
model_constraints.append(constraint)
|
|
160
|
+
logger.info(f"Detected model constraint: {constraint_name}")
|
|
161
|
+
else:
|
|
162
|
+
network_constraints.append(constraint)
|
|
163
|
+
logger.info(f"Detected network constraint: {constraint_name}")
|
|
164
|
+
|
|
165
|
+
logger.info(f"Constraint breakdown: {len(model_constraints)} model constraints, {len(network_constraints)} network constraints")
|
|
166
|
+
|
|
167
|
+
# Create extra_functionality for ALL constraints (both model and network)
|
|
168
|
+
all_constraints = model_constraints + network_constraints
|
|
169
|
+
if all_constraints:
|
|
170
|
+
extra_functionality = self._create_extra_functionality(all_constraints, constraint_applicator)
|
|
171
|
+
logger.info(f"Prepared {len(all_constraints)} constraints for optimization-time application")
|
|
172
|
+
|
|
173
|
+
# NOTE: Model constraints are now applied DURING solve via extra_functionality
|
|
174
|
+
# This ensures they are applied to the actual model PyPSA creates, not a separate model
|
|
149
175
|
|
|
150
176
|
# Solver diagnostics
|
|
151
177
|
logger.info(f"=== PYPSA SOLVER DIAGNOSTICS ===")
|
|
@@ -342,7 +368,7 @@ class NetworkSolver:
|
|
|
342
368
|
return 'gurobi', gurobi_dual_options
|
|
343
369
|
|
|
344
370
|
# Check if this is a known valid solver name
|
|
345
|
-
elif solver_name in ['highs', 'gurobi', 'cplex', 'glpk', 'cbc', 'scip']:
|
|
371
|
+
elif solver_name in ['highs', 'gurobi', 'cplex', 'glpk', 'cbc', 'scip', 'copt']:
|
|
346
372
|
return solver_name, solver_options
|
|
347
373
|
|
|
348
374
|
else:
|
|
@@ -351,6 +377,51 @@ class NetworkSolver:
|
|
|
351
377
|
return 'highs', solver_options
|
|
352
378
|
|
|
353
379
|
|
|
380
|
+
def _detect_constraint_type(self, constraint_code: str) -> str:
|
|
381
|
+
"""
|
|
382
|
+
Detect if constraint is network-modification or model-constraint type.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
constraint_code: The constraint code to analyze
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
"model_constraint" or "network_modification"
|
|
389
|
+
"""
|
|
390
|
+
# Type 2 indicators (model constraints) - need access to optimization model
|
|
391
|
+
model_indicators = [
|
|
392
|
+
'n.optimize.create_model()',
|
|
393
|
+
'm.variables',
|
|
394
|
+
'm.add_constraints',
|
|
395
|
+
'gen_p =',
|
|
396
|
+
'constraint_expr =',
|
|
397
|
+
'LinearExpression',
|
|
398
|
+
'linopy',
|
|
399
|
+
'Generator-p',
|
|
400
|
+
'lhs <=',
|
|
401
|
+
'constraint_expr ='
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
# Type 1 indicators (network modifications) - modify network directly
|
|
405
|
+
network_indicators = [
|
|
406
|
+
'n.generators.loc',
|
|
407
|
+
'n.add(',
|
|
408
|
+
'n.buses.',
|
|
409
|
+
'n.lines.',
|
|
410
|
+
'network.generators.loc',
|
|
411
|
+
'network.add(',
|
|
412
|
+
'network.buses.',
|
|
413
|
+
'network.lines.'
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
# Check for model constraint indicators first (more specific)
|
|
417
|
+
if any(indicator in constraint_code for indicator in model_indicators):
|
|
418
|
+
return "model_constraint"
|
|
419
|
+
elif any(indicator in constraint_code for indicator in network_indicators):
|
|
420
|
+
return "network_modification"
|
|
421
|
+
else:
|
|
422
|
+
# Default to network_modification for safety (existing behavior)
|
|
423
|
+
return "network_modification"
|
|
424
|
+
|
|
354
425
|
def _create_extra_functionality(self, optimization_constraints: list, constraint_applicator) -> callable:
|
|
355
426
|
"""
|
|
356
427
|
Create extra_functionality function for optimization-time constraints.
|
|
@@ -14,14 +14,19 @@ src/pyconvexity/core/errors.py
|
|
|
14
14
|
src/pyconvexity/core/types.py
|
|
15
15
|
src/pyconvexity/data/README.md
|
|
16
16
|
src/pyconvexity/data/__init__.py
|
|
17
|
+
src/pyconvexity/data/__pycache__/__init__.cpython-313.pyc
|
|
17
18
|
src/pyconvexity/data/loaders/__init__.py
|
|
18
19
|
src/pyconvexity/data/loaders/cache.py
|
|
20
|
+
src/pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc
|
|
21
|
+
src/pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc
|
|
19
22
|
src/pyconvexity/data/schema/01_core_schema.sql
|
|
20
23
|
src/pyconvexity/data/schema/02_data_metadata.sql
|
|
21
24
|
src/pyconvexity/data/schema/03_validation_data.sql
|
|
22
25
|
src/pyconvexity/data/schema/04_scenario_schema.sql
|
|
23
26
|
src/pyconvexity/data/sources/__init__.py
|
|
24
27
|
src/pyconvexity/data/sources/gem.py
|
|
28
|
+
src/pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc
|
|
29
|
+
src/pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc
|
|
25
30
|
src/pyconvexity/io/__init__.py
|
|
26
31
|
src/pyconvexity/io/excel_exporter.py
|
|
27
32
|
src/pyconvexity/io/excel_importer.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/01_core_schema.sql
RENAMED
|
File without changes
|
{pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/02_data_metadata.sql
RENAMED
|
File without changes
|
{pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/03_validation_data.sql
RENAMED
|
File without changes
|
{pyconvexity-0.3.8 → pyconvexity-0.3.8.post1}/src/pyconvexity/data/schema/04_scenario_schema.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|