pyconvexity 0.3.8.post3__py3-none-any.whl → 0.3.8.post5__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/_version.py +1 -1
- pyconvexity/data/schema/01_core_schema.sql +51 -0
- pyconvexity/data/schema/migrate_add_geometries.sql +73 -0
- pyconvexity/models/__init__.py +49 -5
- pyconvexity/models/carriers.py +156 -0
- pyconvexity/models/components.py +120 -0
- pyconvexity/models/network.py +67 -1
- pyconvexity/models/results.py +138 -0
- pyconvexity/models/scenarios.py +102 -114
- pyconvexity/solvers/pypsa/api.py +31 -9
- pyconvexity/solvers/pypsa/constraints.py +5 -78
- pyconvexity/solvers/pypsa/solver.py +193 -73
- {pyconvexity-0.3.8.post3.dist-info → pyconvexity-0.3.8.post5.dist-info}/METADATA +1 -1
- {pyconvexity-0.3.8.post3.dist-info → pyconvexity-0.3.8.post5.dist-info}/RECORD +16 -13
- {pyconvexity-0.3.8.post3.dist-info → pyconvexity-0.3.8.post5.dist-info}/WHEEL +0 -0
- {pyconvexity-0.3.8.post3.dist-info → pyconvexity-0.3.8.post5.dist-info}/top_level.txt +0 -0
|
@@ -77,6 +77,8 @@ class NetworkSolver:
|
|
|
77
77
|
known_solvers = ['highs', 'gurobi', 'gurobi (barrier)', 'gurobi (barrier homogeneous)',
|
|
78
78
|
'gurobi (barrier+crossover balanced)', 'gurobi (dual simplex)',
|
|
79
79
|
'mosek', 'mosek (default)', 'mosek (barrier)', 'mosek (barrier+crossover)', 'mosek (dual simplex)',
|
|
80
|
+
'copt', 'copt (barrier)', 'copt (barrier homogeneous)', 'copt (barrier+crossover)',
|
|
81
|
+
'copt (dual simplex)', 'copt (concurrent)',
|
|
80
82
|
'cplex', 'glpk', 'cbc', 'scip']
|
|
81
83
|
|
|
82
84
|
if default_solver in known_solvers:
|
|
@@ -99,17 +101,21 @@ class NetworkSolver:
|
|
|
99
101
|
conn=None,
|
|
100
102
|
network_id: Optional[int] = None,
|
|
101
103
|
scenario_id: Optional[int] = None,
|
|
102
|
-
constraint_applicator=None
|
|
104
|
+
constraint_applicator=None,
|
|
105
|
+
custom_solver_config: Optional[Dict[str, Any]] = None
|
|
103
106
|
) -> Dict[str, Any]:
|
|
104
107
|
"""
|
|
105
108
|
Solve PyPSA network and return results.
|
|
106
109
|
|
|
107
110
|
Args:
|
|
108
111
|
network: PyPSA Network object to solve
|
|
109
|
-
solver_name: Solver to use (default: "highs")
|
|
112
|
+
solver_name: Solver to use (default: "highs"). Use "custom" for custom_solver_config.
|
|
110
113
|
solver_options: Optional solver-specific options
|
|
111
114
|
discount_rate: Optional discount rate for multi-period optimization
|
|
112
115
|
job_id: Optional job ID for tracking
|
|
116
|
+
custom_solver_config: Optional custom solver configuration when solver_name="custom"
|
|
117
|
+
Format: {"solver": "actual_solver_name", "solver_options": {...}}
|
|
118
|
+
Example: {"solver": "gurobi", "solver_options": {"Method": 2, "Crossover": 0}}
|
|
113
119
|
|
|
114
120
|
Returns:
|
|
115
121
|
Dictionary with solve results and metadata
|
|
@@ -125,14 +131,16 @@ class NetworkSolver:
|
|
|
125
131
|
|
|
126
132
|
try:
|
|
127
133
|
# Get solver configuration
|
|
128
|
-
actual_solver_name, solver_config = self._get_solver_config(solver_name, solver_options)
|
|
134
|
+
actual_solver_name, solver_config = self._get_solver_config(solver_name, solver_options, custom_solver_config)
|
|
129
135
|
|
|
136
|
+
# Resolve discount rate - fallback to 0.0 if None
|
|
137
|
+
# Note: API layer (api.py) handles fetching from network_config before calling this
|
|
138
|
+
effective_discount_rate = discount_rate if discount_rate is not None else 0.0
|
|
139
|
+
logger.info(f"Discount rate for solve: {effective_discount_rate}")
|
|
130
140
|
|
|
131
141
|
years = list(network.investment_periods)
|
|
132
|
-
effective_discount_rate = discount_rate if discount_rate is not None else 0.05 # Default 5%
|
|
133
142
|
|
|
134
143
|
logger.info(f"Multi-period optimization with {len(years)} periods: {years}")
|
|
135
|
-
logger.info(f"Discount rate: {effective_discount_rate}")
|
|
136
144
|
|
|
137
145
|
# Calculate investment period weightings with discount rate
|
|
138
146
|
self._calculate_investment_weightings(network, effective_discount_rate)
|
|
@@ -141,17 +149,17 @@ class NetworkSolver:
|
|
|
141
149
|
if conn and network_id:
|
|
142
150
|
self._set_snapshot_weightings_after_multiperiod(conn, network_id, network)
|
|
143
151
|
|
|
144
|
-
# Prepare optimization constraints
|
|
152
|
+
# Prepare optimization constraints - ONLY model constraints
|
|
153
|
+
# Network constraints were already applied before solve in api.py
|
|
145
154
|
extra_functionality = None
|
|
146
155
|
model_constraints = []
|
|
147
|
-
network_constraints = []
|
|
148
156
|
|
|
149
157
|
if conn and network_id and constraint_applicator:
|
|
150
158
|
optimization_constraints = constraint_applicator.get_optimization_constraints(conn, network_id, scenario_id)
|
|
151
159
|
if optimization_constraints:
|
|
152
160
|
logger.info(f"Found {len(optimization_constraints)} optimization constraints")
|
|
153
161
|
|
|
154
|
-
#
|
|
162
|
+
# Filter for model constraints only (network constraints already applied)
|
|
155
163
|
for constraint in optimization_constraints:
|
|
156
164
|
constraint_code = constraint.get('constraint_code', '')
|
|
157
165
|
constraint_type = self._detect_constraint_type(constraint_code)
|
|
@@ -159,21 +167,19 @@ class NetworkSolver:
|
|
|
159
167
|
|
|
160
168
|
if constraint_type == "model_constraint":
|
|
161
169
|
model_constraints.append(constraint)
|
|
162
|
-
logger.info(f"
|
|
170
|
+
logger.info(f"Will apply model constraint during solve: {constraint_name}")
|
|
163
171
|
else:
|
|
164
|
-
|
|
165
|
-
logger.info(f"Detected network constraint: {constraint_name}")
|
|
172
|
+
logger.info(f"Skipping network constraint (already applied): {constraint_name}")
|
|
166
173
|
|
|
167
|
-
logger.info(f"
|
|
174
|
+
logger.info(f"Will apply {len(model_constraints)} model constraints during optimization")
|
|
168
175
|
|
|
169
|
-
# Create extra_functionality for
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
logger.info(f"Prepared {len(all_constraints)} constraints for optimization-time application")
|
|
176
|
+
# Create extra_functionality for model constraints only
|
|
177
|
+
if model_constraints:
|
|
178
|
+
extra_functionality = self._create_extra_functionality(model_constraints, constraint_applicator)
|
|
179
|
+
logger.info(f"Prepared {len(model_constraints)} model constraints for optimization-time application")
|
|
174
180
|
|
|
175
|
-
# NOTE: Model constraints are
|
|
176
|
-
#
|
|
181
|
+
# NOTE: Model constraints are applied DURING solve via extra_functionality
|
|
182
|
+
# Network constraints were already applied to the network structure before solve
|
|
177
183
|
|
|
178
184
|
# Solver diagnostics
|
|
179
185
|
logger.info(f"=== PYPSA SOLVER DIAGNOSTICS ===")
|
|
@@ -273,17 +279,40 @@ class NetworkSolver:
|
|
|
273
279
|
"objective_value": None
|
|
274
280
|
}
|
|
275
281
|
|
|
276
|
-
def _get_solver_config(self, solver_name: str, solver_options: Optional[Dict[str, Any]] = None
|
|
282
|
+
def _get_solver_config(self, solver_name: str, solver_options: Optional[Dict[str, Any]] = None,
|
|
283
|
+
custom_solver_config: Optional[Dict[str, Any]] = None) -> tuple[str, Optional[Dict[str, Any]]]:
|
|
277
284
|
"""
|
|
278
285
|
Get the actual solver name and options for special solver configurations.
|
|
279
286
|
|
|
280
287
|
Args:
|
|
281
|
-
solver_name: The solver name (e.g., 'gurobi (barrier)', 'highs')
|
|
288
|
+
solver_name: The solver name (e.g., 'gurobi (barrier)', 'highs', 'custom')
|
|
282
289
|
solver_options: Optional additional solver options
|
|
290
|
+
custom_solver_config: Optional custom solver configuration for solver_name='custom'
|
|
291
|
+
Format: {"solver": "actual_solver_name", "solver_options": {...}}
|
|
283
292
|
|
|
284
293
|
Returns:
|
|
285
294
|
Tuple of (actual_solver_name, solver_options_dict)
|
|
286
295
|
"""
|
|
296
|
+
# Handle "custom" solver with custom configuration
|
|
297
|
+
if solver_name == 'custom':
|
|
298
|
+
if not custom_solver_config:
|
|
299
|
+
raise ValueError("custom_solver_config must be provided when solver_name='custom'")
|
|
300
|
+
|
|
301
|
+
if 'solver' not in custom_solver_config:
|
|
302
|
+
raise ValueError("custom_solver_config must contain 'solver' key with the actual solver name")
|
|
303
|
+
|
|
304
|
+
actual_solver = custom_solver_config['solver']
|
|
305
|
+
custom_options = custom_solver_config.get('solver_options', {})
|
|
306
|
+
|
|
307
|
+
# Merge with any additional solver_options passed separately
|
|
308
|
+
if solver_options:
|
|
309
|
+
merged_options = {'solver_options': {**custom_options, **solver_options}}
|
|
310
|
+
else:
|
|
311
|
+
merged_options = {'solver_options': custom_options} if custom_options else None
|
|
312
|
+
|
|
313
|
+
logger.info(f"Using custom solver configuration: {actual_solver} with options: {custom_options}")
|
|
314
|
+
return actual_solver, merged_options
|
|
315
|
+
|
|
287
316
|
# Handle "default" solver
|
|
288
317
|
if solver_name == 'default':
|
|
289
318
|
# Try to read user's default solver preference
|
|
@@ -298,7 +327,7 @@ class NetworkSolver:
|
|
|
298
327
|
'Method': 2, # Barrier
|
|
299
328
|
'Crossover': 0, # Skip crossover
|
|
300
329
|
'MIPGap': 0.05, # 5% gap
|
|
301
|
-
'Threads':
|
|
330
|
+
'Threads': 0, # Use all cores (0 = auto)
|
|
302
331
|
'Presolve': 2, # Aggressive presolve
|
|
303
332
|
'ConcurrentMIP': 1, # Parallel root strategies
|
|
304
333
|
'BarConvTol': 1e-4, # Relaxed barrier convergence
|
|
@@ -319,7 +348,7 @@ class NetworkSolver:
|
|
|
319
348
|
'Method': 2, # Barrier
|
|
320
349
|
'Crossover': 0, # Skip crossover
|
|
321
350
|
'MIPGap': 0.05,
|
|
322
|
-
'Threads':
|
|
351
|
+
'Threads': 0, # Use all cores (0 = auto)
|
|
323
352
|
'Presolve': 2,
|
|
324
353
|
'ConcurrentMIP': 1,
|
|
325
354
|
'BarConvTol': 1e-4,
|
|
@@ -340,7 +369,7 @@ class NetworkSolver:
|
|
|
340
369
|
'Method': 2,
|
|
341
370
|
'Crossover': 1, # Dual crossover
|
|
342
371
|
'MIPGap': 0.01,
|
|
343
|
-
'Threads':
|
|
372
|
+
'Threads': 0, # Use all cores (0 = auto)
|
|
344
373
|
'Presolve': 2,
|
|
345
374
|
'Heuristics': 0.1,
|
|
346
375
|
'Cuts': 2,
|
|
@@ -374,67 +403,53 @@ class NetworkSolver:
|
|
|
374
403
|
# No custom options - let Mosek use its default configuration
|
|
375
404
|
mosek_default_options = {
|
|
376
405
|
'solver_options': {
|
|
377
|
-
'
|
|
378
|
-
'
|
|
379
|
-
'MSK_IPAR_LOG_SIM': 4, # Log simplex progress
|
|
380
|
-
'MSK_IPAR_LOG_MIO': 4, # Log MIP progress (4 = full)
|
|
381
|
-
'MSK_IPAR_LOG_MIO_FREQ': 10, # Log MIP every 10 seconds
|
|
406
|
+
'MSK_DPAR_MIO_REL_GAP_CONST': 0.05, # MIP relative gap tolerance (5% to match Gurobi)
|
|
407
|
+
'MSK_IPAR_MIO_MAX_TIME': 36000, # Max time 1 hour
|
|
382
408
|
}
|
|
383
409
|
}
|
|
384
410
|
if solver_options:
|
|
385
411
|
mosek_default_options['solver_options'].update(solver_options)
|
|
386
|
-
logger.info(f"Using Mosek with default configuration (auto-select optimizer)")
|
|
412
|
+
logger.info(f"Using Mosek with default configuration (auto-select optimizer) and moderate MIP strategies")
|
|
387
413
|
return 'mosek', mosek_default_options
|
|
388
414
|
|
|
389
415
|
elif solver_name == 'mosek (barrier)':
|
|
390
416
|
mosek_barrier_options = {
|
|
391
417
|
'solver_options': {
|
|
392
418
|
'MSK_IPAR_INTPNT_BASIS': 0, # Skip crossover (barrier-only) - 0 = MSK_BI_NEVER
|
|
393
|
-
'MSK_DPAR_INTPNT_TOL_REL_GAP': 1e-
|
|
394
|
-
'MSK_DPAR_INTPNT_TOL_PFEAS': 1e-
|
|
395
|
-
'MSK_DPAR_INTPNT_TOL_DFEAS': 1e-
|
|
396
|
-
|
|
397
|
-
'MSK_IPAR_NUM_THREADS':
|
|
398
|
-
'MSK_IPAR_PRESOLVE_USE':
|
|
399
|
-
'MSK_IPAR_PRESOLVE_LINDEP_USE': 1, # Linear dependency check
|
|
400
|
-
'MSK_DPAR_MIO_REL_GAP_CONST':
|
|
401
|
-
'MSK_IPAR_MIO_NODE_OPTIMIZER': 4, # Use interior-point for MIP nodes
|
|
402
|
-
'MSK_IPAR_MIO_ROOT_OPTIMIZER': 4, # Use interior-point for MIP root
|
|
403
|
-
'
|
|
404
|
-
'MSK_IPAR_LOG_INTPNT': 1, # Log interior-point progress
|
|
405
|
-
'MSK_IPAR_LOG_MIO': 4, # Log MIP progress (4 = full)
|
|
406
|
-
'MSK_IPAR_LOG_MIO_FREQ': 10, # Log MIP every 10 seconds
|
|
407
|
-
# Note: Don't force MSK_IPAR_OPTIMIZER - let Mosek choose based on problem type (LP vs MILP)
|
|
419
|
+
'MSK_DPAR_INTPNT_TOL_REL_GAP': 1e-4, # Match Gurobi barrier tolerance
|
|
420
|
+
'MSK_DPAR_INTPNT_TOL_PFEAS': 1e-5, # Match Gurobi primal feasibility
|
|
421
|
+
'MSK_DPAR_INTPNT_TOL_DFEAS': 1e-5, # Match Gurobi dual feasibility
|
|
422
|
+
# Removed MSK_DPAR_INTPNT_TOL_INFEAS - was 1000x tighter than other tolerances!
|
|
423
|
+
'MSK_IPAR_NUM_THREADS': 0, # Use all available cores (0 = auto)
|
|
424
|
+
'MSK_IPAR_PRESOLVE_USE': 2, # Aggressive presolve (match Gurobi Presolve=2)
|
|
425
|
+
'MSK_IPAR_PRESOLVE_LINDEP_USE': 1, # Linear dependency check
|
|
426
|
+
'MSK_DPAR_MIO_REL_GAP_CONST': 0.05, # Match Gurobi 5% MIP gap
|
|
427
|
+
'MSK_IPAR_MIO_NODE_OPTIMIZER': 4, # Use interior-point for MIP nodes
|
|
428
|
+
'MSK_IPAR_MIO_ROOT_OPTIMIZER': 4, # Use interior-point for MIP root
|
|
429
|
+
'MSK_DPAR_MIO_MAX_TIME': 36000, # Max time 10 hour
|
|
408
430
|
}
|
|
409
431
|
}
|
|
410
432
|
if solver_options:
|
|
411
433
|
mosek_barrier_options['solver_options'].update(solver_options)
|
|
412
|
-
logger.info(f"Using Mosek Barrier
|
|
434
|
+
logger.info(f"Using Mosek Barrier with aggressive presolve and relaxed tolerances")
|
|
413
435
|
return 'mosek', mosek_barrier_options
|
|
414
436
|
|
|
415
437
|
elif solver_name == 'mosek (barrier+crossover)':
|
|
416
438
|
mosek_barrier_crossover_options = {
|
|
417
439
|
'solver_options': {
|
|
418
440
|
'MSK_IPAR_INTPNT_BASIS': 1, # Always crossover (1 = MSK_BI_ALWAYS)
|
|
419
|
-
'MSK_DPAR_INTPNT_TOL_REL_GAP': 1e-
|
|
420
|
-
'MSK_DPAR_INTPNT_TOL_PFEAS': 1e-
|
|
421
|
-
'MSK_DPAR_INTPNT_TOL_DFEAS': 1e-
|
|
422
|
-
'MSK_IPAR_NUM_THREADS':
|
|
423
|
-
'
|
|
424
|
-
'MSK_IPAR_PRESOLVE_LINDEP_USE': 1, # Linear dependency check
|
|
425
|
-
'MSK_DPAR_MIO_REL_GAP_CONST': 1e-6, # MIP relative gap tolerance
|
|
426
|
-
'MSK_IPAR_MIO_NODE_OPTIMIZER': 4, # Use interior-point for MIP nodes
|
|
441
|
+
'MSK_DPAR_INTPNT_TOL_REL_GAP': 1e-4, # Match Gurobi barrier tolerance (was 1e-6)
|
|
442
|
+
'MSK_DPAR_INTPNT_TOL_PFEAS': 1e-5, # Match Gurobi (was 1e-6)
|
|
443
|
+
'MSK_DPAR_INTPNT_TOL_DFEAS': 1e-5, # Match Gurobi (was 1e-6)
|
|
444
|
+
'MSK_IPAR_NUM_THREADS': 0, # Use all available cores (0 = auto)
|
|
445
|
+
'MSK_DPAR_MIO_REL_GAP_CONST': 0.05, # Match Gurobi 5% MIP gap (was 1e-6)
|
|
427
446
|
'MSK_IPAR_MIO_ROOT_OPTIMIZER': 4, # Use interior-point for MIP root
|
|
428
|
-
'
|
|
429
|
-
'MSK_IPAR_LOG_INTPNT': 1, # Log interior-point progress
|
|
430
|
-
'MSK_IPAR_LOG_MIO': 4, # Log MIP progress (4 = full)
|
|
431
|
-
'MSK_IPAR_LOG_MIO_FREQ': 10, # Log MIP every 10 seconds
|
|
432
|
-
# Note: Don't force MSK_IPAR_OPTIMIZER - let Mosek choose based on problem type
|
|
447
|
+
'MSK_DPAR_MIO_MAX_TIME': 36000, # Max time 10 hour (safety limit)
|
|
433
448
|
}
|
|
434
449
|
}
|
|
435
450
|
if solver_options:
|
|
436
451
|
mosek_barrier_crossover_options['solver_options'].update(solver_options)
|
|
437
|
-
logger.info(f"Using Mosek Barrier+Crossover configuration with
|
|
452
|
+
logger.info(f"Using Mosek Barrier+Crossover configuration with Gurobi-matched tolerances and moderate MIP strategies")
|
|
438
453
|
return 'mosek', mosek_barrier_crossover_options
|
|
439
454
|
|
|
440
455
|
elif solver_name == 'mosek (dual simplex)':
|
|
@@ -442,20 +457,16 @@ class NetworkSolver:
|
|
|
442
457
|
'solver_options': {
|
|
443
458
|
'MSK_IPAR_NUM_THREADS': 0, # Use all available cores (0 = automatic)
|
|
444
459
|
'MSK_IPAR_PRESOLVE_USE': 1, # Force presolve
|
|
445
|
-
'
|
|
446
|
-
'MSK_DPAR_MIO_REL_GAP_CONST': 1e-6, # MIP relative gap tolerance
|
|
460
|
+
'MSK_DPAR_MIO_REL_GAP_CONST': 0.05, # Match Gurobi 5% MIP gap (was 1e-6)
|
|
447
461
|
'MSK_IPAR_MIO_NODE_OPTIMIZER': 1, # Use dual simplex for MIP nodes (1 = MSK_OPTIMIZER_DUAL_SIMPLEX)
|
|
448
462
|
'MSK_IPAR_MIO_ROOT_OPTIMIZER': 1, # Use dual simplex for MIP root
|
|
449
|
-
'
|
|
450
|
-
|
|
451
|
-
'MSK_IPAR_LOG_MIO': 4, # Log MIP progress (4 = full)
|
|
452
|
-
'MSK_IPAR_LOG_MIO_FREQ': 10, # Log MIP every 10 seconds
|
|
453
|
-
# Note: For pure LP, set optimizer; for MILP, only set node/root optimizers
|
|
463
|
+
'MSK_DPAR_MIO_MAX_TIME': 36000, # Max time 10 hour (safety limit)
|
|
464
|
+
|
|
454
465
|
}
|
|
455
466
|
}
|
|
456
467
|
if solver_options:
|
|
457
468
|
mosek_dual_options['solver_options'].update(solver_options)
|
|
458
|
-
logger.info(f"Using Mosek Dual Simplex configuration with
|
|
469
|
+
logger.info(f"Using Mosek Dual Simplex configuration with Gurobi-matched tolerances and moderate MIP strategies")
|
|
459
470
|
return 'mosek', mosek_dual_options
|
|
460
471
|
|
|
461
472
|
# Check if this is a known valid solver name
|
|
@@ -463,16 +474,14 @@ class NetworkSolver:
|
|
|
463
474
|
# Add default MILP-friendly settings for plain Mosek
|
|
464
475
|
mosek_defaults = {
|
|
465
476
|
'solver_options': {
|
|
466
|
-
'MSK_DPAR_MIO_REL_GAP_CONST':
|
|
467
|
-
'MSK_IPAR_MIO_MAX_TIME':
|
|
477
|
+
'MSK_DPAR_MIO_REL_GAP_CONST': 0.05, # Match Gurobi 5% MIP gap (was 1e-4)
|
|
478
|
+
'MSK_IPAR_MIO_MAX_TIME': 36000, # Max time 1 hour
|
|
468
479
|
'MSK_IPAR_NUM_THREADS': 0, # Use all cores (0 = auto)
|
|
469
|
-
'MSK_IPAR_LOG': 4, # Moderate logging
|
|
470
|
-
'MSK_IPAR_LOG_MIO': 2, # Log MIP occasionally
|
|
471
480
|
}
|
|
472
481
|
}
|
|
473
482
|
if solver_options:
|
|
474
483
|
mosek_defaults['solver_options'].update(solver_options)
|
|
475
|
-
logger.info(f"Using Mosek with
|
|
484
|
+
logger.info(f"Using Mosek with barrier method for MIP (interior-point for root/nodes)")
|
|
476
485
|
return solver_name, mosek_defaults
|
|
477
486
|
|
|
478
487
|
elif solver_name == 'gurobi':
|
|
@@ -490,6 +499,117 @@ class NetworkSolver:
|
|
|
490
499
|
logger.info(f"Using Gurobi with default MILP-friendly settings")
|
|
491
500
|
return solver_name, gurobi_defaults
|
|
492
501
|
|
|
502
|
+
# Handle special COPT configurations
|
|
503
|
+
elif solver_name == 'copt (barrier)':
|
|
504
|
+
copt_barrier_options = {
|
|
505
|
+
'solver_options': {
|
|
506
|
+
'LpMethod': 2, # Barrier method
|
|
507
|
+
'Crossover': 0, # Skip crossover for speed
|
|
508
|
+
'RelGap': 0.05, # 5% MIP gap (match Gurobi)
|
|
509
|
+
'TimeLimit': 7200, # 1 hour time limit
|
|
510
|
+
'Threads': -1, # 4 threads (memory-conscious)
|
|
511
|
+
'Presolve': 3, # Aggressive presolve
|
|
512
|
+
'Scaling': 1, # Enable scaling
|
|
513
|
+
'FeasTol': 1e-5, # Match Gurobi feasibility
|
|
514
|
+
'DualTol': 1e-5, # Match Gurobi dual tolerance
|
|
515
|
+
# MIP performance settings
|
|
516
|
+
'CutLevel': 2, # Normal cut generation
|
|
517
|
+
'HeurLevel': 3, # Aggressive heuristics
|
|
518
|
+
'StrongBranching': 1, # Fast strong branching
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if solver_options:
|
|
522
|
+
copt_barrier_options['solver_options'].update(solver_options)
|
|
523
|
+
logger.info(f"Using COPT Barrier configuration (fast interior-point method)")
|
|
524
|
+
return 'copt', copt_barrier_options
|
|
525
|
+
|
|
526
|
+
elif solver_name == 'copt (barrier homogeneous)':
|
|
527
|
+
copt_barrier_homogeneous_options = {
|
|
528
|
+
'solver_options': {
|
|
529
|
+
'LpMethod': 2, # Barrier method
|
|
530
|
+
'Crossover': 0, # Skip crossover
|
|
531
|
+
'BarHomogeneous': 1, # Use homogeneous self-dual form
|
|
532
|
+
'RelGap': 0.05, # 5% MIP gap
|
|
533
|
+
'TimeLimit': 3600, # 1 hour
|
|
534
|
+
'Threads': -1, # 4 threads (memory-conscious)
|
|
535
|
+
'Presolve': 3, # Aggressive presolve
|
|
536
|
+
'Scaling': 1, # Enable scaling
|
|
537
|
+
'FeasTol': 1e-5,
|
|
538
|
+
'DualTol': 1e-5,
|
|
539
|
+
# MIP performance settings
|
|
540
|
+
'CutLevel': 2, # Normal cuts
|
|
541
|
+
'HeurLevel': 3, # Aggressive heuristics
|
|
542
|
+
'StrongBranching': 1, # Fast strong branching
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if solver_options:
|
|
546
|
+
copt_barrier_homogeneous_options['solver_options'].update(solver_options)
|
|
547
|
+
logger.info(f"Using COPT Barrier Homogeneous configuration")
|
|
548
|
+
return 'copt', copt_barrier_homogeneous_options
|
|
549
|
+
|
|
550
|
+
elif solver_name == 'copt (barrier+crossover)':
|
|
551
|
+
copt_barrier_crossover_options = {
|
|
552
|
+
'solver_options': {
|
|
553
|
+
'LpMethod': 2, # Barrier method
|
|
554
|
+
'Crossover': 1, # Enable crossover for better solutions
|
|
555
|
+
'RelGap': 0.05, # 5% MIP gap (relaxed for faster solves)
|
|
556
|
+
'TimeLimit': 36000, # 10 hour
|
|
557
|
+
'Threads': -1, # Use all cores
|
|
558
|
+
'Presolve': 2, # Aggressive presolve
|
|
559
|
+
'Scaling': 1, # Enable scaling
|
|
560
|
+
'FeasTol': 1e-4, # Tighter feasibility
|
|
561
|
+
'DualTol': 1e-4, # Tighter dual tolerance
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if solver_options:
|
|
565
|
+
copt_barrier_crossover_options['solver_options'].update(solver_options)
|
|
566
|
+
logger.info(f"Using COPT Barrier+Crossover configuration (balanced performance)")
|
|
567
|
+
return 'copt', copt_barrier_crossover_options
|
|
568
|
+
|
|
569
|
+
elif solver_name == 'copt (dual simplex)':
|
|
570
|
+
copt_dual_simplex_options = {
|
|
571
|
+
'solver_options': {
|
|
572
|
+
'LpMethod': 1, # Dual simplex method
|
|
573
|
+
'RelGap': 0.05, # 5% MIP gap
|
|
574
|
+
'TimeLimit': 3600, # 1 hour
|
|
575
|
+
'Threads': -1, # Use all cores
|
|
576
|
+
'Presolve': 3, # Aggressive presolve
|
|
577
|
+
'Scaling': 1, # Enable scaling
|
|
578
|
+
'FeasTol': 1e-6,
|
|
579
|
+
'DualTol': 1e-6,
|
|
580
|
+
# MIP performance settings
|
|
581
|
+
'CutLevel': 2, # Normal cuts
|
|
582
|
+
'HeurLevel': 2, # Normal heuristics
|
|
583
|
+
'StrongBranching': 1, # Fast strong branching
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if solver_options:
|
|
587
|
+
copt_dual_simplex_options['solver_options'].update(solver_options)
|
|
588
|
+
logger.info(f"Using COPT Dual Simplex configuration (robust method)")
|
|
589
|
+
return 'copt', copt_dual_simplex_options
|
|
590
|
+
|
|
591
|
+
elif solver_name == 'copt (concurrent)':
|
|
592
|
+
copt_concurrent_options = {
|
|
593
|
+
'solver_options': {
|
|
594
|
+
'LpMethod': 4, # Concurrent (simplex + barrier)
|
|
595
|
+
'RelGap': 0.05, # 5% MIP gap
|
|
596
|
+
'TimeLimit': 3600, # 1 hour
|
|
597
|
+
'Threads': -1, # Use all cores
|
|
598
|
+
'Presolve': 3, # Aggressive presolve
|
|
599
|
+
'Scaling': 1, # Enable scaling
|
|
600
|
+
'FeasTol': 1e-5,
|
|
601
|
+
'DualTol': 1e-5,
|
|
602
|
+
# MIP performance settings
|
|
603
|
+
'CutLevel': 2, # Normal cuts
|
|
604
|
+
'HeurLevel': 3, # Aggressive heuristics
|
|
605
|
+
'StrongBranching': 1, # Fast strong branching
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if solver_options:
|
|
609
|
+
copt_concurrent_options['solver_options'].update(solver_options)
|
|
610
|
+
logger.info(f"Using COPT Concurrent configuration (parallel simplex + barrier)")
|
|
611
|
+
return 'copt', copt_concurrent_options
|
|
612
|
+
|
|
493
613
|
elif solver_name in ['highs', 'cplex', 'glpk', 'cbc', 'scip', 'copt']:
|
|
494
614
|
return solver_name, solver_options
|
|
495
615
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
pyconvexity/__init__.py,sha256=eiAFroO4n-z8F0jTLpJgBIO7vtSxu9ovu3G2N-qqpUo,4783
|
|
2
|
-
pyconvexity/_version.py,sha256=
|
|
2
|
+
pyconvexity/_version.py,sha256=hAXE8ndUYHyK3gOK1hBR1ri9H4YX_KTmcufHouTLgTo,27
|
|
3
3
|
pyconvexity/timeseries.py,sha256=4p1Tdpa1otqDvCq2zppA4tw660sF_XWb8Xobib-cCms,11340
|
|
4
4
|
pyconvexity/core/__init__.py,sha256=MgVa5rrRWIi2w1UI1P4leiBntvHeeOPv0Thm0DEXBHo,1209
|
|
5
5
|
pyconvexity/core/database.py,sha256=M02q4UkJqAPeTXuwng9I7kHm16reJ7eq7wccWxnhE5I,15227
|
|
@@ -12,10 +12,11 @@ pyconvexity/data/loaders/__init__.py,sha256=6xPtOmH2n1mNby7ZjA-2Mk9F48Q246RNsyMn
|
|
|
12
12
|
pyconvexity/data/loaders/cache.py,sha256=nnz8bV3slSehOT0alexFga9tM1XoJqWHBGqaXvz132U,7299
|
|
13
13
|
pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc,sha256=AuT3aXy3v5gssxdD1_CBaKqNAVmDt6GBwFSyAe3jHow,265
|
|
14
14
|
pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc,sha256=9_xMQN6AciMzbzhCmWAzvEKRXfRINmfRsO8Dyg0_CUQ,9804
|
|
15
|
-
pyconvexity/data/schema/01_core_schema.sql,sha256=
|
|
15
|
+
pyconvexity/data/schema/01_core_schema.sql,sha256=2kkEevAhXJtNnC-wca2bnyw0m11mjheh4g9MPZpwBAc,20865
|
|
16
16
|
pyconvexity/data/schema/02_data_metadata.sql,sha256=oOfwa3PLY2_8rxKDD4cpDeqP5I_PdahcF8m6cSKStJM,10732
|
|
17
17
|
pyconvexity/data/schema/03_validation_data.sql,sha256=1rKFi9y6jQ2OnfH32jnIKnZ5WtB8eG43hz0OVJhwn3w,58325
|
|
18
18
|
pyconvexity/data/schema/04_scenario_schema.sql,sha256=sL4PySJNHIthXsnoJ2T5pdXUbpAi94ld0XGuU8LwNuQ,4641
|
|
19
|
+
pyconvexity/data/schema/migrate_add_geometries.sql,sha256=ljTz2ZIvfRkHCjJiUbZJr7PvUxPv3UeLl3ADb9U7dWc,2710
|
|
19
20
|
pyconvexity/data/sources/__init__.py,sha256=Dn6_oS7wB-vLjMj2YeXlmIl6hNjACbicimSabKxIWnc,108
|
|
20
21
|
pyconvexity/data/sources/gem.py,sha256=Ft2pAYsWe1V9poRge2Q4xdNt15XkG-USSR0XR9KFmsY,14935
|
|
21
22
|
pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc,sha256=9x5FyLxmTE5ZRaEFNSF375KBd_rDLY6pGHGSWPpcxxA,313
|
|
@@ -25,22 +26,24 @@ pyconvexity/io/excel_exporter.py,sha256=pjgvTs5vq9K61mNOVutEzaH5Zx4FgrDG4Xc_YmXh
|
|
|
25
26
|
pyconvexity/io/excel_importer.py,sha256=M7YcBqKUVzOMoR5HN-v8M2UnZgHRfhqgXBMUVD10-IQ,56898
|
|
26
27
|
pyconvexity/io/netcdf_exporter.py,sha256=AMM-uXBj8sh86n5m57aZ6S7LulAyIx_HM-eM-26BrWQ,7428
|
|
27
28
|
pyconvexity/io/netcdf_importer.py,sha256=nv4CYYqnbCBeznwCU_JGBMTbg-BGNpXKlsqbu2R8fTU,72152
|
|
28
|
-
pyconvexity/models/__init__.py,sha256
|
|
29
|
+
pyconvexity/models/__init__.py,sha256=N8YqEntbF5NrxIgUk1Knj9FiOzmMtD5Kywc6THJVeFk,3528
|
|
29
30
|
pyconvexity/models/attributes.py,sha256=LTvYF0hl56HeLjS8ZVocZWLhbLRTNhmZ5gUKxf93eSE,18254
|
|
30
|
-
pyconvexity/models/
|
|
31
|
-
pyconvexity/models/
|
|
32
|
-
pyconvexity/models/
|
|
31
|
+
pyconvexity/models/carriers.py,sha256=-nmasYvsaUeYPY1B0QdzfF_eph2HUFb5n3KF3CFd-YI,3700
|
|
32
|
+
pyconvexity/models/components.py,sha256=wWRdX6vErZrQhhLTnHBLDOnkmLjbHY2e9J9ITZJi3F8,18287
|
|
33
|
+
pyconvexity/models/network.py,sha256=2oEZOeVotyAs-SJl-b73zJKzSBvJEa6n1ryM0wV-Nko,14762
|
|
34
|
+
pyconvexity/models/results.py,sha256=9IKgO4bve94OGHgUGUcxvFpySGRW8-K3Wwvv9RXEF2k,4031
|
|
35
|
+
pyconvexity/models/scenarios.py,sha256=oF_xSOjrKMmUTzSR4oBAKKqWyudOGewW6DvZBa5IKXw,4125
|
|
33
36
|
pyconvexity/solvers/__init__.py,sha256=zoVf6T2Tmyj2XOeiVbEvaIMOX584orqCz1q9t1oXy0M,674
|
|
34
37
|
pyconvexity/solvers/pypsa/__init__.py,sha256=KZqYDo7CvwB-5Kp784xxxtdn5kRcmn3gGSRlaQdDA4c,554
|
|
35
|
-
pyconvexity/solvers/pypsa/api.py,sha256=
|
|
38
|
+
pyconvexity/solvers/pypsa/api.py,sha256=si2VAvotQKk-hcNtT3bIWV0CE4EzSER94mxehPFm7M8,18015
|
|
36
39
|
pyconvexity/solvers/pypsa/batch_loader.py,sha256=eQb8B11akQYtH3aK93WAOoXEI-ktk4imATw9gaYDNR4,13547
|
|
37
40
|
pyconvexity/solvers/pypsa/builder.py,sha256=WrimcBvG4mNFLTrLq7131Ku0AXY_0oRKxfI81ywc5Cs,24460
|
|
38
|
-
pyconvexity/solvers/pypsa/constraints.py,sha256=
|
|
39
|
-
pyconvexity/solvers/pypsa/solver.py,sha256=
|
|
41
|
+
pyconvexity/solvers/pypsa/constraints.py,sha256=qosBSNe0pr4va4dMmQFM-ifJCNGAkhS1R2gerNmhaiQ,16266
|
|
42
|
+
pyconvexity/solvers/pypsa/solver.py,sha256=7jaksRKMaQuFYWb7Pl7rw7Pu0kO5DPysQX2JtWdUbBc,72074
|
|
40
43
|
pyconvexity/solvers/pypsa/storage.py,sha256=T-0qEryiEy_8G4KiscPoiiWvTPd_OGqpLczW0_Xm85E,87331
|
|
41
44
|
pyconvexity/validation/__init__.py,sha256=_6SVqXkaDFqmagub_O064Zm_QIdBrOra-Gvvbo9vM4I,549
|
|
42
45
|
pyconvexity/validation/rules.py,sha256=6Kak12BVfUpjmgB5B7Wre55eGc5e1dvIdFca-vN-IFI,9296
|
|
43
|
-
pyconvexity-0.3.8.
|
|
44
|
-
pyconvexity-0.3.8.
|
|
45
|
-
pyconvexity-0.3.8.
|
|
46
|
-
pyconvexity-0.3.8.
|
|
46
|
+
pyconvexity-0.3.8.post5.dist-info/METADATA,sha256=Sls9yFmzwrnujuODybzOPn5Z7s-8DoikRaHfto5sbuM,4886
|
|
47
|
+
pyconvexity-0.3.8.post5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
48
|
+
pyconvexity-0.3.8.post5.dist-info/top_level.txt,sha256=wFPEDXVaebR3JO5Tt3HNse-ws5aROCcxEco15d6j64s,12
|
|
49
|
+
pyconvexity-0.3.8.post5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|