pypsa2smspp 0.0.1__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.
- pypsa2smspp-0.0.1/MANIFEST.in +2 -0
- pypsa2smspp-0.0.1/PKG-INFO +88 -0
- pypsa2smspp-0.0.1/README.md +48 -0
- pypsa2smspp-0.0.1/pyproject.toml +103 -0
- pypsa2smspp-0.0.1/pypsa2smspp/__init__.py +63 -0
- pypsa2smspp-0.0.1/pypsa2smspp/constants.py +59 -0
- pypsa2smspp-0.0.1/pypsa2smspp/data/config_default.yaml +22 -0
- pypsa2smspp-0.0.1/pypsa2smspp/data/smspp_parameters.xlsx +0 -0
- pypsa2smspp-0.0.1/pypsa2smspp/inverse.py +143 -0
- pypsa2smspp-0.0.1/pypsa2smspp/io_parser.py +233 -0
- pypsa2smspp-0.0.1/pypsa2smspp/network_correction.py +532 -0
- pypsa2smspp-0.0.1/pypsa2smspp/pip_utils.py +228 -0
- pypsa2smspp-0.0.1/pypsa2smspp/transformation.py +1225 -0
- pypsa2smspp-0.0.1/pypsa2smspp/transformation_config.py +217 -0
- pypsa2smspp-0.0.1/pypsa2smspp/utils.py +1494 -0
- pypsa2smspp-0.0.1/pypsa2smspp.egg-info/PKG-INFO +88 -0
- pypsa2smspp-0.0.1/pypsa2smspp.egg-info/SOURCES.txt +24 -0
- pypsa2smspp-0.0.1/pypsa2smspp.egg-info/dependency_links.txt +1 -0
- pypsa2smspp-0.0.1/pypsa2smspp.egg-info/requires.txt +28 -0
- pypsa2smspp-0.0.1/pypsa2smspp.egg-info/top_level.txt +1 -0
- pypsa2smspp-0.0.1/setup.cfg +4 -0
- pypsa2smspp-0.0.1/test/test_cases.py +178 -0
- pypsa2smspp-0.0.1/test/test_investmentblock.py +102 -0
- pypsa2smspp-0.0.1/test/test_main.py +215 -0
- pypsa2smspp-0.0.1/test/test_mainpypsaeur.py +261 -0
- pypsa2smspp-0.0.1/test/test_ucblock.py +103 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypsa2smspp
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Bi-directional interface between PyPSA and SMS++
|
|
5
|
+
Author-email: Alessandro Pampado <alessandro.pampado@ing.unipi.it>, Davide Fioriti <davide.fioriti@unipi.it>, Unipi developers <davide.fioriti@unipi.it>
|
|
6
|
+
Project-URL: Homepage, https://github.com/SPSUnipi/pypsa2smspp
|
|
7
|
+
Project-URL: Issues, https://github.com/SPSUnipi/pypsa2smspp/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: pandas<3,>=0.24
|
|
16
|
+
Requires-Dist: pypsa>=1
|
|
17
|
+
Requires-Dist: xarray
|
|
18
|
+
Requires-Dist: openpyxl
|
|
19
|
+
Requires-Dist: pysmspp>=0.0.2
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest; extra == "dev"
|
|
22
|
+
Requires-Dist: highspy; extra == "dev"
|
|
23
|
+
Requires-Dist: gurobipy; extra == "dev"
|
|
24
|
+
Requires-Dist: coverage; extra == "dev"
|
|
25
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
26
|
+
Requires-Dist: ncompare; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: numpydoc; extra == "docs"
|
|
30
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
31
|
+
Requires-Dist: sphinx-book-theme; extra == "docs"
|
|
32
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
33
|
+
Requires-Dist: pydata-sphinx-theme; extra == "docs"
|
|
34
|
+
Requires-Dist: sphinx-reredirects; extra == "docs"
|
|
35
|
+
Requires-Dist: nbsphinx; extra == "docs"
|
|
36
|
+
Requires-Dist: nbsphinx-link; extra == "docs"
|
|
37
|
+
Requires-Dist: scikit-learn; extra == "docs"
|
|
38
|
+
Requires-Dist: ipython; extra == "docs"
|
|
39
|
+
Requires-Dist: ipykernel; extra == "docs"
|
|
40
|
+
|
|
41
|
+
# pypsa2smspp
|
|
42
|
+
|
|
43
|
+
[](https://github.com/SPSUnipi/pypsa2smspp/actions/workflows/test.yml)
|
|
44
|
+
|
|
45
|
+
This package aims at providing a python interface between [PyPSA](https://github.com/PyPSA/pypsa) and [SMS++](https://gitlab.com/smspp/smspp-project) models using a simple python interface.
|
|
46
|
+
The package aims to support:
|
|
47
|
+
- Convert a PyPSA model to SMS++
|
|
48
|
+
- Execute the optimization of the so-created SMS++ model
|
|
49
|
+
- Parse the solution from the SMS++ model to PyPSA
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## How to develop
|
|
53
|
+
|
|
54
|
+
1. First, clone the repository using git:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/SPSUnipi/pypsa2smspp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. Create a virtual environment using venv or conda.
|
|
61
|
+
For exaample, using venv:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python -m venv .venv
|
|
65
|
+
source .venv/bin/activate
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Alternatively, using conda:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
conda create -n pypsa2smspp python=3.10
|
|
72
|
+
conda activate pypsa2smspp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Install the required packages and pre-commit hooks:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install -e .[dev]
|
|
79
|
+
pre-commit install
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Note that the `-e` command line option installs the package in editable mode, so that changes to the source code are immediately available in the environment being used. The `[dev]` option installs the packages required for development. The `pre-commit install` command installs the pre-commit hooks, which are used to check the code before committing to ensure code quality standards.
|
|
83
|
+
|
|
84
|
+
4. Develop and test the code. For testing, please run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pytest
|
|
88
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# pypsa2smspp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/SPSUnipi/pypsa2smspp/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
This package aims at providing a python interface between [PyPSA](https://github.com/PyPSA/pypsa) and [SMS++](https://gitlab.com/smspp/smspp-project) models using a simple python interface.
|
|
6
|
+
The package aims to support:
|
|
7
|
+
- Convert a PyPSA model to SMS++
|
|
8
|
+
- Execute the optimization of the so-created SMS++ model
|
|
9
|
+
- Parse the solution from the SMS++ model to PyPSA
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## How to develop
|
|
13
|
+
|
|
14
|
+
1. First, clone the repository using git:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/SPSUnipi/pypsa2smspp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. Create a virtual environment using venv or conda.
|
|
21
|
+
For exaample, using venv:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python -m venv .venv
|
|
25
|
+
source .venv/bin/activate
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Alternatively, using conda:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
conda create -n pypsa2smspp python=3.10
|
|
32
|
+
conda activate pypsa2smspp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Install the required packages and pre-commit hooks:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e .[dev]
|
|
39
|
+
pre-commit install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Note that the `-e` command line option installs the package in editable mode, so that changes to the source code are immediately available in the environment being used. The `[dev]` option installs the packages required for development. The `pre-commit install` command installs the pre-commit hooks, which are used to check the code before committing to ensure code quality standards.
|
|
43
|
+
|
|
44
|
+
4. Develop and test the code. For testing, please run:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pytest
|
|
48
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pypsa2smspp"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Alessandro Pampado", email="alessandro.pampado@ing.unipi.it"},
|
|
10
|
+
{ name="Davide Fioriti", email="davide.fioriti@unipi.it" },
|
|
11
|
+
{ name="Unipi developers", email="davide.fioriti@unipi.it" },
|
|
12
|
+
]
|
|
13
|
+
description = "Bi-directional interface between PyPSA and SMS++"
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
requires-python = ">=3.12"
|
|
23
|
+
|
|
24
|
+
dependencies = [
|
|
25
|
+
"numpy",
|
|
26
|
+
"pandas>=0.24, <3",
|
|
27
|
+
"pypsa>=1",
|
|
28
|
+
"xarray",
|
|
29
|
+
"openpyxl",
|
|
30
|
+
"pysmspp>=0.0.2",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/SPSUnipi/pypsa2smspp"
|
|
35
|
+
Issues = "https://github.com/SPSUnipi/pypsa2smspp/issues"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest",
|
|
40
|
+
"highspy",
|
|
41
|
+
"gurobipy",
|
|
42
|
+
"coverage",
|
|
43
|
+
"pre-commit",
|
|
44
|
+
"ncompare",
|
|
45
|
+
"mypy",
|
|
46
|
+
]
|
|
47
|
+
docs=[
|
|
48
|
+
"numpydoc",
|
|
49
|
+
"sphinx",
|
|
50
|
+
"sphinx-book-theme",
|
|
51
|
+
"sphinx-rtd-theme",
|
|
52
|
+
"pydata-sphinx-theme",
|
|
53
|
+
"sphinx-reredirects",
|
|
54
|
+
"nbsphinx",
|
|
55
|
+
"nbsphinx-link",
|
|
56
|
+
"scikit-learn",
|
|
57
|
+
"ipython",
|
|
58
|
+
"ipykernel",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools]
|
|
62
|
+
packages = ["pypsa2smspp"]
|
|
63
|
+
|
|
64
|
+
[tool.setuptools_scm]
|
|
65
|
+
version_scheme = "no-guess-dev"
|
|
66
|
+
|
|
67
|
+
[tool.setuptools.package-data]
|
|
68
|
+
"pysmspp" = ["py.typed"]
|
|
69
|
+
|
|
70
|
+
# Pytest settings
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
filterwarnings = [
|
|
74
|
+
"error::DeprecationWarning", # Raise all DeprecationWarnings as errors
|
|
75
|
+
"error::FutureWarning", # Raise all FutureWarnings as errors
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# Coverage settings
|
|
79
|
+
|
|
80
|
+
[tool.coverage.run]
|
|
81
|
+
branch = true
|
|
82
|
+
source = ["pypsa2smspp"]
|
|
83
|
+
omit = ["test/*"]
|
|
84
|
+
[tool.coverage.report]
|
|
85
|
+
exclude_also = [
|
|
86
|
+
"if TYPE_CHECKING:",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
# Static type checker settings
|
|
90
|
+
[tool.mypy]
|
|
91
|
+
exclude = ['dev/*', 'examples/*', 'doc/*']
|
|
92
|
+
ignore_missing_imports = true
|
|
93
|
+
no_implicit_optional = true
|
|
94
|
+
warn_unused_ignores = true
|
|
95
|
+
show_error_code_links = true
|
|
96
|
+
|
|
97
|
+
[[tool.mypy.overrides]]
|
|
98
|
+
module = "pypsa2smspp.*"
|
|
99
|
+
disallow_untyped_defs = true
|
|
100
|
+
check_untyped_defs = true
|
|
101
|
+
|
|
102
|
+
# [tool.setuptools.dynamic]
|
|
103
|
+
# version = {attr = "pysmspp.__version__"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
pypsa2smspp package init
|
|
4
|
+
|
|
5
|
+
Exposes high-level transformation and network correction utilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("pypsa2smspp")
|
|
11
|
+
logger.setLevel(logging.INFO)
|
|
12
|
+
|
|
13
|
+
if not logger.handlers: # 👈 evita di aggiungere più handler
|
|
14
|
+
console_handler = logging.StreamHandler()
|
|
15
|
+
console_handler.setLevel(logging.DEBUG)
|
|
16
|
+
|
|
17
|
+
formatter = logging.Formatter(
|
|
18
|
+
'[%(asctime)s] %(levelname)s - %(name)s - %(message)s'
|
|
19
|
+
)
|
|
20
|
+
console_handler.setFormatter(formatter)
|
|
21
|
+
|
|
22
|
+
logger.addHandler(console_handler)
|
|
23
|
+
|
|
24
|
+
# opzionale ma consigliato in una libreria:
|
|
25
|
+
logger.propagate = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Transformation logic (PyPSA ↔ SMS++)
|
|
30
|
+
from pypsa2smspp.transformation import Transformation
|
|
31
|
+
from pypsa2smspp.transformation_config import TransformationConfig
|
|
32
|
+
|
|
33
|
+
# PyPSA network correction tools
|
|
34
|
+
from pypsa2smspp.network_correction import (
|
|
35
|
+
clean_marginal_cost,
|
|
36
|
+
clean_global_constraints,
|
|
37
|
+
clean_e_sum,
|
|
38
|
+
clean_efficiency_link,
|
|
39
|
+
clean_ciclicity_storage,
|
|
40
|
+
clean_marginal_cost_intermittent,
|
|
41
|
+
clean_storage_units,
|
|
42
|
+
clean_stores,
|
|
43
|
+
parse_txt_file,
|
|
44
|
+
clean_p_min_pu,
|
|
45
|
+
one_bus_network,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"Transformation",
|
|
50
|
+
"TransformationConfig",
|
|
51
|
+
# network correction tools
|
|
52
|
+
"clean_marginal_cost",
|
|
53
|
+
"clean_global_constraints",
|
|
54
|
+
"clean_e_sum",
|
|
55
|
+
"clean_efficiency_link",
|
|
56
|
+
"clean_ciclicity_storage",
|
|
57
|
+
"clean_marginal_cost_intermittent",
|
|
58
|
+
"clean_storage_units",
|
|
59
|
+
"clean_stores",
|
|
60
|
+
"parse_txt_file",
|
|
61
|
+
"clean_p_min_pu",
|
|
62
|
+
"one_bus_network",
|
|
63
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Jun 26 15:50:01 2025
|
|
4
|
+
|
|
5
|
+
@author: aless
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
constants.py
|
|
10
|
+
|
|
11
|
+
This module contains structural constants used throughout the PyPSA2SMSpp
|
|
12
|
+
transformation process. These values define generic mappings and categories
|
|
13
|
+
used repeatedly in the Transformation class and related utilities.
|
|
14
|
+
|
|
15
|
+
They are not meant to be modified by the user and do not depend on any
|
|
16
|
+
specific instance or configuration of the network.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Dictionary mapping internal shorthand dimensions to full SMS++ dimension names
|
|
20
|
+
conversion_dict = {
|
|
21
|
+
"T": "TimeHorizon",
|
|
22
|
+
"NU": "NumberUnits",
|
|
23
|
+
"NE": "NumberElectricalGenerators",
|
|
24
|
+
"N": "NumberNodes",
|
|
25
|
+
"L": "NumberLines",
|
|
26
|
+
"Li": "Links",
|
|
27
|
+
"NA": "NumberArcs",
|
|
28
|
+
"NR": "NumberReservoirs",
|
|
29
|
+
"NP": "TotalNumberPieces",
|
|
30
|
+
"Nass": "NumAssets",
|
|
31
|
+
"NB": "NumberBranches",
|
|
32
|
+
"NDL": "NumberDesignLines",
|
|
33
|
+
"NDLL": "NumberDesignLines_lines",
|
|
34
|
+
"NDLLi": "NumberDesignLines_links",
|
|
35
|
+
"1": 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Mapping from PyPSA component types to their nominal attribute
|
|
39
|
+
nominal_attrs = {
|
|
40
|
+
"Generator": "p_nom",
|
|
41
|
+
"Line": "s_nom",
|
|
42
|
+
"Transformer": "s_nom",
|
|
43
|
+
"Link": "p_nom",
|
|
44
|
+
"Store": "e_nom",
|
|
45
|
+
"StorageUnit": "p_nom",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# List of renewable carriers used to identify IntermittentUnitBlocks
|
|
49
|
+
renewable_carriers = [
|
|
50
|
+
"solar",
|
|
51
|
+
"solar-hsat",
|
|
52
|
+
"onwind",
|
|
53
|
+
"offwind-ac",
|
|
54
|
+
"offwind-dc",
|
|
55
|
+
"offwind-float",
|
|
56
|
+
"PV",
|
|
57
|
+
"wind",
|
|
58
|
+
"ror"
|
|
59
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
transformation:
|
|
2
|
+
merge_links: true
|
|
3
|
+
expansion_ucblock: false # To include with investmentblock... Collect everything with mode
|
|
4
|
+
max_hours_stores: 1
|
|
5
|
+
|
|
6
|
+
run:
|
|
7
|
+
mode: investmentblock # ucblock | investmentblock
|
|
8
|
+
|
|
9
|
+
smspp:
|
|
10
|
+
ucblock:
|
|
11
|
+
output_prefix: uc
|
|
12
|
+
template: UCBlock/uc_solverconfig_grb
|
|
13
|
+
investmentblock:
|
|
14
|
+
output_prefix: inv
|
|
15
|
+
template: InvestmentBlock/BSPar.txt
|
|
16
|
+
inner_block_name: InvestmentBlock
|
|
17
|
+
log_executable_call: true
|
|
18
|
+
|
|
19
|
+
io:
|
|
20
|
+
workdir: output
|
|
21
|
+
name: test_case
|
|
22
|
+
overwrite: true
|
|
Binary file
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Jun 26 15:55:15 2025
|
|
4
|
+
|
|
5
|
+
@author: aless
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
inverse.py
|
|
10
|
+
|
|
11
|
+
This module handles the inverse transformation from an SMS++ solution
|
|
12
|
+
back into a PyPSA-compatible xarray.Dataset. It reconstructs time-series
|
|
13
|
+
and scalar variables from SMS unit blocks and prepares them for use with
|
|
14
|
+
assign_solution in PyPSA.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import xarray as xr
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def normalize_key(key: str) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Normalizes a key string by converting to lowercase and replacing spaces with underscores.
|
|
24
|
+
"""
|
|
25
|
+
return key.lower().replace(" ", "_")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def component_definition(n, unit_block: dict) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Maps a unit block type to its corresponding PyPSA component name.
|
|
31
|
+
"""
|
|
32
|
+
block = unit_block['block']
|
|
33
|
+
match block:
|
|
34
|
+
case "IntermittentUnitBlock":
|
|
35
|
+
return "Generator"
|
|
36
|
+
case "ThermalUnitBlock":
|
|
37
|
+
return "Generator"
|
|
38
|
+
case "HydroUnitBlock":
|
|
39
|
+
return "StorageUnit"
|
|
40
|
+
case "BatteryUnitBlock":
|
|
41
|
+
return "StorageUnit" if unit_block['name'] in n.storage_units.index else "Store"
|
|
42
|
+
case "DCNetworkBlock_lines":
|
|
43
|
+
return "Line"
|
|
44
|
+
case "DCNetworkBlock_links":
|
|
45
|
+
return "Link"
|
|
46
|
+
case "SlackUnitBlock":
|
|
47
|
+
return "Generator"
|
|
48
|
+
case _:
|
|
49
|
+
raise ValueError(f"Unknown unit block type: {block}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def evaluate_function(func, normalized_keys, unit_block, df):
|
|
53
|
+
"""
|
|
54
|
+
Evaluates a callable inverse function by extracting arguments from unit block or network.
|
|
55
|
+
"""
|
|
56
|
+
param_names = func.__code__.co_varnames[:func.__code__.co_argcount]
|
|
57
|
+
args = []
|
|
58
|
+
for param in param_names:
|
|
59
|
+
param = normalize_key(param)
|
|
60
|
+
if param in normalized_keys:
|
|
61
|
+
args.append(unit_block[normalized_keys[param]])
|
|
62
|
+
else:
|
|
63
|
+
args.append(df.loc[unit_block['name']][param])
|
|
64
|
+
return func(*args)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def dataarray_components(n, value, component, unit_block, key):
|
|
68
|
+
"""
|
|
69
|
+
Build xarray dims/coords compliant with PyPSA 1.0 assign_solution():
|
|
70
|
+
- static per-unit: dims -> ('name',)
|
|
71
|
+
- time series: dims -> ('snapshot','name')
|
|
72
|
+
"""
|
|
73
|
+
# Unmask masked arrays; use NaN for masked scalars
|
|
74
|
+
if isinstance(value, np.ma.MaskedArray):
|
|
75
|
+
value = value.filled(np.nan)
|
|
76
|
+
|
|
77
|
+
value = np.asarray(value)
|
|
78
|
+
unit_name = unit_block["name"]
|
|
79
|
+
|
|
80
|
+
if value.ndim == 0:
|
|
81
|
+
# scalar -> one value for this unit: ('name',)
|
|
82
|
+
value = value.reshape(1)
|
|
83
|
+
dims = ["name"]
|
|
84
|
+
coords = {"name": [unit_name]}
|
|
85
|
+
|
|
86
|
+
elif value.ndim == 1:
|
|
87
|
+
if len(value) == len(n.snapshots):
|
|
88
|
+
# (T,) -> ('snapshot','name')
|
|
89
|
+
value = value[:, np.newaxis] # (T,1)
|
|
90
|
+
dims = ["snapshot", "name"]
|
|
91
|
+
coords = {"snapshot": n.snapshots, "name": [unit_name]}
|
|
92
|
+
else:
|
|
93
|
+
# vector but not time-based -> treat as per-unit static ('name',)
|
|
94
|
+
# (se è un vettore con più elementi non-temporali, somma o valida prima)
|
|
95
|
+
value = np.array([value.sum()]) if value.size > 1 else value.reshape(1)
|
|
96
|
+
dims = ["name"]
|
|
97
|
+
coords = {"name": [unit_name]}
|
|
98
|
+
|
|
99
|
+
elif value.ndim == 2:
|
|
100
|
+
T = len(n.snapshots)
|
|
101
|
+
if value.shape == (T, 1):
|
|
102
|
+
dims = ["snapshot", "name"]
|
|
103
|
+
coords = {"snapshot": n.snapshots, "name": [unit_name]}
|
|
104
|
+
elif value.shape == (1, T):
|
|
105
|
+
value = value.T
|
|
106
|
+
dims = ["snapshot", "name"]
|
|
107
|
+
coords = {"snapshot": n.snapshots, "name": [unit_name]}
|
|
108
|
+
else:
|
|
109
|
+
raise ValueError(f"Unsupported shape for variable {key}: {value.shape}")
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError(f"Unsupported ndim for variable {key}: {value.ndim}")
|
|
112
|
+
|
|
113
|
+
var_name = f"{component}-{key}" # e.g., 'Generator-p', 'Store-e_nom', 'Link-p'
|
|
114
|
+
return value, dims, coords, var_name
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def block_to_dataarrays(n, unit_name, unit_block, component, config) -> dict:
|
|
119
|
+
"""
|
|
120
|
+
Transforms a unit block into a dictionary of DataArrays.
|
|
121
|
+
"""
|
|
122
|
+
attr_name = f"{unit_block['block']}_inverse"
|
|
123
|
+
converted_dict = {}
|
|
124
|
+
normalized_keys = {normalize_key(k): k for k in unit_block.keys()}
|
|
125
|
+
|
|
126
|
+
if hasattr(config, attr_name):
|
|
127
|
+
unitblock_parameters = getattr(config, attr_name)
|
|
128
|
+
else:
|
|
129
|
+
print(f"Block {unit_block['block']} not yet implemented")
|
|
130
|
+
return {}
|
|
131
|
+
|
|
132
|
+
df = getattr(n, config.component_mapping[component])
|
|
133
|
+
|
|
134
|
+
for key, func in unitblock_parameters.items():
|
|
135
|
+
if callable(func):
|
|
136
|
+
value = evaluate_function(func, normalized_keys, unit_block, df)
|
|
137
|
+
if isinstance(value, np.ndarray) and value.ndim == 2 and all(dim > 1 for dim in value.shape):
|
|
138
|
+
value = value.sum(axis=0)
|
|
139
|
+
value, dims, coords, var_name = dataarray_components(n, value, component, unit_block, key)
|
|
140
|
+
converted_dict[var_name] = xr.DataArray(value, dims=dims, coords=coords, name=var_name)
|
|
141
|
+
|
|
142
|
+
return converted_dict
|
|
143
|
+
|