pyconvexity 0.4.6.post1__tar.gz → 0.4.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyconvexity might be problematic. Click here for more details.
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/PKG-INFO +1 -1
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/pyproject.toml +2 -2
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/__init__.py +15 -0
- pyconvexity-0.4.8/src/pyconvexity/_version.py +1 -0
- pyconvexity-0.4.8/src/pyconvexity/dashboard.py +265 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/schema/01_core_schema.sql +1 -1
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/schema/03_validation_data.sql +2 -2
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/__init__.py +6 -0
- pyconvexity-0.4.8/src/pyconvexity/solvers/pypsa/clearing_price.py +678 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/storage.py +94 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity.egg-info/PKG-INFO +1 -1
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity.egg-info/SOURCES.txt +2 -0
- pyconvexity-0.4.6.post1/src/pyconvexity/_version.py +0 -1
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/README.md +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/setup.cfg +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/core/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/core/database.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/core/errors.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/core/types.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/README.md +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/loaders/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/loaders/cache.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/schema/02_data_metadata.sql +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/sources/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/sources/gem.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/io/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/io/excel_exporter.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/io/excel_importer.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/io/netcdf_exporter.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/io/netcdf_importer.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/attributes.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/carriers.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/components.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/network.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/results.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/models/scenarios.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/api.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/batch_loader.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/builder.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/constraints.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/solvers/pypsa/solver.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/timeseries.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/validation/__init__.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/validation/rules.py +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity.egg-info/dependency_links.txt +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity.egg-info/requires.txt +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity.egg-info/top_level.txt +0 -0
- {pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/tests/test_core_types.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyconvexity"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.8"
|
|
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.4.
|
|
84
|
+
python_version = "0.4.8"
|
|
85
85
|
warn_return_any = true
|
|
86
86
|
warn_unused_configs = true
|
|
87
87
|
disallow_untyped_defs = true
|
|
@@ -96,6 +96,15 @@ from pyconvexity.timeseries import (
|
|
|
96
96
|
numpy_to_timeseries,
|
|
97
97
|
)
|
|
98
98
|
|
|
99
|
+
# Dashboard configuration for Convexity app
|
|
100
|
+
from pyconvexity.dashboard import (
|
|
101
|
+
DashboardConfig,
|
|
102
|
+
set_dashboard_config,
|
|
103
|
+
get_dashboard_config,
|
|
104
|
+
clear_dashboard_config,
|
|
105
|
+
auto_layout,
|
|
106
|
+
)
|
|
107
|
+
|
|
99
108
|
# High-level API functions
|
|
100
109
|
__all__ = [
|
|
101
110
|
# Version info
|
|
@@ -171,6 +180,12 @@ __all__ = [
|
|
|
171
180
|
"get_multiple_timeseries",
|
|
172
181
|
"timeseries_to_numpy",
|
|
173
182
|
"numpy_to_timeseries",
|
|
183
|
+
# Dashboard configuration
|
|
184
|
+
"DashboardConfig",
|
|
185
|
+
"set_dashboard_config",
|
|
186
|
+
"get_dashboard_config",
|
|
187
|
+
"clear_dashboard_config",
|
|
188
|
+
"auto_layout",
|
|
174
189
|
]
|
|
175
190
|
|
|
176
191
|
# Data module imports
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.8"
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dashboard configuration for Convexity app visualization.
|
|
3
|
+
|
|
4
|
+
Allows programmatic configuration of the analytics dashboard layout
|
|
5
|
+
that will be displayed when the model is loaded in the Convexity app.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from pyconvexity.dashboard import set_dashboard_config, DashboardConfig, auto_layout
|
|
9
|
+
>>>
|
|
10
|
+
>>> charts = [
|
|
11
|
+
... {
|
|
12
|
+
... "id": "dispatch-1",
|
|
13
|
+
... "title": "Generation by Carrier",
|
|
14
|
+
... "visible": True,
|
|
15
|
+
... "view": {
|
|
16
|
+
... "timeseries": {
|
|
17
|
+
... "component": "Generator",
|
|
18
|
+
... "attribute": "p",
|
|
19
|
+
... "group_by": "carrier"
|
|
20
|
+
... }
|
|
21
|
+
... }
|
|
22
|
+
... },
|
|
23
|
+
... {
|
|
24
|
+
... "id": "lmp-1",
|
|
25
|
+
... "title": "Locational Marginal Prices",
|
|
26
|
+
... "visible": True,
|
|
27
|
+
... "view": {
|
|
28
|
+
... "timeseries": {
|
|
29
|
+
... "component": "Bus",
|
|
30
|
+
... "attribute": "marginal_price"
|
|
31
|
+
... }
|
|
32
|
+
... }
|
|
33
|
+
... }
|
|
34
|
+
... ]
|
|
35
|
+
>>>
|
|
36
|
+
>>> config = DashboardConfig(charts=charts, layout=auto_layout(charts))
|
|
37
|
+
>>> set_dashboard_config(conn, config)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import sqlite3
|
|
42
|
+
from dataclasses import dataclass, field, asdict
|
|
43
|
+
from typing import Any, Dict, List, Optional
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class DashboardConfig:
|
|
48
|
+
"""
|
|
49
|
+
Dashboard configuration for the Convexity app analytics view.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
charts: List of chart configurations. Each chart is a dict with:
|
|
53
|
+
- id: Unique identifier (e.g., "dispatch-1", "lmp-main")
|
|
54
|
+
- title: Display title
|
|
55
|
+
- visible: Whether chart is shown
|
|
56
|
+
- view: Chart type configuration (see below)
|
|
57
|
+
- filters: Optional chart-specific filters
|
|
58
|
+
|
|
59
|
+
layout: List of layout positions. Each position is a dict with:
|
|
60
|
+
- i: Chart ID (must match a chart's id)
|
|
61
|
+
- x: Grid column (0-11)
|
|
62
|
+
- y: Grid row
|
|
63
|
+
- w: Width in grid units (max 12)
|
|
64
|
+
- h: Height in grid units
|
|
65
|
+
|
|
66
|
+
selected_scenario_id: Pre-selected scenario ID (optional)
|
|
67
|
+
selected_ensemble_name: Pre-selected ensemble name (optional)
|
|
68
|
+
selected_bus_id: Pre-selected bus ID for filtering (optional)
|
|
69
|
+
|
|
70
|
+
Chart View Types:
|
|
71
|
+
Timeseries (dispatch, LMP, etc.):
|
|
72
|
+
{"timeseries": {"component": "Generator", "attribute": "p", "group_by": "carrier"}}
|
|
73
|
+
{"timeseries": {"component": "Bus", "attribute": "marginal_price"}}
|
|
74
|
+
{"timeseries": {"component": "Load", "attribute": "p"}}
|
|
75
|
+
|
|
76
|
+
Network map:
|
|
77
|
+
{"network": {"network": true}}
|
|
78
|
+
|
|
79
|
+
Statistics/Summary:
|
|
80
|
+
{"statistic": {"statistic": "optimal_capacity", "metric": "capacity"}}
|
|
81
|
+
{"statistic": {"statistic": "total_cost"}}
|
|
82
|
+
"""
|
|
83
|
+
charts: List[Dict[str, Any]]
|
|
84
|
+
layout: List[Dict[str, Any]]
|
|
85
|
+
selected_scenario_id: Optional[int] = None
|
|
86
|
+
selected_ensemble_name: Optional[str] = None
|
|
87
|
+
selected_bus_id: Optional[int] = None
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
90
|
+
"""Convert to dictionary for JSON serialization."""
|
|
91
|
+
result = {
|
|
92
|
+
"charts": self.charts,
|
|
93
|
+
"layout": self.layout,
|
|
94
|
+
}
|
|
95
|
+
# Only include optional fields if set (matches Rust's skip_serializing_if)
|
|
96
|
+
if self.selected_scenario_id is not None:
|
|
97
|
+
result["selected_scenario_id"] = self.selected_scenario_id
|
|
98
|
+
if self.selected_ensemble_name is not None:
|
|
99
|
+
result["selected_ensemble_name"] = self.selected_ensemble_name
|
|
100
|
+
if self.selected_bus_id is not None:
|
|
101
|
+
result["selected_bus_id"] = self.selected_bus_id
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def set_dashboard_config(conn: sqlite3.Connection, config: DashboardConfig) -> int:
|
|
106
|
+
"""
|
|
107
|
+
Save dashboard configuration to the database.
|
|
108
|
+
|
|
109
|
+
This configuration will be loaded by the Convexity app when the model
|
|
110
|
+
is opened, setting up the analytics dashboard with the specified charts
|
|
111
|
+
and layout.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
conn: Database connection
|
|
115
|
+
config: Dashboard configuration
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Row ID of the stored configuration
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> config = DashboardConfig(
|
|
122
|
+
... charts=[{"id": "dispatch-1", "title": "Dispatch", "visible": True,
|
|
123
|
+
... "view": {"timeseries": {"component": "Generator", "attribute": "p", "group_by": "carrier"}}}],
|
|
124
|
+
... layout=[{"i": "dispatch-1", "x": 0, "y": 0, "w": 12, "h": 40}]
|
|
125
|
+
... )
|
|
126
|
+
>>> set_dashboard_config(conn, config)
|
|
127
|
+
"""
|
|
128
|
+
data_json = json.dumps(config.to_dict())
|
|
129
|
+
data_bytes = data_json.encode('utf-8')
|
|
130
|
+
|
|
131
|
+
# Check if analytics config exists
|
|
132
|
+
cursor = conn.execute(
|
|
133
|
+
"SELECT id FROM network_data_store WHERE category = 'analytics_view' AND name = 'default'"
|
|
134
|
+
)
|
|
135
|
+
row = cursor.fetchone()
|
|
136
|
+
|
|
137
|
+
if row:
|
|
138
|
+
# Update existing
|
|
139
|
+
row_id = row[0]
|
|
140
|
+
conn.execute(
|
|
141
|
+
"UPDATE network_data_store SET data = ?, updated_at = datetime('now') WHERE id = ?",
|
|
142
|
+
(data_bytes, row_id)
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
# Insert new
|
|
146
|
+
conn.execute(
|
|
147
|
+
"""INSERT INTO network_data_store (category, name, data_format, data, created_at, updated_at)
|
|
148
|
+
VALUES ('analytics_view', 'default', 'json', ?, datetime('now'), datetime('now'))""",
|
|
149
|
+
(data_bytes,)
|
|
150
|
+
)
|
|
151
|
+
row_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
|
|
152
|
+
|
|
153
|
+
conn.commit()
|
|
154
|
+
return row_id
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_dashboard_config(conn: sqlite3.Connection) -> Optional[DashboardConfig]:
|
|
158
|
+
"""
|
|
159
|
+
Get the current dashboard configuration from the database.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
conn: Database connection
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
DashboardConfig if one exists, None otherwise
|
|
166
|
+
"""
|
|
167
|
+
cursor = conn.execute(
|
|
168
|
+
"""SELECT data FROM network_data_store
|
|
169
|
+
WHERE category = 'analytics_view'
|
|
170
|
+
ORDER BY updated_at DESC LIMIT 1"""
|
|
171
|
+
)
|
|
172
|
+
row = cursor.fetchone()
|
|
173
|
+
|
|
174
|
+
if not row:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
data_bytes = row[0]
|
|
178
|
+
if isinstance(data_bytes, bytes):
|
|
179
|
+
data_str = data_bytes.decode('utf-8')
|
|
180
|
+
else:
|
|
181
|
+
data_str = data_bytes
|
|
182
|
+
|
|
183
|
+
data = json.loads(data_str)
|
|
184
|
+
|
|
185
|
+
return DashboardConfig(
|
|
186
|
+
charts=data.get("charts", []),
|
|
187
|
+
layout=data.get("layout", []),
|
|
188
|
+
selected_scenario_id=data.get("selected_scenario_id"),
|
|
189
|
+
selected_ensemble_name=data.get("selected_ensemble_name"),
|
|
190
|
+
selected_bus_id=data.get("selected_bus_id"),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def auto_layout(charts: List[Dict[str, Any]], cols: int = 12) -> List[Dict[str, Any]]:
|
|
195
|
+
"""
|
|
196
|
+
Automatically generate layout positions for charts.
|
|
197
|
+
|
|
198
|
+
Places charts in a vertical stack, each taking full width.
|
|
199
|
+
Timeseries charts get height 40, others get height 20.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
charts: List of chart configurations
|
|
203
|
+
cols: Grid columns (default 12)
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of layout position dicts
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
>>> charts = [
|
|
210
|
+
... {"id": "dispatch-1", "title": "Dispatch", "visible": True,
|
|
211
|
+
... "view": {"timeseries": {...}}},
|
|
212
|
+
... {"id": "network-1", "title": "Network", "visible": True,
|
|
213
|
+
... "view": {"network": {"network": True}}}
|
|
214
|
+
... ]
|
|
215
|
+
>>> layout = auto_layout(charts)
|
|
216
|
+
>>> # Returns: [{"i": "dispatch-1", "x": 0, "y": 0, "w": 12, "h": 40}, ...]
|
|
217
|
+
"""
|
|
218
|
+
layout = []
|
|
219
|
+
y = 0
|
|
220
|
+
|
|
221
|
+
for chart in charts:
|
|
222
|
+
if not chart.get("visible", True):
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
chart_id = chart["id"]
|
|
226
|
+
view = chart.get("view", {})
|
|
227
|
+
|
|
228
|
+
# Determine chart dimensions based on type
|
|
229
|
+
if "timeseries" in view:
|
|
230
|
+
w, h = 12, 40
|
|
231
|
+
elif "network" in view:
|
|
232
|
+
w, h = 6, 40
|
|
233
|
+
elif "statistic" in view:
|
|
234
|
+
w, h = 6, 20
|
|
235
|
+
else:
|
|
236
|
+
w, h = 12, 20
|
|
237
|
+
|
|
238
|
+
layout.append({
|
|
239
|
+
"i": chart_id,
|
|
240
|
+
"x": 0,
|
|
241
|
+
"y": y,
|
|
242
|
+
"w": w,
|
|
243
|
+
"h": h,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
y += h
|
|
247
|
+
|
|
248
|
+
return layout
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def clear_dashboard_config(conn: sqlite3.Connection) -> bool:
|
|
252
|
+
"""
|
|
253
|
+
Remove any existing dashboard configuration.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
conn: Database connection
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
True if a config was deleted, False if none existed
|
|
260
|
+
"""
|
|
261
|
+
cursor = conn.execute(
|
|
262
|
+
"DELETE FROM network_data_store WHERE category = 'analytics_view' AND name = 'default'"
|
|
263
|
+
)
|
|
264
|
+
conn.commit()
|
|
265
|
+
return cursor.rowcount > 0
|
{pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/schema/01_core_schema.sql
RENAMED
|
@@ -54,7 +54,7 @@ CREATE TABLE carriers (
|
|
|
54
54
|
nice_name TEXT, -- Display name
|
|
55
55
|
max_growth REAL DEFAULT NULL, -- MW - can be infinite
|
|
56
56
|
max_relative_growth REAL DEFAULT 0.0, -- MW
|
|
57
|
-
curtailable BOOLEAN DEFAULT
|
|
57
|
+
curtailable BOOLEAN DEFAULT TRUE, -- Whether the carrier can be curtailed
|
|
58
58
|
|
|
59
59
|
-- Metadata
|
|
60
60
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
{pyconvexity-0.4.6.post1 → pyconvexity-0.4.8}/src/pyconvexity/data/schema/03_validation_data.sql
RENAMED
|
@@ -33,8 +33,8 @@ INSERT INTO attribute_validation_rules (component_type, attribute_name, display_
|
|
|
33
33
|
('BUS', 'q', 'Reactive Power', 'float', 'MVar', '0', 'timeseries', FALSE, FALSE, 'reactive power (positive if net generation at bus)', NULL, NULL, 'electrical', TRUE),
|
|
34
34
|
('BUS', 'v_mag_pu', 'Voltage Magnitude', 'float', 'per unit', '1', 'timeseries', FALSE, FALSE, 'Voltage magnitude, per unit of v_nom', NULL, NULL, 'electrical', TRUE),
|
|
35
35
|
('BUS', 'v_ang', 'Voltage Angle', 'float', 'radians', '0', 'timeseries', FALSE, FALSE, 'Voltage angle', NULL, NULL, 'electrical', TRUE),
|
|
36
|
-
('BUS', 'marginal_price', 'Marginal Price', 'float', 'currency/MWh', '0', 'timeseries', FALSE, FALSE, 'Locational marginal price from LOPF from power balance constraint', NULL, NULL, 'costs', TRUE),
|
|
37
|
-
('BUS', '
|
|
36
|
+
('BUS', 'marginal_price', 'Marginal Price', 'float', 'currency/MWh', '0', 'timeseries', FALSE, FALSE, 'Locational marginal price from LOPF from power balance constraint (shadow price). Includes effects of UC constraints, ramping limits, and other binding constraints.', NULL, NULL, 'costs', TRUE),
|
|
37
|
+
('BUS', 'clearing_price', 'Clearing Price', 'float', 'currency/MWh', '0', 'timeseries', FALSE, FALSE, 'Pay-as-clear price: marginal cost of the cheapest available source (generator, storage, or import via uncongested link) with spare capacity. Calculated as min of: (1) local generators/storage with spare capacity, (2) adjacent bus local marginal + link cost adjusted for efficiency, for uncongested links, (3) unmet load price in scarcity. Differs from marginal_price which is the LP shadow price.', NULL, NULL, 'costs', TRUE);
|
|
38
38
|
|
|
39
39
|
-- ============================================================================
|
|
40
40
|
-- GENERATOR ATTRIBUTES
|
|
@@ -3,6 +3,10 @@ PyPSA solver integration for PyConvexity.
|
|
|
3
3
|
|
|
4
4
|
Provides high-level and low-level APIs for building PyPSA networks from database,
|
|
5
5
|
solving them, and storing results back to the database.
|
|
6
|
+
|
|
7
|
+
After solving, the following prices are stored on each bus:
|
|
8
|
+
- marginal_price: LP shadow price of power balance constraint (from PyPSA)
|
|
9
|
+
- clearing_price: Pay-as-clear price based on cheapest source with spare capacity
|
|
6
10
|
"""
|
|
7
11
|
|
|
8
12
|
from pyconvexity.solvers.pypsa.api import (
|
|
@@ -13,6 +17,7 @@ from pyconvexity.solvers.pypsa.api import (
|
|
|
13
17
|
apply_constraints,
|
|
14
18
|
store_solve_results,
|
|
15
19
|
)
|
|
20
|
+
from pyconvexity.solvers.pypsa.clearing_price import ClearingPriceCalculator
|
|
16
21
|
|
|
17
22
|
__all__ = [
|
|
18
23
|
"solve_network",
|
|
@@ -21,4 +26,5 @@ __all__ = [
|
|
|
21
26
|
"load_network_components",
|
|
22
27
|
"apply_constraints",
|
|
23
28
|
"store_solve_results",
|
|
29
|
+
"ClearingPriceCalculator",
|
|
24
30
|
]
|