pyconvexity 0.4.0__py3-none-any.whl → 0.4.1__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.
- pyconvexity/__init__.py +87 -46
- pyconvexity/_version.py +1 -1
- pyconvexity/core/__init__.py +3 -5
- pyconvexity/core/database.py +111 -103
- pyconvexity/core/errors.py +16 -10
- pyconvexity/core/types.py +61 -54
- pyconvexity/data/__init__.py +0 -1
- pyconvexity/data/loaders/cache.py +65 -64
- pyconvexity/data/schema/01_core_schema.sql +134 -234
- pyconvexity/data/schema/02_data_metadata.sql +38 -168
- pyconvexity/data/schema/03_validation_data.sql +327 -264
- pyconvexity/data/sources/gem.py +169 -139
- pyconvexity/io/__init__.py +4 -10
- pyconvexity/io/excel_exporter.py +694 -480
- pyconvexity/io/excel_importer.py +817 -545
- pyconvexity/io/netcdf_exporter.py +66 -61
- pyconvexity/io/netcdf_importer.py +850 -619
- pyconvexity/models/__init__.py +109 -59
- pyconvexity/models/attributes.py +197 -178
- pyconvexity/models/carriers.py +70 -67
- pyconvexity/models/components.py +260 -236
- pyconvexity/models/network.py +202 -284
- pyconvexity/models/results.py +65 -55
- pyconvexity/models/scenarios.py +58 -88
- pyconvexity/solvers/__init__.py +5 -5
- pyconvexity/solvers/pypsa/__init__.py +3 -3
- pyconvexity/solvers/pypsa/api.py +150 -134
- pyconvexity/solvers/pypsa/batch_loader.py +165 -162
- pyconvexity/solvers/pypsa/builder.py +390 -291
- pyconvexity/solvers/pypsa/constraints.py +184 -162
- pyconvexity/solvers/pypsa/solver.py +968 -663
- pyconvexity/solvers/pypsa/storage.py +1377 -671
- pyconvexity/timeseries.py +63 -60
- pyconvexity/validation/__init__.py +14 -6
- pyconvexity/validation/rules.py +95 -84
- pyconvexity-0.4.1.dist-info/METADATA +46 -0
- pyconvexity-0.4.1.dist-info/RECORD +42 -0
- pyconvexity/data/schema/04_scenario_schema.sql +0 -122
- pyconvexity/data/schema/migrate_add_geometries.sql +0 -73
- pyconvexity-0.4.0.dist-info/METADATA +0 -138
- pyconvexity-0.4.0.dist-info/RECORD +0 -44
- {pyconvexity-0.4.0.dist-info → pyconvexity-0.4.1.dist-info}/WHEEL +0 -0
- {pyconvexity-0.4.0.dist-info → pyconvexity-0.4.1.dist-info}/top_level.txt +0 -0
pyconvexity/models/results.py
CHANGED
|
@@ -18,6 +18,7 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class SolveResults:
|
|
20
20
|
"""Represents solve results for a scenario."""
|
|
21
|
+
|
|
21
22
|
network_statistics: Dict[str, Any]
|
|
22
23
|
metadata: Dict[str, Any]
|
|
23
24
|
status: str
|
|
@@ -28,58 +29,65 @@ class SolveResults:
|
|
|
28
29
|
@dataclass
|
|
29
30
|
class YearlyResults:
|
|
30
31
|
"""Represents yearly solve results."""
|
|
32
|
+
|
|
31
33
|
year: int
|
|
32
34
|
network_statistics: Dict[str, Any]
|
|
33
35
|
metadata: Dict[str, Any]
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
def get_solve_results(
|
|
37
|
-
conn: sqlite3.Connection,
|
|
38
|
-
network_id: int,
|
|
39
|
-
scenario_id: Optional[int] = None
|
|
39
|
+
conn: sqlite3.Connection, scenario_id: Optional[int] = None
|
|
40
40
|
) -> Optional[SolveResults]:
|
|
41
41
|
"""
|
|
42
|
-
Get overall solve results for a scenario.
|
|
43
|
-
|
|
42
|
+
Get overall solve results for a scenario (single network per database).
|
|
43
|
+
|
|
44
44
|
Args:
|
|
45
45
|
conn: Database connection
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
scenario_id: Scenario ID (NULL for base network)
|
|
47
|
+
|
|
49
48
|
Returns:
|
|
50
49
|
SolveResults object or None if no results found
|
|
51
50
|
"""
|
|
52
|
-
#
|
|
51
|
+
# Query based on scenario_id (NULL for base network)
|
|
53
52
|
if scenario_id is None:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
cursor = conn.execute(
|
|
54
|
+
"""
|
|
55
|
+
SELECT results_json, metadata_json, solve_status, objective_value, solve_time_seconds
|
|
56
|
+
FROM network_solve_results
|
|
57
|
+
WHERE scenario_id IS NULL
|
|
58
|
+
ORDER BY solved_at DESC
|
|
59
|
+
LIMIT 1
|
|
60
|
+
"""
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
cursor = conn.execute(
|
|
64
|
+
"""
|
|
65
|
+
SELECT results_json, metadata_json, solve_status, objective_value, solve_time_seconds
|
|
66
|
+
FROM network_solve_results
|
|
67
|
+
WHERE scenario_id = ?
|
|
68
|
+
ORDER BY solved_at DESC
|
|
69
|
+
LIMIT 1
|
|
70
|
+
""",
|
|
71
|
+
(scenario_id,),
|
|
72
|
+
)
|
|
73
|
+
|
|
66
74
|
row = cursor.fetchone()
|
|
67
75
|
if not row:
|
|
68
76
|
return None
|
|
69
|
-
|
|
77
|
+
|
|
70
78
|
try:
|
|
71
79
|
results_json = json.loads(row[0]) if row[0] else {}
|
|
72
80
|
metadata_json = json.loads(row[1]) if row[1] else {}
|
|
73
|
-
|
|
81
|
+
|
|
74
82
|
# Extract network_statistics from results_json
|
|
75
|
-
network_statistics = results_json.get(
|
|
76
|
-
|
|
83
|
+
network_statistics = results_json.get("network_statistics", {})
|
|
84
|
+
|
|
77
85
|
return SolveResults(
|
|
78
86
|
network_statistics=network_statistics,
|
|
79
87
|
metadata=metadata_json,
|
|
80
|
-
status=row[2] or
|
|
88
|
+
status=row[2] or "unknown",
|
|
81
89
|
objective_value=row[3],
|
|
82
|
-
solve_time=row[4] or 0.0
|
|
90
|
+
solve_time=row[4] or 0.0,
|
|
83
91
|
)
|
|
84
92
|
except json.JSONDecodeError as e:
|
|
85
93
|
logger.error(f"Error parsing JSON for scenario {scenario_id}: {e}")
|
|
@@ -87,52 +95,54 @@ def get_solve_results(
|
|
|
87
95
|
|
|
88
96
|
|
|
89
97
|
def get_yearly_results(
|
|
90
|
-
conn: sqlite3.Connection,
|
|
91
|
-
network_id: int,
|
|
92
|
-
scenario_id: Optional[int] = None
|
|
98
|
+
conn: sqlite3.Connection, scenario_id: Optional[int] = None
|
|
93
99
|
) -> Dict[int, YearlyResults]:
|
|
94
100
|
"""
|
|
95
|
-
Get year-by-year solve results for a scenario.
|
|
96
|
-
|
|
101
|
+
Get year-by-year solve results for a scenario (single network per database).
|
|
102
|
+
|
|
97
103
|
Args:
|
|
98
104
|
conn: Database connection
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
scenario_id: Scenario ID (NULL for base network)
|
|
106
|
+
|
|
102
107
|
Returns:
|
|
103
108
|
Dictionary mapping years to YearlyResults objects
|
|
104
109
|
"""
|
|
105
|
-
#
|
|
110
|
+
# Query based on scenario_id (NULL for base network)
|
|
106
111
|
if scenario_id is None:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
cursor = conn.execute(
|
|
113
|
+
"""
|
|
114
|
+
SELECT year, results_json, metadata_json
|
|
115
|
+
FROM network_solve_results_by_year
|
|
116
|
+
WHERE scenario_id IS NULL
|
|
117
|
+
ORDER BY year
|
|
118
|
+
"""
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
cursor = conn.execute(
|
|
122
|
+
"""
|
|
123
|
+
SELECT year, results_json, metadata_json
|
|
124
|
+
FROM network_solve_results_by_year
|
|
125
|
+
WHERE scenario_id = ?
|
|
126
|
+
ORDER BY year
|
|
127
|
+
""",
|
|
128
|
+
(scenario_id,),
|
|
129
|
+
)
|
|
130
|
+
|
|
118
131
|
yearly_results = {}
|
|
119
132
|
for row in cursor.fetchall():
|
|
120
133
|
year = row[0]
|
|
121
134
|
try:
|
|
122
135
|
results_json = json.loads(row[1]) if row[1] else {}
|
|
123
136
|
metadata_json = json.loads(row[2]) if row[2] else {}
|
|
124
|
-
|
|
137
|
+
|
|
125
138
|
# Extract network_statistics from results_json
|
|
126
|
-
network_statistics = results_json.get(
|
|
127
|
-
|
|
139
|
+
network_statistics = results_json.get("network_statistics", {})
|
|
140
|
+
|
|
128
141
|
yearly_results[year] = YearlyResults(
|
|
129
|
-
year=year,
|
|
130
|
-
network_statistics=network_statistics,
|
|
131
|
-
metadata=metadata_json
|
|
142
|
+
year=year, network_statistics=network_statistics, metadata=metadata_json
|
|
132
143
|
)
|
|
133
144
|
except json.JSONDecodeError as e:
|
|
134
145
|
logger.error(f"Error parsing JSON for year {year}: {e}")
|
|
135
146
|
continue
|
|
136
|
-
|
|
137
|
-
return yearly_results
|
|
138
147
|
|
|
148
|
+
return yearly_results
|
pyconvexity/models/scenarios.py
CHANGED
|
@@ -16,145 +16,115 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
18
18
|
class Scenario:
|
|
19
|
-
"""Represents a scenario
|
|
19
|
+
"""Represents a scenario (single network per database)."""
|
|
20
|
+
|
|
20
21
|
id: int
|
|
21
|
-
network_id: int
|
|
22
22
|
name: str
|
|
23
23
|
description: Optional[str]
|
|
24
|
-
|
|
24
|
+
probability: Optional[float] # For stochastic optimization
|
|
25
25
|
created_at: str
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def list_scenarios(conn: sqlite3.Connection
|
|
28
|
+
def list_scenarios(conn: sqlite3.Connection) -> List[Scenario]:
|
|
29
29
|
"""
|
|
30
|
-
List all scenarios
|
|
31
|
-
|
|
30
|
+
List all scenarios (single network per database).
|
|
31
|
+
|
|
32
32
|
Args:
|
|
33
33
|
conn: Database connection
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
Returns:
|
|
37
|
-
List of Scenario objects ordered by
|
|
36
|
+
List of Scenario objects ordered by creation date
|
|
38
37
|
"""
|
|
39
|
-
cursor = conn.execute(
|
|
40
|
-
|
|
38
|
+
cursor = conn.execute(
|
|
39
|
+
"""
|
|
40
|
+
SELECT id, name, description, probability, created_at
|
|
41
41
|
FROM scenarios
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
ORDER BY created_at
|
|
43
|
+
"""
|
|
44
|
+
)
|
|
45
|
+
|
|
46
46
|
scenarios = []
|
|
47
47
|
for row in cursor.fetchall():
|
|
48
|
-
scenarios.append(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
scenarios.append(
|
|
49
|
+
Scenario(
|
|
50
|
+
id=row[0],
|
|
51
|
+
name=row[1],
|
|
52
|
+
description=row[2],
|
|
53
|
+
probability=row[3],
|
|
54
|
+
created_at=row[4],
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
57
58
|
return scenarios
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def get_scenario_by_name(conn: sqlite3.Connection,
|
|
61
|
+
def get_scenario_by_name(conn: sqlite3.Connection, name: str) -> Scenario:
|
|
61
62
|
"""
|
|
62
|
-
Get a scenario by name.
|
|
63
|
-
|
|
63
|
+
Get a scenario by name (single network per database).
|
|
64
|
+
|
|
64
65
|
Args:
|
|
65
66
|
conn: Database connection
|
|
66
|
-
network_id: Network ID
|
|
67
67
|
name: Scenario name
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
Returns:
|
|
70
70
|
Scenario object
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
Raises:
|
|
73
73
|
ValidationError: If scenario doesn't exist
|
|
74
74
|
"""
|
|
75
|
-
cursor = conn.execute(
|
|
76
|
-
|
|
75
|
+
cursor = conn.execute(
|
|
76
|
+
"""
|
|
77
|
+
SELECT id, name, description, probability, created_at
|
|
77
78
|
FROM scenarios
|
|
78
|
-
WHERE
|
|
79
|
-
""",
|
|
80
|
-
|
|
79
|
+
WHERE name = ?
|
|
80
|
+
""",
|
|
81
|
+
(name,),
|
|
82
|
+
)
|
|
83
|
+
|
|
81
84
|
row = cursor.fetchone()
|
|
82
85
|
if not row:
|
|
83
|
-
raise ValidationError(f"Scenario '{name}' not found
|
|
84
|
-
|
|
86
|
+
raise ValidationError(f"Scenario '{name}' not found")
|
|
87
|
+
|
|
85
88
|
return Scenario(
|
|
86
89
|
id=row[0],
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
created_at=row[5]
|
|
90
|
+
name=row[1],
|
|
91
|
+
description=row[2],
|
|
92
|
+
probability=row[3],
|
|
93
|
+
created_at=row[4],
|
|
92
94
|
)
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
def get_scenario_by_id(conn: sqlite3.Connection, scenario_id: int) -> Scenario:
|
|
96
98
|
"""
|
|
97
99
|
Get a scenario by ID.
|
|
98
|
-
|
|
100
|
+
|
|
99
101
|
Args:
|
|
100
102
|
conn: Database connection
|
|
101
103
|
scenario_id: Scenario ID
|
|
102
|
-
|
|
104
|
+
|
|
103
105
|
Returns:
|
|
104
106
|
Scenario object
|
|
105
|
-
|
|
107
|
+
|
|
106
108
|
Raises:
|
|
107
109
|
ValidationError: If scenario doesn't exist
|
|
108
110
|
"""
|
|
109
|
-
cursor = conn.execute(
|
|
110
|
-
|
|
111
|
+
cursor = conn.execute(
|
|
112
|
+
"""
|
|
113
|
+
SELECT id, name, description, probability, created_at
|
|
111
114
|
FROM scenarios
|
|
112
115
|
WHERE id = ?
|
|
113
|
-
""",
|
|
114
|
-
|
|
115
|
-
row = cursor.fetchone()
|
|
116
|
-
if not row:
|
|
117
|
-
raise ValidationError(f"Scenario with ID {scenario_id} not found")
|
|
118
|
-
|
|
119
|
-
return Scenario(
|
|
120
|
-
id=row[0],
|
|
121
|
-
network_id=row[1],
|
|
122
|
-
name=row[2],
|
|
123
|
-
description=row[3],
|
|
124
|
-
is_master=bool(row[4]),
|
|
125
|
-
created_at=row[5]
|
|
116
|
+
""",
|
|
117
|
+
(scenario_id,),
|
|
126
118
|
)
|
|
127
119
|
|
|
128
|
-
|
|
129
|
-
def get_master_scenario(conn: sqlite3.Connection, network_id: int) -> Scenario:
|
|
130
|
-
"""
|
|
131
|
-
Get the master scenario for a network.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
conn: Database connection
|
|
135
|
-
network_id: Network ID
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
Scenario object for the master scenario
|
|
139
|
-
|
|
140
|
-
Raises:
|
|
141
|
-
ValidationError: If master scenario doesn't exist
|
|
142
|
-
"""
|
|
143
|
-
cursor = conn.execute("""
|
|
144
|
-
SELECT id, network_id, name, description, is_master, created_at
|
|
145
|
-
FROM scenarios
|
|
146
|
-
WHERE network_id = ? AND is_master = TRUE
|
|
147
|
-
""", (network_id,))
|
|
148
|
-
|
|
149
120
|
row = cursor.fetchone()
|
|
150
121
|
if not row:
|
|
151
|
-
raise ValidationError(f"
|
|
152
|
-
|
|
122
|
+
raise ValidationError(f"Scenario with ID {scenario_id} not found")
|
|
123
|
+
|
|
153
124
|
return Scenario(
|
|
154
125
|
id=row[0],
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
created_at=row[5]
|
|
126
|
+
name=row[1],
|
|
127
|
+
description=row[2],
|
|
128
|
+
probability=row[3],
|
|
129
|
+
created_at=row[4],
|
|
160
130
|
)
|
pyconvexity/solvers/__init__.py
CHANGED
|
@@ -12,18 +12,18 @@ try:
|
|
|
12
12
|
solve_pypsa_network,
|
|
13
13
|
load_network_components,
|
|
14
14
|
apply_constraints,
|
|
15
|
-
store_solve_results
|
|
15
|
+
store_solve_results,
|
|
16
16
|
)
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
__all__ = [
|
|
19
19
|
"solve_network",
|
|
20
|
-
"build_pypsa_network",
|
|
20
|
+
"build_pypsa_network",
|
|
21
21
|
"solve_pypsa_network",
|
|
22
22
|
"load_network_components",
|
|
23
23
|
"apply_constraints",
|
|
24
|
-
"store_solve_results"
|
|
24
|
+
"store_solve_results",
|
|
25
25
|
]
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
except ImportError:
|
|
28
28
|
# PyPSA not available
|
|
29
29
|
__all__ = []
|
|
@@ -11,14 +11,14 @@ from pyconvexity.solvers.pypsa.api import (
|
|
|
11
11
|
solve_pypsa_network,
|
|
12
12
|
load_network_components,
|
|
13
13
|
apply_constraints,
|
|
14
|
-
store_solve_results
|
|
14
|
+
store_solve_results,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
18
|
"solve_network",
|
|
19
19
|
"build_pypsa_network",
|
|
20
|
-
"solve_pypsa_network",
|
|
20
|
+
"solve_pypsa_network",
|
|
21
21
|
"load_network_components",
|
|
22
22
|
"apply_constraints",
|
|
23
|
-
"store_solve_results"
|
|
23
|
+
"store_solve_results",
|
|
24
24
|
]
|