pyconvexity 0.5.0__tar.gz → 0.5.0.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.

Files changed (51) hide show
  1. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/PKG-INFO +1 -1
  2. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/pyproject.toml +2 -2
  3. pyconvexity-0.5.0.post1/src/pyconvexity/_version.py +1 -0
  4. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/solver.py +11 -373
  5. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity.egg-info/PKG-INFO +1 -1
  6. pyconvexity-0.5.0/src/pyconvexity/_version.py +0 -1
  7. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/README.md +0 -0
  8. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/setup.cfg +0 -0
  9. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/__init__.py +0 -0
  10. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/core/__init__.py +0 -0
  11. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/core/database.py +0 -0
  12. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/core/errors.py +0 -0
  13. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/core/types.py +0 -0
  14. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/dashboard.py +0 -0
  15. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/README.md +0 -0
  16. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/__init__.py +0 -0
  17. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/loaders/__init__.py +0 -0
  18. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/loaders/cache.py +0 -0
  19. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/schema/01_core_schema.sql +0 -0
  20. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/schema/02_data_metadata.sql +0 -0
  21. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/schema/03_validation_data.sql +0 -0
  22. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/sources/__init__.py +0 -0
  23. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/data/sources/gem.py +0 -0
  24. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/io/__init__.py +0 -0
  25. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/io/excel_exporter.py +0 -0
  26. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/io/excel_importer.py +0 -0
  27. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/io/netcdf_exporter.py +0 -0
  28. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/io/netcdf_importer.py +0 -0
  29. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/__init__.py +0 -0
  30. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/attributes.py +0 -0
  31. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/carriers.py +0 -0
  32. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/components.py +0 -0
  33. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/network.py +0 -0
  34. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/results.py +0 -0
  35. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/models/scenarios.py +0 -0
  36. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/__init__.py +0 -0
  37. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/__init__.py +0 -0
  38. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/api.py +0 -0
  39. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/batch_loader.py +0 -0
  40. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/builder.py +0 -0
  41. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/clearing_price.py +0 -0
  42. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/constraints.py +0 -0
  43. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/solvers/pypsa/storage.py +0 -0
  44. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/timeseries.py +0 -0
  45. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/validation/__init__.py +0 -0
  46. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity/validation/rules.py +0 -0
  47. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity.egg-info/SOURCES.txt +0 -0
  48. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity.egg-info/dependency_links.txt +0 -0
  49. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity.egg-info/requires.txt +0 -0
  50. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/src/pyconvexity.egg-info/top_level.txt +0 -0
  51. {pyconvexity-0.5.0 → pyconvexity-0.5.0.post1}/tests/test_core_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyconvexity
3
- Version: 0.5.0
3
+ Version: 0.5.0.post1
4
4
  Summary: Python library for energy system modeling and optimization with PyPSA
5
5
  Author-email: Convexity Team <info@convexity.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyconvexity"
7
- version = "0.5.0"
7
+ version = "0.5.0.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 = 88
82
82
 
83
83
  [tool.mypy]
84
- python_version = "0.5.0"
84
+ python_version = "0.5.0.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.5.0.post1"
@@ -38,81 +38,6 @@ class NetworkSolver:
38
38
  "Please ensure it is installed correctly in the environment."
39
39
  ) from e
40
40
 
