pyconvexity 0.4.2.post1__py3-none-any.whl → 0.4.6__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.

@@ -29,6 +29,15 @@ class ResultStorage:
29
29
  and storing them back to the database with proper validation and error handling.
30
30
  """
31
31
 
32
+ def __init__(self, verbose: bool = False):
33
+ """
34
+ Initialize ResultStorage.
35
+
36
+ Args:
37
+ verbose: Enable detailed logging output
38
+ """
39
+ self.verbose = verbose
40
+
32
41
  def store_results(
33
42
  self,
34
43
  conn,
@@ -51,71 +60,30 @@ class ResultStorage:
51
60
  run_id = solve_result.get("run_id", str(uuid.uuid4()))
52
61
 
53
62
  try:
54
- logger.info("=" * 80)
55
- logger.info("📊 STARTING RESULT STORAGE")
56
- logger.info("=" * 80)
57
-
58
63
  # Store component results
59
- logger.info("📝 Step 1: Storing component results (timeseries data)...")
60
64
  component_stats = self._store_component_results(conn, network, scenario_id)
61
- logger.info(f"✅ Component results stored: {component_stats}")
62
65
 
63
66
  # Calculate network statistics first
64
- logger.info("📊 Step 2: Calculating network statistics...")
65
67
  network_stats = self._calculate_network_statistics(
66
68
  conn, network, solve_result
67
69
  )
68
- logger.info(f"✅ Network statistics calculated")
69
- logger.info(
70
- f" - Total generation: {network_stats.get('core_summary', {}).get('total_generation_mwh', 0):.2f} MWh"
71
- )
72
- logger.info(
73
- f" - Total cost: {network_stats.get('core_summary', {}).get('total_cost', 0):.2f}"
74
- )
75
70
 
76
71
  # Store solve summary with network statistics
77
- logger.info(
78
- "💾 Step 3: Storing solve summary to network_solve_results table..."
79
- )
80
72
  self._store_solve_summary(conn, solve_result, scenario_id, network_stats)
81
- # Explicit commit after storing summary
82
73
  conn.commit()
83
- logger.info("✅ Solve summary stored and committed successfully")
84
74
 
85
75
  # Store year-based statistics if available
86
76
  year_stats_stored = 0
87
77
  if solve_result.get("year_statistics"):
88
- logger.info(
89
- f"📅 Step 4: Storing year-based statistics ({len(solve_result['year_statistics'])} years)..."
90
- )
91
78
  year_stats_stored = self._store_year_based_statistics(
92
79
  conn, network, solve_result["year_statistics"], scenario_id
93
80
  )
94
- # Explicit commit after storing year statistics
95
81
  conn.commit()
96
- logger.info(
97
- f"✅ Year-based statistics stored and committed: {year_stats_stored} years"
98
- )
99
- else:
100
- logger.warning(
101
- "⚠️ No year_statistics in solve_result - skipping year-based storage"
102
- )
103
- logger.info(
104
- f" Available solve_result keys: {list(solve_result.keys())}"
105
- )
106
-
107
- logger.info("=" * 80)
108
- logger.info("✅ RESULT STORAGE COMPLETED SUCCESSFULLY")
109
- logger.info("=" * 80)
110
82
 
111
- # Final verification - check if data was actually stored
112
- cursor = conn.execute(
113
- "SELECT COUNT(*) FROM network_solve_results WHERE scenario_id IS ? ",
114
- (scenario_id,) if scenario_id is not None else (None,),
115
- )
116
- count = cursor.fetchone()[0]
83
+ total_gen = network_stats.get("core_summary", {}).get("total_generation_mwh", 0)
84
+ total_cost = network_stats.get("core_summary", {}).get("total_cost", 0)
117
85
  logger.info(
118
- f"🔍 Verification: {count} solve result(s) found in network_solve_results table"
86
+ f"Results stored: {total_gen:.0f} MWh generation, {total_cost:.0f} cost, {year_stats_stored} years"
119
87
  )
120
88
 
121
89
  return {
@@ -127,10 +95,7 @@ class ResultStorage:
127
95
  }
128
96
 
129
97
  except Exception as e:
130
- logger.error("=" * 80)
131
- logger.error(f"❌ RESULT STORAGE FAILED: {e}")
132
- logger.error("=" * 80)
133
- logger.exception("Full traceback:")
98
+ logger.error(f"Result storage failed: {e}")
134
99
  return {
135
100
  "component_stats": {},
136
101
  "network_stats": {},
@@ -382,22 +347,12 @@ class ResultStorage:
382
347
  ):
383
348
  """Store solve summary to network_solve_results table (single network per database)."""
384
349
  try:
385
- # Debug logging
386
- logger.info(
387
- f"📝 _store_solve_summary called with scenario_id={scenario_id} (type={type(scenario_id)})"
388
- )
389
-
390
350
  # Prepare solve summary data
391
351
  solver_name = solve_result.get("solver_name", "unknown")
392
352
  solve_status = solve_result.get("status", "unknown")
393
353
  objective_value = solve_result.get("objective_value")
394
354
  solve_time = solve_result.get("solve_time", 0.0)
395
355
 
396
- logger.info(f" solver_name: {solver_name}")
397
- logger.info(f" solve_status: {solve_status}")
398
- logger.info(f" objective_value: {objective_value}")
399
- logger.info(f" solve_time: {solve_time}")
400
-
401
356
  # Create enhanced solve result with network statistics for serialization
402
357
  enhanced_solve_result = {
403
358
  **solve_result,
@@ -406,31 +361,20 @@ class ResultStorage:
406
361
 
407
362
  # Delete existing result for this scenario first (handles NULL scenario_id correctly)
408
363
  if scenario_id is None:
409
- logger.info("🗑️ Deleting existing base scenario (NULL) results")
410
- delete_result = conn.execute(
364
+ conn.execute(
411
365
  "DELETE FROM network_solve_results WHERE scenario_id IS NULL"
412
366
  )
413
- logger.info(f" Deleted {delete_result.rowcount} existing rows")
414
367
  else:
415
- logger.info(
416
- f"🗑️ Deleting existing results for scenario_id={scenario_id}"
417
- )
418
- delete_result = conn.execute(
368
+ conn.execute(
419
369
  "DELETE FROM network_solve_results WHERE scenario_id = ?",
420
370
  (scenario_id,),
421
371
  )
422
- logger.info(f" Deleted {delete_result.rowcount} existing rows")
423
372
 
424
- logger.info(f"💾 Inserting solve results for scenario_id={scenario_id}")
425
- logger.info(
426
- f" Serializing results_json ({len(str(enhanced_solve_result))} chars)..."
427
- )
428
373
  results_json = self._serialize_results_json(enhanced_solve_result)
429
- logger.info(f" Serializing metadata_json...")
430
374
  metadata_json = self._serialize_metadata_json(enhanced_solve_result)
431
375
 
432
376
  # Insert new solve results summary
433
- insert_result = conn.execute(
377
+ conn.execute(
434
378
  """
435
379
  INSERT INTO network_solve_results (
436
380
  scenario_id, solver_name, solve_type, solve_status,
@@ -449,20 +393,8 @@ class ResultStorage:
449
393
  ),
450
394
  )
451
395
 
452
- logger.info(f"✅ Inserted solve summary (rowid={insert_result.lastrowid})")
453
-
454
- # Verify insertion
455
- verify_cursor = conn.execute(
456
- "SELECT COUNT(*), solver_name, solve_status FROM network_solve_results WHERE scenario_id IS ?",
457
- (scenario_id,) if scenario_id is not None else (None,),
458
- )
459
- verify_result = verify_cursor.fetchone()
460
- logger.info(
461
- f"🔍 Verification: {verify_result[0]} row(s) with solver={verify_result[1]}, status={verify_result[2]}"
462
- )
463
-
464
396
  except Exception as e:
465
- logger.error(f" FAILED to store solve summary: {e}", exc_info=True)
397
+ logger.error(f"Failed to store solve summary: {e}")
466
398
  raise # Re-raise to trigger rollback
467
399
 
468
400
  def _calculate_network_statistics(
@@ -605,30 +537,19 @@ class ResultStorage:
605
537
  This is the primary calculation - per-year stats will be calculated separately.
606
538
  """
607
539
  try:
608
- logger.info("🔍 _calculate_carrier_statistics: Starting calculation...")
609
-
610
540
  # Calculate all-year statistics directly from the network
611
541
  # Extract years from network snapshots
612
542
  if hasattr(network.snapshots, "levels"):
613
543
  # Multi-period optimization - get years from period level
614
544
  period_values = network.snapshots.get_level_values(0)
615
545
  years = sorted(period_values.unique())
616
- logger.info(
617
- f" Detected multi-period optimization with years: {years}"
618
- )
619
546
  elif hasattr(network.snapshots, "year"):
620
547
  years = sorted(network.snapshots.year.unique())
621
- logger.info(f" Detected DatetimeIndex with years: {years}")
622
548
  elif hasattr(network, "_available_years"):
623
549
  years = network._available_years
624
- logger.info(f" Using _available_years: {years}")
625
550
  else:
626
551
  years = [2020] # Fallback
627
- logger.warning(f" No year information found, using fallback: {years}")
628
-
629
- logger.info(
630
- f"📅 Calculating all-year carrier statistics for {len(years)} years: {years}"
631
- )
552
+ logger.warning(f"No year information found, using fallback: {years}")
632
553
 
633
554
  # Calculate per-year statistics first
634
555
  all_year_stats = {
@@ -694,51 +615,10 @@ class ResultStorage:
694
615
  year_stats["energy_capacity_by_carrier"].get(carrier, 0.0)
695
616
  )
696
617
 
697
- logger.info(f"Calculated all-year carrier statistics:")
698
- logger.info(
699
- f" Total dispatch: {sum(all_year_stats['dispatch_by_carrier'].values()):.2f} MWh"
700
- )
701
- logger.info(
702
- f" Total emissions: {sum(all_year_stats['emissions_by_carrier'].values()):.2f} tonnes CO2"
703
- )
704
- logger.info(
705
- f" Total capital cost: {sum(all_year_stats['capital_cost_by_carrier'].values()):.2f} USD"
706
- )
707
- logger.info(
708
- f" Total operational cost: {sum(all_year_stats['operational_cost_by_carrier'].values()):.2f} USD"
709
- )
710
- logger.info(
711
- f" Final power capacity: {sum(all_year_stats['power_capacity_by_carrier'].values()):.2f} MW"
712
- )
713
-
714
- # Detailed logging for cost and emission statistics by carrier
715
- logger.info("=" * 80)
716
- logger.info("📊 DETAILED CARRIER STATISTICS (RAW VALUES FROM MODEL):")
717
- logger.info("=" * 80)
718
- for carrier in sorted(all_carriers_with_unmet):
719
- dispatch = all_year_stats['dispatch_by_carrier'].get(carrier, 0.0)
720
- emissions = all_year_stats['emissions_by_carrier'].get(carrier, 0.0)
721
- capital_cost = all_year_stats['capital_cost_by_carrier'].get(carrier, 0.0)
722
- operational_cost = all_year_stats['operational_cost_by_carrier'].get(carrier, 0.0)
723
- total_cost = all_year_stats['total_system_cost_by_carrier'].get(carrier, 0.0)
724
- power_cap = all_year_stats['power_capacity_by_carrier'].get(carrier, 0.0)
725
- energy_cap = all_year_stats['energy_capacity_by_carrier'].get(carrier, 0.0)
726
-
727
- if dispatch > 0.001 or emissions > 0.001 or capital_cost > 0.001 or operational_cost > 0.001:
728
- logger.info(f" {carrier}:")
729
- logger.info(f" - Dispatch: {dispatch:.2f} MWh")
730
- logger.info(f" - Emissions: {emissions:.2f} tonnes CO2")
731
- logger.info(f" - Capital Cost: {capital_cost:.2f} USD")
732
- logger.info(f" - Operational Cost: {operational_cost:.2f} USD")
733
- logger.info(f" - Total System Cost: {total_cost:.2f} USD")
734
- logger.info(f" - Power Capacity: {power_cap:.2f} MW")
735
- logger.info(f" - Energy Capacity: {energy_cap:.2f} MWh")
736
- logger.info("=" * 80)
737
-
738
618
  return all_year_stats
739
619
 
740
620
  except Exception as e:
741
- logger.error(f"Failed to calculate carrier statistics: {e}", exc_info=True)
621
+ logger.error(f"Failed to calculate carrier statistics: {e}")
742
622
  return {
743
623
  "dispatch_by_carrier": {},
744
624
  "power_capacity_by_carrier": {},
@@ -836,19 +716,15 @@ class ResultStorage:
836
716
  )
837
717
 
838
718
  stored_count += 1
839
- logger.info(f"Stored year-based statistics for year {year}")
840
719
 
841
720
  except Exception as e:
842
721
  logger.error(f"Failed to store statistics for year {year}: {e}")
843
722
  continue
844
723
 
845
- logger.info(
846
- f"Successfully stored year-based statistics for {stored_count} years"
847
- )
848
724
  return stored_count
849
725
 
850
726
  except Exception as e:
851
- logger.error(f"Failed to store year-based statistics: {e}", exc_info=True)
727
+ logger.error(f"Failed to store year-based statistics: {e}")
852
728
  return 0
853
729
 
854
730
  def _calculate_year_carrier_statistics(
@@ -859,7 +735,6 @@ class ResultStorage:
859
735
  For now, only calculate capacity statistics.
860
736
  """
861
737
  try:
862
- logger.info(f" 🔍 Calculating statistics for year {year}...")
863
738
  # Initialize carrier statistics
864
739
  carrier_stats = {
865
740
  "dispatch_by_carrier": {},
@@ -1136,24 +1011,6 @@ class ResultStorage:
1136
1011
 
1137
1012
  # Calculate annualized capital cost for this year
1138
1013
  annual_capital_cost = capacity_mw * capital_cost_per_mw
1139
-
1140
- # Log capital cost calculation details with unit check
1141
- if annual_capital_cost > 0.001:
1142
- logger.info(
1143
- f" 💰 Capital cost calc for {gen_name} ({carrier_name}) in year {year}: "
1144
- f"{capacity_mw:.2f} MW × {capital_cost_per_mw:.2f} currency/MW = {annual_capital_cost:.2f} currency"
1145
- )
1146
- # Check if values seem too small (might be in kUSD instead of USD)
1147
- if capital_cost_per_mw > 0 and capital_cost_per_mw < 1:
1148
- logger.warning(
1149
- f" ⚠️ WARNING: capital_cost_per_mw ({capital_cost_per_mw:.2f}) seems very small. "
1150
- f"Expected USD/MW but might be in kUSD/MW (thousands)?"
1151
- )
1152
- if annual_capital_cost > 0 and annual_capital_cost < 1000:
1153
- logger.warning(
1154
- f" ⚠️ WARNING: annual_capital_cost ({annual_capital_cost:.2f}) seems very small. "
1155
- f"Expected USD but might be in kUSD (thousands)?"
1156
- )
1157
1014
 
1158
1015
  if carrier_name in carrier_stats["capital_cost_by_carrier"]:
1159
1016
  carrier_stats["capital_cost_by_carrier"][
@@ -1459,24 +1316,6 @@ class ResultStorage:
1459
1316
 
1460
1317
  # Calculate operational cost for this year
1461
1318
  operational_cost = generation_mwh * marginal_cost
1462
-
1463
- # Log operational cost calculation details with unit check
1464
- if operational_cost > 0.001:
1465
- logger.info(
1466
- f" 💰 Operational cost calc for {gen_name} ({carrier_name}) in year {year}: "
1467
- f"{generation_mwh:.2f} MWh × {marginal_cost:.2f} currency/MWh = {operational_cost:.2f} currency"
1468
- )
1469
- # Check if values seem too small (might be in kUSD instead of USD)
1470
- if marginal_cost > 0 and marginal_cost < 1:
1471
- logger.warning(
1472
- f" ⚠️ WARNING: marginal_cost ({marginal_cost:.2f}) seems very small. "
1473
- f"Expected USD/MWh but might be in kUSD/MWh (thousands)?"
1474
- )
1475
- if operational_cost > 0 and operational_cost < 1000:
1476
- logger.warning(
1477
- f" ⚠️ WARNING: operational_cost ({operational_cost:.2f}) seems very small. "
1478
- f"Expected USD but might be in kUSD (thousands)?"
1479
- )
1480
1319
 
1481
1320
  if (
1482
1321
  carrier_name
@@ -1899,50 +1738,10 @@ class ResultStorage:
1899
1738
  carrier_name
1900
1739
  ] += capacity_mw
1901
1740
 
1902
- logger.info(f"Calculated year {year} carrier statistics:")
1903
- logger.info(
1904
- f" Dispatch: {sum(carrier_stats['dispatch_by_carrier'].values()):.2f} MWh"
1905
- )
1906
- logger.info(
1907
- f" Emissions: {sum(carrier_stats['emissions_by_carrier'].values()):.2f} tonnes CO2"
1908
- )
1909
- logger.info(
1910
- f" Capital cost: {sum(carrier_stats['capital_cost_by_carrier'].values()):.2f} USD"
1911
- )
1912
- logger.info(
1913
- f" Operational cost: {sum(carrier_stats['operational_cost_by_carrier'].values()):.2f} USD"
1914
- )
1915
- logger.info(
1916
- f" Total system cost: {sum(carrier_stats['total_system_cost_by_carrier'].values()):.2f} USD"
1917
- )
1918
- logger.info(
1919
- f" Power capacity: {sum(carrier_stats['power_capacity_by_carrier'].values()):.2f} MW"
1920
- )
1921
- logger.info(
1922
- f" Energy capacity: {sum(carrier_stats['energy_capacity_by_carrier'].values()):.2f} MWh"
1923
- )
1924
-
1925
- # Detailed logging for year-specific statistics
1926
- logger.info(f" 📊 Year {year} detailed carrier stats (RAW VALUES):")
1927
- for carrier in sorted(all_carriers_with_unmet):
1928
- dispatch = carrier_stats['dispatch_by_carrier'].get(carrier, 0.0)
1929
- emissions = carrier_stats['emissions_by_carrier'].get(carrier, 0.0)
1930
- capital_cost = carrier_stats['capital_cost_by_carrier'].get(carrier, 0.0)
1931
- operational_cost = carrier_stats['operational_cost_by_carrier'].get(carrier, 0.0)
1932
- total_cost = carrier_stats['total_system_cost_by_carrier'].get(carrier, 0.0)
1933
-
1934
- if dispatch > 0.001 or emissions > 0.001 or capital_cost > 0.001 or operational_cost > 0.001:
1935
- logger.info(f" {carrier}: dispatch={dispatch:.2f} MWh, emissions={emissions:.2f} tCO2, "
1936
- f"capital={capital_cost:.2f} USD, operational={operational_cost:.2f} USD, "
1937
- f"total={total_cost:.2f} USD")
1938
-
1939
1741
  return carrier_stats
1940
1742
 
1941
1743
  except Exception as e:
1942
- logger.error(
1943
- f"Failed to calculate year {year} carrier statistics: {e}",
1944
- exc_info=True,
1945
- )
1744
+ logger.error(f"Failed to calculate year {year} carrier statistics: {e}")
1946
1745
  return {
1947
1746
  "dispatch_by_carrier": {},
1948
1747
  "power_capacity_by_carrier": {},
@@ -2000,10 +1799,8 @@ class ResultStorage:
2000
1799
  )
2001
1800
 
2002
1801
  year_results = cursor.fetchall()
2003
- logger.info(f"Found {len(year_results)} year-based results to sum")
2004
1802
 
2005
1803
  if not year_results:
2006
- logger.warning(f"No year-based results found")
2007
1804
  return totals
2008
1805
 
2009
1806
  # For capacity: use the LAST YEAR only (final capacity state)
@@ -2031,10 +1828,6 @@ class ResultStorage:
2031
1828
  value or 0
2032
1829
  )
2033
1830
 
2034
- logger.info(
2035
- f"Used last year ({last_year}) capacity as all-year capacity"
2036
- )
2037
-
2038
1831
  except Exception as e:
2039
1832
  logger.error(f"Failed to process last year ({last_year}) results: {e}")
2040
1833
 
@@ -2091,35 +1884,10 @@ class ResultStorage:
2091
1884
  logger.error(f"Failed to process year {year} results: {e}")
2092
1885
  continue
2093
1886
 
2094
- logger.info(f"Summed carrier statistics across {len(year_results)} years:")
2095
- logger.info(
2096
- f" Final power capacity: {sum(totals['power_capacity_by_carrier'].values()):.2f} MW"
2097
- )
2098
- logger.info(
2099
- f" Final energy capacity: {sum(totals['energy_capacity_by_carrier'].values()):.2f} MWh"
2100
- )
2101
- logger.info(
2102
- f" Total dispatch: {sum(totals['dispatch_by_carrier'].values()):.2f} MWh"
2103
- )
2104
- logger.info(
2105
- f" Total emissions: {sum(totals['emissions_by_carrier'].values()):.2f} tonnes CO2"
2106
- )
2107
- logger.info(
2108
- f" Total capital cost: {sum(totals['capital_cost_by_carrier'].values()):.2f} USD"
2109
- )
2110
- logger.info(
2111
- f" Total operational cost: {sum(totals['operational_cost_by_carrier'].values()):.2f} USD"
2112
- )
2113
- logger.info(
2114
- f" Total system cost: {sum(totals['total_system_cost_by_carrier'].values()):.2f} USD"
2115
- )
2116
-
2117
1887
  return totals
2118
1888
 
2119
1889
  except Exception as e:
2120
- logger.error(
2121
- f"Failed to sum year-based carrier statistics: {e}", exc_info=True
2122
- )
1890
+ logger.error(f"Failed to sum year-based carrier statistics: {e}")
2123
1891
  # Return empty structure on error
2124
1892
  return {
2125
1893
  "dispatch_by_carrier": {},
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyconvexity
3
+ Version: 0.4.6
4
+ Summary: Python library for energy system modeling and optimization with PyPSA
5
+ Author-email: Convexity Team <info@convexity.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/bayesian-energy/pyconvexity
8
+ Project-URL: Repository, https://github.com/bayesian-energy/pyconvexity
9
+ Project-URL: Issues, https://github.com/bayesian-energy/pyconvexity/issues
10
+ Project-URL: Documentation, https://pyconvexity.readthedocs.io
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: pandas>=1.5.0
23
+ Requires-Dist: numpy>=1.21.0
24
+ Requires-Dist: pyarrow>=10.0.0
25
+ Provides-Extra: pypsa
26
+ Requires-Dist: pypsa>=0.25.0; extra == "pypsa"
27
+ Requires-Dist: networkx; extra == "pypsa"
28
+ Requires-Dist: scipy; extra == "pypsa"
29
+ Requires-Dist: xarray; extra == "pypsa"
30
+ Provides-Extra: excel
31
+ Requires-Dist: openpyxl>=3.0.0; extra == "excel"
32
+ Requires-Dist: xlsxwriter>=3.0.0; extra == "excel"
33
+ Provides-Extra: netcdf
34
+ Requires-Dist: netcdf4>=1.6.0; extra == "netcdf"
35
+ Requires-Dist: xarray>=2022.3.0; extra == "netcdf"
36
+ Provides-Extra: data
37
+ Requires-Dist: country-converter>=1.0.0; extra == "data"
38
+ Requires-Dist: pyyaml>=6.0.0; extra == "data"
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
41
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
42
+ Requires-Dist: black>=22.0.0; extra == "dev"
43
+ Requires-Dist: isort>=5.10.0; extra == "dev"
44
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
45
+ Requires-Dist: pre-commit>=2.20.0; extra == "dev"
46
+ Provides-Extra: all
47
+ Requires-Dist: pyconvexity[data,excel,netcdf,pypsa]; extra == "all"
48
+
49
+ # pyconvexity
50
+
51
+ Python library for energy system modeling and optimization with PyPSA.
52
+
53
+ [![PyPI version](https://badge.fury.io/py/pyconvexity.svg)](https://badge.fury.io/py/pyconvexity)
54
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
55
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
56
+
57
+ ## Overview
58
+
59
+ pyconvexity is the Python library that powers [Convexity](https://bayesian.energy/convexity), providing programmatic access to energy system modeling and optimization. It stores models in SQLite databases and integrates with PyPSA for solving.
60
+
61
+ ## Installation
62
+
63
+ ```bash
64
+ pip install pyconvexity
65
+ ```
66
+
67
+ With optional dependencies:
68
+
69
+ ```bash
70
+ pip install pyconvexity[pypsa] # PyPSA solver integration
71
+ pip install pyconvexity[excel] # Excel import/export
72
+ pip install pyconvexity[all] # All optional dependencies
73
+ ```
74
+
75
+ ## Quick Start
76
+
77
+ ```python
78
+ import pyconvexity as px
79
+
80
+ # Create a new model database
81
+ px.create_database_with_schema("my_model.db")
82
+
83
+ # Create a network
84
+ with px.database_context("my_model.db") as conn:
85
+ network_req = px.CreateNetworkRequest(
86
+ name="My Network",
87
+ start_time="2024-01-01 00:00:00",
88
+ end_time="2024-01-01 23:00:00",
89
+ time_resolution="PT1H",
90
+ )
91
+ px.create_network(conn, network_req)
92
+
93
+ # Create components
94
+ carrier_id = px.create_carrier(conn, name="AC")
95
+ bus_id = px.create_component(conn, "BUS", "Main Bus", carrier_id=carrier_id)
96
+
97
+ conn.commit()
98
+
99
+ # Solve the network
100
+ result = px.solve_network("my_model.db", solver_name="highs")
101
+ print(f"Success: {result['success']}, Objective: {result['objective_value']}")
102
+ ```
103
+
104
+ ## Documentation
105
+
106
+ Full documentation is available at: **[docs.bayesian.energy](https://docs.bayesian.energy/convexity/user-guide/advanced-features/pyconvexity/)**
107
+
108
+ - [API Reference](https://docs.bayesian.energy/convexity/user-guide/advanced-features/pyconvexity/api/api-reference)
109
+ - [Examples & Tutorials](https://docs.bayesian.energy/convexity/user-guide/advanced-features/pyconvexity/examples/example-1/)
110
+
111
+ ## Development
112
+
113
+ ### Setup
114
+
115
+ ```bash
116
+ git clone https://github.com/bayesian-energy/pyconvexity.git
117
+ cd pyconvexity
118
+ pip install -e ".[dev,all]"
119
+ ```
120
+
121
+ ### Running Tests
122
+
123
+ ```bash
124
+ pytest
125
+ ```
126
+
127
+ ### Documentation Deployment
128
+
129
+ API documentation is auto-generated and synced to [bayesian-docs](https://github.com/bayesian-energy/bayesian-docs).
130
+
131
+ **Generate API docs locally:**
132
+
133
+ ```bash
134
+ python scripts/generate_api_docs.py --output-dir api-docs
135
+ ```
136
+
137
+ **How it works:**
138
+ 1. On push to `main` or release, the `sync-docs.yml` workflow runs
139
+ 2. It generates API markdown from Python docstrings
140
+ 3. Commits the generated docs to the `bayesian-docs` repository
141
+
142
+ **Setup for maintainers:**
143
+ - Add `DOCS_DEPLOY_TOKEN` secret to the repo (personal access token with `repo` scope for bayesian-docs)
144
+
145
+ ## License
146
+
147
+ MIT License - see [LICENSE](LICENSE) for details.
148
+
@@ -1,5 +1,5 @@
1
1
  pyconvexity/__init__.py,sha256=TJvgkEaMHFFWhIGaOdjMgc4rHVIxHKni3Ggg1YAQziE,5347
2
- pyconvexity/_version.py,sha256=BObAQyMJTgNEQbPpM5x4R8aeAPCZ_eHVSXPwL90NUlk,28
2
+ pyconvexity/_version.py,sha256=bbBpXE_PBbo_SaI807mDML0QJywD0_ufCDPgAMlDHaE,22
3
3
  pyconvexity/timeseries.py,sha256=QdKbiqjAlxkJATyKm2Kelx1Ea2PsAnnCYfVLU5VER1Y,11085
4
4
  pyconvexity/core/__init__.py,sha256=gdyyHNqOc4h9Nfe9u6NA936GNzH6coGNCMgBvvvOnGE,1196
5
5
  pyconvexity/core/database.py,sha256=vwCmuN0B0xwImh6L0bFR4vNWHw_wVfYSG1KwsUjK4iY,14831
@@ -11,7 +11,7 @@ pyconvexity/data/loaders/__init__.py,sha256=6xPtOmH2n1mNby7ZjA-2Mk9F48Q246RNsyMn
11
11
  pyconvexity/data/loaders/cache.py,sha256=R-DUIiFpphjyi5EitcUZwzwUdZeqN6poYVyuNpKzB4g,7040
12
12
  pyconvexity/data/schema/01_core_schema.sql,sha256=xCCxUl65IGYxaMxAbgZVS7jVceRUJ_wOMw5c7cm6_Ww,16903
13
13
  pyconvexity/data/schema/02_data_metadata.sql,sha256=BbpTkH1s7IbZQkDBRF2kL_UR9tzMEWDBYS3VBkwDRu0,4323
14
- pyconvexity/data/schema/03_validation_data.sql,sha256=bxadwwN9Om0KYwRo3lXom33TJI5cg4n2n_X0rpK2CoA,88454
14
+ pyconvexity/data/schema/03_validation_data.sql,sha256=Y4iZ1RzqMzeaBWalecT6BRd8I3MSW5nclXJI3cE_JAQ,88663
15
15
  pyconvexity/data/sources/__init__.py,sha256=Dn6_oS7wB-vLjMj2YeXlmIl6hNjACbicimSabKxIWnc,108
16
16
  pyconvexity/data/sources/gem.py,sha256=v8OYCMsb2t-8u-YmK8vzMsgI9ArUAOAXMZZQOFpJ-nI,14923
17
17
  pyconvexity/io/__init__.py,sha256=FCyvRDfBUrrNei-y5JVod6MMN1bkPMSSfE0fpKi1aKQ,751
@@ -23,20 +23,20 @@ pyconvexity/models/__init__.py,sha256=WqDSq1Mst7iJsFytausruoM562FKlOKV0Egmnpm290
23
23
  pyconvexity/models/attributes.py,sha256=RpH3rBoHD33xBSXUEfaD-CvRj3JruolCwexo6HPMGC8,23388
24
24
  pyconvexity/models/carriers.py,sha256=L_WuDMW13k8aaA-obsDPxjmpZgZELiIAZuNtxq7YLpg,3447
25
25
  pyconvexity/models/components.py,sha256=vxznTAvAzF99ILcc1DdLtj4K6k8aqclwlH1VhLcFAMM,17570
26
- pyconvexity/models/network.py,sha256=A5_GVxVeuthAmVKfoSA4ANhVXKwuRizke3E7eR0P8rM,11535
26
+ pyconvexity/models/network.py,sha256=P2Cuxv2lX9gWDwIBDDFqLs1sznhAXYquYNYstaMPjfU,14352
27
27
  pyconvexity/models/results.py,sha256=6j1H4AwVmp94L97gl_sGnE8izMxkU5o89guKIU8JdtE,4169
28
28
  pyconvexity/models/scenarios.py,sha256=-0UPUDXf6r9mFriA-z2fD5KKMARm2PUBjLba49S9mCI,5867
29
29
  pyconvexity/solvers/__init__.py,sha256=t1gOUTqbYDCtIvKPqGVY1fjKwqJi2Od9bGeIO7bPvJE,667
30
30
  pyconvexity/solvers/pypsa/__init__.py,sha256=nudu0AOYEfPhpGHZ1Q9pUgjGeeIJd_zeULc975iyluE,555
31
- pyconvexity/solvers/pypsa/api.py,sha256=9cK5cnJxRI6rkCvqELwMcTFGbmFYJgDsXD6UKCQ5Yfw,17897
31
+ pyconvexity/solvers/pypsa/api.py,sha256=OXGfVsv76HIXMJ4b_5LSiWW1OL5Xiq5EnUzaArTuaTI,18196
32
32
  pyconvexity/solvers/pypsa/batch_loader.py,sha256=w525_lqanKtARKgicajhBiDwIJzkaU_HbkL1I82gDqg,12361
33
- pyconvexity/solvers/pypsa/builder.py,sha256=Nx6fSNzbW4KYbqrgQD-dSlnzvGmypCwcjXwm5KNFq9c,24351
33
+ pyconvexity/solvers/pypsa/builder.py,sha256=SOr77SmDCYKNxfb-wGHYRX5A0M7zdv_E2Sjyf82uS6Y,24208
34
34
  pyconvexity/solvers/pypsa/constraints.py,sha256=20WliFDhPQGMAsS4VOTU8LZJpsFpLVRHpNsZW49GTcc,16397
35
- pyconvexity/solvers/pypsa/solver.py,sha256=RjHece-2j3dvzR0zVJYLoSBJnSxbAYPx2f0MN_h6acM,74176
36
- pyconvexity/solvers/pypsa/storage.py,sha256=7IoT2DpWBKe6jTdKe11a_48d8AuI1TzNMe7LSI3PG-g,107947
35
+ pyconvexity/solvers/pypsa/solver.py,sha256=oXX2kqU9AWg8GgsMOV0fYqv93XjIk8Kh-fKWbQSzaig,63239
36
+ pyconvexity/solvers/pypsa/storage.py,sha256=9edLe46f62jvpai1mNQEKypV77L26VyFdvKgQLCEA3o,94635
37
37
  pyconvexity/validation/__init__.py,sha256=VJNZlFoWABsWwUKktNk2jbtXIepH5omvC0WtsTS7o3o,583
38
38
  pyconvexity/validation/rules.py,sha256=GiNadc8hvbWBr09vUkGiLLTmSdvtNSeGLFwvCjlikYY,9241
39
- pyconvexity-0.4.2.post1.dist-info/METADATA,sha256=P68R4qaVX3K5d9fumRaXUby3q22i7CKXg9hUdnO9nss,2019
40
- pyconvexity-0.4.2.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
- pyconvexity-0.4.2.post1.dist-info/top_level.txt,sha256=wFPEDXVaebR3JO5Tt3HNse-ws5aROCcxEco15d6j64s,12
42
- pyconvexity-0.4.2.post1.dist-info/RECORD,,
39
+ pyconvexity-0.4.6.dist-info/METADATA,sha256=YEKMDpHrBrlWJZ8VA1DJgHrLN2oLXZNl3aojYwebRfg,4967
40
+ pyconvexity-0.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
+ pyconvexity-0.4.6.dist-info/top_level.txt,sha256=wFPEDXVaebR3JO5Tt3HNse-ws5aROCcxEco15d6j64s,12
42
+ pyconvexity-0.4.6.dist-info/RECORD,,