41
- def _get_user_settings_path(self):
42
- """Get the path to the user settings file (same location as Tauri uses)"""
43
- try:
44
- import platform
45
- import os
46
- from pathlib import Path
47
-
48
- system = platform.system()
49
- if system == "Darwin": # macOS
50
- home = Path.home()
51
- app_data_dir = (
52
- home / "Library" / "Application Support" / "com.convexity.desktop"
53
- )
54
- elif system == "Windows":
55
- app_data_dir = (
56
- Path(os.environ.get("APPDATA", "")) / "com.convexity.desktop"
57
- )
58
- else: # Linux
59
- home = Path.home()
60
- app_data_dir = home / ".local" / "share" / "com.convexity.desktop"
61
-
62
- settings_file = app_data_dir / "user_settings.json"
63
- return settings_file if settings_file.exists() else None
64
-
65
- except Exception as e:
66
- return None
67
-
68
- def _resolve_default_solver(self) -> str:
69
- """Resolve 'default' solver to user's preferred solver"""
70
- try:
71
- import json
72
-
73
- settings_path = self._get_user_settings_path()
74
- if not settings_path:
75
- return "highs"
76
-
77
- with open(settings_path, "r") as f:
78
- user_settings = json.load(f)
79
-
80
- # Get default solver from user settings
81
- default_solver = user_settings.get("default_solver", "highs")
82
-
83
- # Validate that it's a known solver
84
- known_solvers = [
85
- "highs",
86
- "gurobi",
87
- "gurobi (barrier)",
88
- "gurobi (barrier homogeneous)",
89
- "gurobi (barrier+crossover balanced)",
90
- "gurobi (dual simplex)",
91
- "mosek",
92
- "mosek (default)",
93
- "mosek (barrier)",
94
- "mosek (barrier+crossover)",
95
- "mosek (dual simplex)",
96
- "copt",
97
- "copt (barrier)",
98
- "copt (barrier homogeneous)",
99
- "copt (barrier+crossover)",
100
- "copt (dual simplex)",
101
- "copt (concurrent)",
102
- "cplex",
103
- "glpk",
104
- "cbc",
105
- "scip",
106
- ]
107
-
108
- if default_solver in known_solvers:
109
- return default_solver
110
- else:
111
- return "highs"
112
-
113
- except Exception as e:
114
- return "highs"
115
-
116
41
  def solve_network(
117
42
  self,
118
43
  network: "pypsa.Network",
@@ -269,24 +194,19 @@ class NetworkSolver:
269
194
  custom_solver_config: Optional[Dict[str, Any]] = None,
270
195
  ) -> tuple[str, Optional[Dict[str, Any]]]:
271
196
  """
272
- Get the actual solver name and options for special solver configurations.
197
+ Get the actual solver name and options for solver configurations.
273
198
 
274
199
  Args:
275
- solver_name: The solver name (e.g., 'gurobi (barrier)', 'highs', 'custom')
200
+ solver_name: The solver name (e.g., 'highs', 'gurobi', 'custom')
276
201
  solver_options: Optional additional solver options
277
- custom_solver_config: Optional custom solver configuration for solver_name='custom'
202
+ custom_solver_config: Custom solver configuration (from frontend)
278
203
  Format: {"solver": "actual_solver_name", "solver_options": {...}}
279
204
 
280
205
  Returns:
281
206
  Tuple of (actual_solver_name, solver_options_dict)
282
207
  """
283
- # Handle "custom" solver with custom configuration
284
- if solver_name == "custom":
285
- if not custom_solver_config:
286
- raise ValueError(
287
- "custom_solver_config must be provided when solver_name='custom'"
288
- )
289
-
208
+ # Handle "custom" solver with custom configuration from frontend
209
+ if solver_name == "custom" and custom_solver_config:
290
210
  if "solver" not in custom_solver_config:
291
211
  raise ValueError(
292
212
  "custom_solver_config must contain 'solver' key with the actual solver name"
@@ -307,294 +227,12 @@ class NetworkSolver:
307
227
 
308
228
  return actual_solver, merged_options
309
229
 
310
- # Handle "default" solver
311
- if solver_name == "default":
312
- # Try to read user's default solver preference
313
- actual_solver = self._resolve_default_solver()
314
- return actual_solver, solver_options
315
-
316
- # Handle special Gurobi configurations
317
- if solver_name == "gurobi (barrier)":
318
- gurobi_barrier_options = {
319
- "solver_options": {
320
- "Method": 2, # Barrier
321
- "Crossover": 0, # Skip crossover
322
- "MIPGap": 0.05, # 5% gap
323
- "Threads": 0, # Use all cores (0 = auto)
324
- "Presolve": 2, # Aggressive presolve
325
- "ConcurrentMIP": 1, # Parallel root strategies
326
- "BarConvTol": 1e-4, # Relaxed barrier convergence
327
- "FeasibilityTol": 1e-5,
328
- "OptimalityTol": 1e-5,
329
- "NumericFocus": 1, # Improve stability
330
- "PreSparsify": 1,
331
- }
332
- }
333
- # Merge with any additional options
334
- if solver_options:
335
- gurobi_barrier_options.update(solver_options)
336
- return "gurobi", gurobi_barrier_options
337
-
338
- elif solver_name == "gurobi (barrier homogeneous)":
339
- gurobi_barrier_homogeneous_options = {
340
- "solver_options": {
341
- "Method": 2, # Barrier
342
- "Crossover": 0, # Skip crossover
343
- "MIPGap": 0.05,
344
- "Threads": 0, # Use all cores (0 = auto)
345
- "Presolve": 2,
346
- "ConcurrentMIP": 1,
347
- "BarConvTol": 1e-4,
348
- "FeasibilityTol": 1e-5,
349
- "OptimalityTol": 1e-5,
350
- "NumericFocus": 1,
351
- "PreSparsify": 1,
352
- "BarHomogeneous": 1, # Enable homogeneous barrier algorithm
353
- }
354
- }
355
- if solver_options:
356
- gurobi_barrier_homogeneous_options.update(solver_options)
357
- return "gurobi", gurobi_barrier_homogeneous_options
358
-
359
- elif solver_name == "gurobi (barrier+crossover balanced)":
360
- gurobi_options_balanced = {
361
- "solver_options": {
362
- "Method": 2,
363
- "Crossover": 1, # Dual crossover
364
- "MIPGap": 0.01,
365
- "Threads": 0, # Use all cores (0 = auto)
366
- "Presolve": 2,
367
- "Heuristics": 0.1,
368
- "Cuts": 2,
369
- "ConcurrentMIP": 1,
370
- "BarConvTol": 1e-6,
371
- "FeasibilityTol": 1e-6,
372
- "OptimalityTol": 1e-6,
373
- "NumericFocus": 1,
374
- "PreSparsify": 1,
375
- }
376
- }
377
- if solver_options:
378
- gurobi_options_balanced.update(solver_options)
379
- return "gurobi", gurobi_options_balanced
380
-
381
- elif solver_name == "gurobi (dual simplex)":
382
- gurobi_dual_options = {
383
- "solver_options": {
384
- "Method": 1, # Dual simplex method
385
- "Threads": 0, # Use all available cores
386
- "Presolve": 2, # Aggressive presolve
387
- }
388
- }
389
- if solver_options:
390
- gurobi_dual_options.update(solver_options)
391
- return "gurobi", gurobi_dual_options
392
-
393
- # Handle special Mosek configurations
394
- elif solver_name == "mosek (default)":
395
- # No custom options - let Mosek use its default configuration
396
- mosek_default_options = {
397
- "solver_options": {
398
- "MSK_DPAR_MIO_REL_GAP_CONST": 0.05, # MIP relative gap tolerance (5% to match Gurobi)
399
- "MSK_DPAR_MIO_MAX_TIME": 36000, # Max time 10 hours
400
- }
401
- }
402
- if solver_options:
403
- mosek_default_options["solver_options"].update(solver_options)
404
- return "mosek", mosek_default_options
405
-
406
- elif solver_name == "mosek (barrier)":
407
- mosek_barrier_options = {
408
- "solver_options": {
409
- "MSK_IPAR_INTPNT_BASIS": 0, # Skip crossover (barrier-only) - 0 = MSK_BI_NEVER
410
- "MSK_DPAR_INTPNT_TOL_REL_GAP": 1e-4, # Match Gurobi barrier tolerance
411
- "MSK_DPAR_INTPNT_TOL_PFEAS": 1e-5, # Match Gurobi primal feasibility
412
- "MSK_DPAR_INTPNT_TOL_DFEAS": 1e-5, # Match Gurobi dual feasibility
413
- # Removed MSK_DPAR_INTPNT_TOL_INFEAS - was 1000x tighter than other tolerances!
414
- "MSK_IPAR_NUM_THREADS": 0, # Use all available cores (0 = auto)
415
- "MSK_IPAR_PRESOLVE_USE": 2, # Aggressive presolve (match Gurobi Presolve=2)
416
- "MSK_DPAR_MIO_REL_GAP_CONST": 0.05, # Match Gurobi 5% MIP gap
417
- "MSK_IPAR_MIO_ROOT_OPTIMIZER": 4, # Use interior-point for MIP root
418
- "MSK_DPAR_MIO_MAX_TIME": 36000, # Max time 10 hour
419
- }
420
- }
421
- if solver_options:
422
- mosek_barrier_options["solver_options"].update(solver_options)
423
- return "mosek", mosek_barrier_options
424
-
425
- elif solver_name == "mosek (barrier+crossover)":
426
- mosek_barrier_crossover_options = {
427
- "solver_options": {
428
- "MSK_IPAR_INTPNT_BASIS": 1, # Always crossover (1 = MSK_BI_ALWAYS)
429
- "MSK_DPAR_INTPNT_TOL_REL_GAP": 1e-4, # Match Gurobi barrier tolerance (was 1e-6)
430
- "MSK_DPAR_INTPNT_TOL_PFEAS": 1e-5, # Match Gurobi (was 1e-6)
431
- "MSK_DPAR_INTPNT_TOL_DFEAS": 1e-5, # Match Gurobi (was 1e-6)
432
- "MSK_IPAR_NUM_THREADS": 0, # Use all available cores (0 = auto)
433
- "MSK_DPAR_MIO_REL_GAP_CONST": 0.05, # Match Gurobi 5% MIP gap (was 1e-6)
434
- "MSK_IPAR_MIO_ROOT_OPTIMIZER": 4, # Use interior-point for MIP root
435
- "MSK_DPAR_MIO_MAX_TIME": 36000, # Max time 10 hour (safety limit)
436
- }
437
- }
438
- if solver_options:
439
- mosek_barrier_crossover_options["solver_options"].update(solver_options)
440
- return "mosek", mosek_barrier_crossover_options
441
-
442
- elif solver_name == "mosek (dual simplex)":
443
- mosek_dual_options = {
444
- "solver_options": {
445
- "MSK_IPAR_NUM_THREADS": 0, # Use all available cores (0 = automatic)
446
- "MSK_IPAR_PRESOLVE_USE": 1, # Force presolve
447
- "MSK_DPAR_MIO_REL_GAP_CONST": 0.05, # Match Gurobi 5% MIP gap (was 1e-6)
448
- "MSK_IPAR_MIO_ROOT_OPTIMIZER": 1, # Use dual simplex for MIP root
449
- "MSK_DPAR_MIO_MAX_TIME": 36000, # Max time 10 hour (safety limit)
450
- }
451
- }
452
- if solver_options:
453
- mosek_dual_options["solver_options"].update(solver_options)
454
- return "mosek", mosek_dual_options
455
-
456
- # Check if this is a known valid solver name
457
- elif solver_name == "mosek":
458
- # Add default MILP-friendly settings for plain Mosek
459
- mosek_defaults = {
460
- "solver_options": {
461
- "MSK_DPAR_MIO_REL_GAP_CONST": 0.05, # Match Gurobi 5% MIP gap (was 1e-4)
462
- "MSK_DPAR_MIO_MAX_TIME": 36000, # Max time 10 hours
463
- "MSK_IPAR_NUM_THREADS": 0, # Use all cores (0 = auto)
464
- }
465
- }
466
- if solver_options:
467
- mosek_defaults["solver_options"].update(solver_options)
468
- return solver_name, mosek_defaults
469
-
470
- elif solver_name == "gurobi":
471
- # Add default MILP-friendly settings for plain Gurobi (for consistency)
472
- gurobi_defaults = {
473
- "solver_options": {
474
- "MIPGap": 1e-4, # 0.01% gap
475
- "TimeLimit": 3600, # 1 hour
476
- "Threads": 0, # Use all cores
477
- "OutputFlag": 1, # Enable output
478
- }
479
- }
480
- if solver_options:
481
- gurobi_defaults["solver_options"].update(solver_options)
482
- return solver_name, gurobi_defaults
483
-
484
- # Handle special COPT configurations
485
- elif solver_name == "copt (barrier)":
486
- copt_barrier_options = {
487
- "solver_options": {
488
- "LpMethod": 2, # Barrier method
489
- "Crossover": 0, # Skip crossover for speed
490
- "RelGap": 0.05, # 5% MIP gap (match Gurobi)
491
- "TimeLimit": 7200, # 1 hour time limit
492
- "Threads": -1, # 4 threads (memory-conscious)
493
- "Presolve": 3, # Aggressive presolve
494
- "Scaling": 1, # Enable scaling
495
- "FeasTol": 1e-5, # Match Gurobi feasibility
496
- "DualTol": 1e-5, # Match Gurobi dual tolerance
497
- # MIP performance settings
498
- "CutLevel": 2, # Normal cut generation
499
- "HeurLevel": 3, # Aggressive heuristics
500
- "StrongBranching": 1, # Fast strong branching
501
- }
502
- }
503
- if solver_options:
504
- copt_barrier_options["solver_options"].update(solver_options)
505
- return "copt", copt_barrier_options
506
-
507
- elif solver_name == "copt (barrier homogeneous)":
508
- copt_barrier_homogeneous_options = {
509
- "solver_options": {
510
- "LpMethod": 2, # Barrier method
511
- "Crossover": 0, # Skip crossover
512
- "BarHomogeneous": 1, # Use homogeneous self-dual form
513
- "RelGap": 0.05, # 5% MIP gap
514
- "TimeLimit": 3600, # 1 hour
515
- "Threads": -1, # 4 threads (memory-conscious)
516
- "Presolve": 3, # Aggressive presolve
517
- "Scaling": 1, # Enable scaling
518
- "FeasTol": 1e-5,
519
- "DualTol": 1e-5,
520
- # MIP performance settings
521
- "CutLevel": 2, # Normal cuts
522
- "HeurLevel": 3, # Aggressive heuristics
523
- "StrongBranching": 1, # Fast strong branching
524
- }
525
- }
526
- if solver_options:
527
- copt_barrier_homogeneous_options["solver_options"].update(
528
- solver_options
529
- )
530
- return "copt", copt_barrier_homogeneous_options
531
-
532
- elif solver_name == "copt (barrier+crossover)":
533
- copt_barrier_crossover_options = {
534
- "solver_options": {
535
- "LpMethod": 2, # Barrier method
536
- "Crossover": 1, # Enable crossover for better solutions
537
- "RelGap": 0.05, # 5% MIP gap (relaxed for faster solves)
538
- "TimeLimit": 36000, # 10 hour
539
- "Threads": -1, # Use all cores
540
- "Presolve": 2, # Aggressive presolve
541
- "Scaling": 1, # Enable scaling
542
- "FeasTol": 1e-4, # Tighter feasibility
543
- "DualTol": 1e-4, # Tighter dual tolerance
544
- }
545
- }
546
- if solver_options:
547
- copt_barrier_crossover_options["solver_options"].update(solver_options)
548
- return "copt", copt_barrier_crossover_options
549
-
550
- elif solver_name == "copt (dual simplex)":
551
- copt_dual_simplex_options = {
552
- "solver_options": {
553
- "LpMethod": 1, # Dual simplex method
554
- "RelGap": 0.05, # 5% MIP gap
555
- "TimeLimit": 3600, # 1 hour
556
- "Threads": -1, # Use all cores
557
- "Presolve": 3, # Aggressive presolve
558
- "Scaling": 1, # Enable scaling
559
- "FeasTol": 1e-6,
560
- "DualTol": 1e-6,
561
- # MIP performance settings
562
- "CutLevel": 2, # Normal cuts
563
- "HeurLevel": 2, # Normal heuristics
564
- "StrongBranching": 1, # Fast strong branching
565
- }
566
- }
567
- if solver_options:
568
- copt_dual_simplex_options["solver_options"].update(solver_options)
569
- return "copt", copt_dual_simplex_options
570
-
571
- elif solver_name == "copt (concurrent)":
572
- copt_concurrent_options = {
573
- "solver_options": {
574
- "LpMethod": 4, # Concurrent (simplex + barrier)
575
- "RelGap": 0.05, # 5% MIP gap
576
- "TimeLimit": 3600, # 1 hour
577
- "Threads": -1, # Use all cores
578
- "Presolve": 3, # Aggressive presolve
579
- "Scaling": 1, # Enable scaling
580
- "FeasTol": 1e-5,
581
- "DualTol": 1e-5,
582
- # MIP performance settings
583
- "CutLevel": 2, # Normal cuts
584
- "HeurLevel": 3, # Aggressive heuristics
585
- "StrongBranching": 1, # Fast strong branching
586
- }
587
- }
588
- if solver_options:
589
- copt_concurrent_options["solver_options"].update(solver_options)
590
- return "copt", copt_concurrent_options
591
-
592
- elif solver_name in ["highs", "cplex", "glpk", "cbc", "scip", "copt"]:
593
- return solver_name, solver_options
594
-
595
- else:
596
- # Unknown solver name - fall back to highs
597
- return "highs", solver_options
230
+ # For all other cases, pass through solver name and options directly
231
+ # The frontend is responsible for resolving presets and defaults
232
+ if solver_options:
233
+ return solver_name, {"solver_options": solver_options}
234
+
235
+ return solver_name, None
598
236
 
599
237
  def _detect_constraint_type(self, constraint_code: str) -> str:
600
238
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyconvexity
3
- Version: 0.5.0
3
+ Version: 0.5.0.post1
4
4
  Summary: Python library for energy system modeling and optimization with PyPSA
5
5
  Author-email: Convexity Team <info@convexity.com>
6
6
  License: MIT
@@ -1 +0,0 @@
1
- __version__ = "0.5.0"
File without changes
File without changes