tsam 3.0.0__tar.gz → 3.1.0__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.
- {tsam-3.0.0/src/tsam.egg-info → tsam-3.1.0}/PKG-INFO +10 -11
- {tsam-3.0.0 → tsam-3.1.0}/README.md +2 -2
- {tsam-3.0.0 → tsam-3.1.0}/environment.yml +2 -2
- {tsam-3.0.0 → tsam-3.1.0}/pyproject.toml +5 -5
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/api.py +1 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/config.py +42 -3
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/timeseriesaggregation.py +85 -5
- {tsam-3.0.0 → tsam-3.1.0/src/tsam.egg-info}/PKG-INFO +10 -11
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam.egg-info/requires.txt +2 -3
- tsam-3.1.0/test/test_extremePeriods.py +237 -0
- tsam-3.0.0/test/test_extremePeriods.py +0 -87
- {tsam-3.0.0 → tsam-3.1.0}/LICENSE.txt +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/MANIFEST.in +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/setup.cfg +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/__init__.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/exceptions.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/hyperparametertuning.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/periodAggregation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/plot.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/py.typed +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/representations.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/result.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/tuning.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/utils/__init__.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/utils/durationRepresentation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/utils/k_maxoids.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/utils/k_medoids_contiguity.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/utils/k_medoids_exact.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam/utils/segmentation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam.egg-info/SOURCES.txt +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam.egg-info/dependency_links.txt +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/src/tsam.egg-info/top_level.txt +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_accuracyIndicators.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_adjacent_periods.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_aggregate_hiearchical.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_api_equivalence.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_assert_raises.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_averaging.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_cluster_order.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_clustering_e2e.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_durationCurve.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_durationRepresentation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_hierarchical.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_hypertuneAggregation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_k_maxoids.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_k_medoids.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_k_medoids_contiguity.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_minmaxRepresentation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_new_api.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_preprocess.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_properties.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_reconstruct_samemean_segmentation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_samemean.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_segmentation.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_subhourlyResolution.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_subhourly_periods.py +0 -0
- {tsam-3.0.0 → tsam-3.1.0}/test/test_weightingFactors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsam
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Time series aggregation module (tsam) to create typical periods
|
|
5
5
|
Author-email: Leander Kotzur <leander.kotzur@googlemail.com>, Maximilian Hoffmann <maximilian.hoffmann@julumni.fz-juelich.de>
|
|
6
6
|
Maintainer-email: Julian Belina <j.belina@fz-juelich.de>
|
|
@@ -49,14 +49,8 @@ Requires-Dist: pandas<=3.0.0,>=2.2.0
|
|
|
49
49
|
Requires-Dist: numpy<=2.4.1,>=1.22.4
|
|
50
50
|
Requires-Dist: pyomo<=6.95,>=6.4.8
|
|
51
51
|
Requires-Dist: networkx<=3.6.1,>=2.5
|
|
52
|
-
Requires-Dist: tqdm<=4.67.
|
|
52
|
+
Requires-Dist: tqdm<=4.67.2,>=4.21.0
|
|
53
53
|
Requires-Dist: highspy<=1.12.0,>=1.7.2
|
|
54
|
-
Provides-Extra: plot
|
|
55
|
-
Requires-Dist: plotly>=5.0.0; extra == "plot"
|
|
56
|
-
Provides-Extra: notebooks
|
|
57
|
-
Requires-Dist: notebook>=7.5.0; extra == "notebooks"
|
|
58
|
-
Requires-Dist: plotly>=5.0.0; extra == "notebooks"
|
|
59
|
-
Requires-Dist: matplotlib; extra == "notebooks"
|
|
60
54
|
Provides-Extra: develop
|
|
61
55
|
Requires-Dist: pytest; extra == "develop"
|
|
62
56
|
Requires-Dist: pytest-cov; extra == "develop"
|
|
@@ -65,6 +59,7 @@ Requires-Dist: codecov; extra == "develop"
|
|
|
65
59
|
Requires-Dist: sphinx; extra == "develop"
|
|
66
60
|
Requires-Dist: sphinx-autobuild; extra == "develop"
|
|
67
61
|
Requires-Dist: sphinx_book_theme; extra == "develop"
|
|
62
|
+
Requires-Dist: nbsphinx; extra == "develop"
|
|
68
63
|
Requires-Dist: twine; extra == "develop"
|
|
69
64
|
Requires-Dist: nbval; extra == "develop"
|
|
70
65
|
Requires-Dist: ruff; extra == "develop"
|
|
@@ -73,7 +68,11 @@ Requires-Dist: pandas-stubs; extra == "develop"
|
|
|
73
68
|
Requires-Dist: pre-commit; extra == "develop"
|
|
74
69
|
Requires-Dist: plotly>=5.0.0; extra == "develop"
|
|
75
70
|
Requires-Dist: notebook>=7.5.0; extra == "develop"
|
|
76
|
-
|
|
71
|
+
Provides-Extra: plot
|
|
72
|
+
Requires-Dist: plotly>=5.0.0; extra == "plot"
|
|
73
|
+
Provides-Extra: notebooks
|
|
74
|
+
Requires-Dist: notebook>=7.5.0; extra == "notebooks"
|
|
75
|
+
Requires-Dist: plotly>=5.0.0; extra == "notebooks"
|
|
77
76
|
Dynamic: license-file
|
|
78
77
|
|
|
79
78
|
[](https://pypi.python.org/pypi/tsam) [](https://anaconda.org/conda-forge/tsam) [](https://tsam.readthedocs.io/en/latest/) []((https://github.com/FZJ-IEK3-VSA/tsam/blob/master/LICENSE.txt)) [](https://codecov.io/gh/FZJ-IEK3-VSA/tsam)
|
|
@@ -217,9 +216,9 @@ cluster_representatives = aggregation.createTypicalPeriods()
|
|
|
217
216
|
### Detailed examples
|
|
218
217
|
Detailed examples can be found at:/docs/source/examples_notebooks/
|
|
219
218
|
|
|
220
|
-
A [**
|
|
219
|
+
A [**quickstart example**](/docs/source/examples_notebooks/quickstart.ipynb) shows the capabilities of tsam as a Jupyter notebook.
|
|
221
220
|
|
|
222
|
-
A [**second example**](/docs/source/examples_notebooks/
|
|
221
|
+
A [**second example**](/docs/source/examples_notebooks/optimization_input.ipynb) shows in more detail how to access the relevant aggregation results required for parameterizing e.g. an optimization.
|
|
223
222
|
|
|
224
223
|
The example time series are based on a department [publication](https://www.mdpi.com/1996-1073/10/3/361) and the [test reference years of the DWD](https://www.dwd.de/DE/leistungen/testreferenzjahre/testreferenzjahre.html).
|
|
225
224
|
|
|
@@ -139,9 +139,9 @@ cluster_representatives = aggregation.createTypicalPeriods()
|
|
|
139
139
|
### Detailed examples
|
|
140
140
|
Detailed examples can be found at:/docs/source/examples_notebooks/
|
|
141
141
|
|
|
142
|
-
A [**
|
|
142
|
+
A [**quickstart example**](/docs/source/examples_notebooks/quickstart.ipynb) shows the capabilities of tsam as a Jupyter notebook.
|
|
143
143
|
|
|
144
|
-
A [**second example**](/docs/source/examples_notebooks/
|
|
144
|
+
A [**second example**](/docs/source/examples_notebooks/optimization_input.ipynb) shows in more detail how to access the relevant aggregation results required for parameterizing e.g. an optimization.
|
|
145
145
|
|
|
146
146
|
The example time series are based on a department [publication](https://www.mdpi.com/1996-1073/10/3/361) and the [test reference years of the DWD](https://www.dwd.de/DE/leistungen/testreferenzjahre/testreferenzjahre.html).
|
|
147
147
|
|
|
@@ -10,9 +10,8 @@ dependencies:
|
|
|
10
10
|
- numpy >=1.22.4,<=2.4.1
|
|
11
11
|
- pyomo >=6.4.3,<=6.95
|
|
12
12
|
- networkx >=2.5,<=3.6.1
|
|
13
|
-
- tqdm >=4.21.0,<=4.67.
|
|
13
|
+
- tqdm >=4.21.0,<=4.67.2
|
|
14
14
|
- highspy >=1.7.2,<=1.12.0
|
|
15
|
-
- matplotlib
|
|
16
15
|
- plotly >=5.0.0
|
|
17
16
|
# Testing
|
|
18
17
|
- pytest
|
|
@@ -23,6 +22,7 @@ dependencies:
|
|
|
23
22
|
# Documentation
|
|
24
23
|
- sphinx
|
|
25
24
|
- sphinx-autobuild
|
|
25
|
+
- nbsphinx
|
|
26
26
|
- sphinx-book-theme
|
|
27
27
|
# Linting and formatting
|
|
28
28
|
- ruff
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "tsam"
|
|
8
|
-
version = "3.
|
|
8
|
+
version = "3.1.0"
|
|
9
9
|
description = "Time series aggregation module (tsam) to create typical periods"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Leander Kotzur", email = "leander.kotzur@googlemail.com" },
|
|
@@ -37,15 +37,13 @@ dependencies = [
|
|
|
37
37
|
"numpy>=1.22.4,<=2.4.1",
|
|
38
38
|
"pyomo>=6.4.8,<=6.95",
|
|
39
39
|
"networkx>=2.5,<=3.6.1",
|
|
40
|
-
"tqdm>=4.21.0,<=4.67.
|
|
40
|
+
"tqdm>=4.21.0,<=4.67.2",
|
|
41
41
|
"highspy>=1.7.2,<=1.12.0",
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
requires-python = ">=3.10,<3.15"
|
|
45
45
|
|
|
46
46
|
[project.optional-dependencies]
|
|
47
|
-
plot = ["plotly>=5.0.0"]
|
|
48
|
-
notebooks = ["notebook>=7.5.0", "plotly>=5.0.0", "matplotlib"]
|
|
49
47
|
develop = [
|
|
50
48
|
"pytest",
|
|
51
49
|
"pytest-cov",
|
|
@@ -54,6 +52,7 @@ develop = [
|
|
|
54
52
|
"sphinx",
|
|
55
53
|
"sphinx-autobuild",
|
|
56
54
|
"sphinx_book_theme",
|
|
55
|
+
"nbsphinx",
|
|
57
56
|
"twine",
|
|
58
57
|
"nbval",
|
|
59
58
|
"ruff",
|
|
@@ -62,8 +61,9 @@ develop = [
|
|
|
62
61
|
"pre-commit",
|
|
63
62
|
"plotly>=5.0.0",
|
|
64
63
|
"notebook>=7.5.0",
|
|
65
|
-
"matplotlib",
|
|
66
64
|
]
|
|
65
|
+
plot = ["plotly>=5.0.0"]
|
|
66
|
+
notebooks = ["notebook>=7.5.0", "plotly>=5.0.0"]
|
|
67
67
|
|
|
68
68
|
[tool.setuptools.packages.find]
|
|
69
69
|
where = ["src"]
|
|
@@ -536,6 +536,7 @@ def _build_old_params(
|
|
|
536
536
|
params["addPeakMin"] = extremes.min_value
|
|
537
537
|
params["addMeanMax"] = extremes.max_period
|
|
538
538
|
params["addMeanMin"] = extremes.min_period
|
|
539
|
+
params["extremePreserveNumClusters"] = extremes._effective_preserve_n_clusters
|
|
539
540
|
else:
|
|
540
541
|
params["extremePeriodMethod"] = "None"
|
|
541
542
|
|
|
@@ -606,9 +606,10 @@ class ClusteringResult:
|
|
|
606
606
|
):
|
|
607
607
|
warnings.warn(
|
|
608
608
|
"The 'replace' extreme method creates a hybrid cluster representation "
|
|
609
|
-
"(some columns from the
|
|
610
|
-
"be perfectly reproduced during transfer. The transferred result
|
|
611
|
-
"the
|
|
609
|
+
"(some columns from the cluster representative, some from the extreme period) "
|
|
610
|
+
"that cannot be perfectly reproduced during transfer. The transferred result "
|
|
611
|
+
"will use the stored cluster center periods directly, without the extreme "
|
|
612
|
+
"value injection that was applied during the original aggregation. "
|
|
612
613
|
"For exact transfer, use 'append' or 'new_cluster' extreme methods.",
|
|
613
614
|
UserWarning,
|
|
614
615
|
stacklevel=2,
|
|
@@ -785,6 +786,18 @@ class ExtremeConfig:
|
|
|
785
786
|
min_period : list[str], optional
|
|
786
787
|
Column names where the period with minimum total should be preserved.
|
|
787
788
|
Example: ["wind_generation"] to preserve lowest wind day.
|
|
789
|
+
|
|
790
|
+
preserve_n_clusters : bool, optional
|
|
791
|
+
Whether extreme periods count toward n_clusters.
|
|
792
|
+
- True: Extremes are included in n_clusters
|
|
793
|
+
(e.g., n_clusters=10 with 2 extremes = 8 from clustering + 2 extremes)
|
|
794
|
+
- False: Extremes are added on top of n_clusters (old api behaviour)
|
|
795
|
+
(e.g., n_clusters=10 + 2 extremes = 12 final clusters)
|
|
796
|
+
Only affects "append" or "new_cluster" methods ("replace" never changes n_clusters).
|
|
797
|
+
|
|
798
|
+
.. deprecated::
|
|
799
|
+
The default will change from False to True in a future release.
|
|
800
|
+
Set explicitly to silence the FutureWarning.
|
|
788
801
|
"""
|
|
789
802
|
|
|
790
803
|
method: ExtremeMethod = "append"
|
|
@@ -792,6 +805,18 @@ class ExtremeConfig:
|
|
|
792
805
|
min_value: list[str] = field(default_factory=list)
|
|
793
806
|
max_period: list[str] = field(default_factory=list)
|
|
794
807
|
min_period: list[str] = field(default_factory=list)
|
|
808
|
+
preserve_n_clusters: bool | None = None
|
|
809
|
+
|
|
810
|
+
def __post_init__(self) -> None:
|
|
811
|
+
"""Emit FutureWarning if preserve_n_clusters is not explicitly set."""
|
|
812
|
+
if self.preserve_n_clusters is None and self.has_extremes():
|
|
813
|
+
warnings.warn(
|
|
814
|
+
"preserve_n_clusters currently defaults to False to match behaviour of the old api, "
|
|
815
|
+
"but will default to True in a future release. Set preserve_n_clusters explicitly "
|
|
816
|
+
"to silence this warning.",
|
|
817
|
+
FutureWarning,
|
|
818
|
+
stacklevel=3,
|
|
819
|
+
)
|
|
795
820
|
|
|
796
821
|
def has_extremes(self) -> bool:
|
|
797
822
|
"""Check if any extreme periods are configured."""
|
|
@@ -799,6 +824,17 @@ class ExtremeConfig:
|
|
|
799
824
|
self.max_value or self.min_value or self.max_period or self.min_period
|
|
800
825
|
)
|
|
801
826
|
|
|
827
|
+
@property
|
|
828
|
+
def _effective_preserve_n_clusters(self) -> bool:
|
|
829
|
+
"""Get the effective value for preserve_n_clusters.
|
|
830
|
+
|
|
831
|
+
Returns False if not explicitly set (current default behavior).
|
|
832
|
+
In a future release, the default will change to True.
|
|
833
|
+
"""
|
|
834
|
+
if self.preserve_n_clusters is None:
|
|
835
|
+
return False # Current default, will change to True in future
|
|
836
|
+
return self.preserve_n_clusters
|
|
837
|
+
|
|
802
838
|
def to_dict(self) -> dict[str, Any]:
|
|
803
839
|
"""Convert to dictionary for JSON serialization."""
|
|
804
840
|
result: dict[str, Any] = {}
|
|
@@ -812,6 +848,8 @@ class ExtremeConfig:
|
|
|
812
848
|
result["max_period"] = self.max_period
|
|
813
849
|
if self.min_period:
|
|
814
850
|
result["min_period"] = self.min_period
|
|
851
|
+
if self.preserve_n_clusters is not None:
|
|
852
|
+
result["preserve_n_clusters"] = self.preserve_n_clusters
|
|
815
853
|
return result
|
|
816
854
|
|
|
817
855
|
@classmethod
|
|
@@ -823,6 +861,7 @@ class ExtremeConfig:
|
|
|
823
861
|
min_value=data.get("min_value", []),
|
|
824
862
|
max_period=data.get("max_period", []),
|
|
825
863
|
min_period=data.get("min_period", []),
|
|
864
|
+
preserve_n_clusters=data.get("preserve_n_clusters"),
|
|
826
865
|
)
|
|
827
866
|
|
|
828
867
|
|
|
@@ -132,6 +132,7 @@ class TimeSeriesAggregation:
|
|
|
132
132
|
weightDict=None,
|
|
133
133
|
segmentation=False,
|
|
134
134
|
extremePeriodMethod="None",
|
|
135
|
+
extremePreserveNumClusters=False,
|
|
135
136
|
representationMethod=None,
|
|
136
137
|
representationDict=None,
|
|
137
138
|
distributionPeriodWise=True,
|
|
@@ -318,6 +319,8 @@ class TimeSeriesAggregation:
|
|
|
318
319
|
|
|
319
320
|
self.extremePeriodMethod = extremePeriodMethod
|
|
320
321
|
|
|
322
|
+
self.extremePreserveNumClusters = extremePreserveNumClusters
|
|
323
|
+
|
|
321
324
|
self.evalSumPeriods = evalSumPeriods
|
|
322
325
|
|
|
323
326
|
self.sortValues = sortValues
|
|
@@ -683,6 +686,46 @@ class TimeSeriesAggregation:
|
|
|
683
686
|
|
|
684
687
|
return unnormalizedTimeSeries
|
|
685
688
|
|
|
689
|
+
def _countExtremePeriods(self, groupedSeries):
|
|
690
|
+
"""
|
|
691
|
+
Count unique extreme periods without modifying any state.
|
|
692
|
+
|
|
693
|
+
Used by extremePreserveNumClusters to determine how many clusters
|
|
694
|
+
to reserve for extreme periods before clustering.
|
|
695
|
+
|
|
696
|
+
Note: The extreme-finding logic (idxmax/idxmin on peak/mean) must
|
|
697
|
+
stay in sync with _addExtremePeriods. This is intentionally separate
|
|
698
|
+
because _addExtremePeriods also filters out periods that are already
|
|
699
|
+
cluster centers (not known at count time).
|
|
700
|
+
"""
|
|
701
|
+
extremePeriodIndices = set()
|
|
702
|
+
|
|
703
|
+
# Only iterate over columns that are actually in extreme lists
|
|
704
|
+
extreme_columns = (
|
|
705
|
+
set(self.addPeakMax)
|
|
706
|
+
| set(self.addPeakMin)
|
|
707
|
+
| set(self.addMeanMax)
|
|
708
|
+
| set(self.addMeanMin)
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
for column in extreme_columns:
|
|
712
|
+
col_data = groupedSeries[column]
|
|
713
|
+
|
|
714
|
+
if column in self.addPeakMax:
|
|
715
|
+
extremePeriodIndices.add(col_data.max(axis=1).idxmax())
|
|
716
|
+
if column in self.addPeakMin:
|
|
717
|
+
extremePeriodIndices.add(col_data.min(axis=1).idxmin())
|
|
718
|
+
|
|
719
|
+
# Compute mean only once if needed for either addMeanMax or addMeanMin
|
|
720
|
+
if column in self.addMeanMax or column in self.addMeanMin:
|
|
721
|
+
mean_series = col_data.mean(axis=1)
|
|
722
|
+
if column in self.addMeanMax:
|
|
723
|
+
extremePeriodIndices.add(mean_series.idxmax())
|
|
724
|
+
if column in self.addMeanMin:
|
|
725
|
+
extremePeriodIndices.add(mean_series.idxmin())
|
|
726
|
+
|
|
727
|
+
return len(extremePeriodIndices)
|
|
728
|
+
|
|
686
729
|
def _addExtremePeriods(
|
|
687
730
|
self,
|
|
688
731
|
groupedSeries,
|
|
@@ -983,7 +1026,7 @@ class TimeSeriesAggregation:
|
|
|
983
1026
|
# Reshape back to 2D: (n_clusters, n_cols * n_timesteps)
|
|
984
1027
|
return arr.reshape(n_clusters, -1)
|
|
985
1028
|
|
|
986
|
-
def _clusterSortedPeriods(self, candidates, n_init=20):
|
|
1029
|
+
def _clusterSortedPeriods(self, candidates, n_init=20, n_clusters=None):
|
|
987
1030
|
"""
|
|
988
1031
|
Runs the clustering algorithms for the sorted profiles within the period
|
|
989
1032
|
instead of the original profiles. (Duration curve clustering)
|
|
@@ -1001,13 +1044,16 @@ class TimeSeriesAggregation:
|
|
|
1001
1044
|
n_periods, -1
|
|
1002
1045
|
)
|
|
1003
1046
|
|
|
1047
|
+
if n_clusters is None:
|
|
1048
|
+
n_clusters = self.noTypicalPeriods
|
|
1049
|
+
|
|
1004
1050
|
(
|
|
1005
1051
|
_altClusterCenters,
|
|
1006
1052
|
self.clusterCenterIndices,
|
|
1007
1053
|
clusterOrders_C,
|
|
1008
1054
|
) = aggregatePeriods(
|
|
1009
1055
|
sortedClusterValues,
|
|
1010
|
-
n_clusters=
|
|
1056
|
+
n_clusters=n_clusters,
|
|
1011
1057
|
n_iter=30,
|
|
1012
1058
|
solver=self.solver,
|
|
1013
1059
|
clusterMethod=self.clusterMethod,
|
|
@@ -1052,6 +1098,41 @@ class TimeSeriesAggregation:
|
|
|
1052
1098
|
"""
|
|
1053
1099
|
self._preProcessTimeSeries()
|
|
1054
1100
|
|
|
1101
|
+
# Warn if extremePreserveNumClusters is ignored due to predefined cluster order
|
|
1102
|
+
if (
|
|
1103
|
+
self.predefClusterOrder is not None
|
|
1104
|
+
and self.extremePreserveNumClusters
|
|
1105
|
+
and self.extremePeriodMethod not in ("None", "replace_cluster_center")
|
|
1106
|
+
):
|
|
1107
|
+
warnings.warn(
|
|
1108
|
+
"extremePreserveNumClusters=True is ignored when predefClusterOrder "
|
|
1109
|
+
"is set. Extreme periods will be appended via _addExtremePeriods "
|
|
1110
|
+
"without reserving clusters upfront. To avoid this warning, set "
|
|
1111
|
+
"extremePreserveNumClusters=False or remove predefClusterOrder.",
|
|
1112
|
+
UserWarning,
|
|
1113
|
+
stacklevel=2,
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
# Count extreme periods upfront if include_in_count is True
|
|
1117
|
+
# Note: replace_cluster_center doesn't add new clusters, so skip
|
|
1118
|
+
n_extremes = 0
|
|
1119
|
+
if (
|
|
1120
|
+
self.extremePreserveNumClusters
|
|
1121
|
+
and self.extremePeriodMethod not in ("None", "replace_cluster_center")
|
|
1122
|
+
and self.predefClusterOrder is None # Don't count for predefined
|
|
1123
|
+
):
|
|
1124
|
+
n_extremes = self._countExtremePeriods(self.normalizedPeriodlyProfiles)
|
|
1125
|
+
|
|
1126
|
+
if self.noTypicalPeriods <= n_extremes:
|
|
1127
|
+
raise ValueError(
|
|
1128
|
+
f"n_clusters ({self.noTypicalPeriods}) must be greater than "
|
|
1129
|
+
f"the number of extreme periods ({n_extremes}) when "
|
|
1130
|
+
"preserve_n_clusters=True"
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
# Compute effective number of clusters for the clustering algorithm
|
|
1134
|
+
effective_n_clusters = self.noTypicalPeriods - n_extremes
|
|
1135
|
+
|
|
1055
1136
|
# check for additional cluster parameters
|
|
1056
1137
|
if self.evalSumPeriods:
|
|
1057
1138
|
evaluationValues = (
|
|
@@ -1096,7 +1177,7 @@ class TimeSeriesAggregation:
|
|
|
1096
1177
|
self._clusterOrder,
|
|
1097
1178
|
) = aggregatePeriods(
|
|
1098
1179
|
candidates,
|
|
1099
|
-
n_clusters=
|
|
1180
|
+
n_clusters=effective_n_clusters,
|
|
1100
1181
|
n_iter=100,
|
|
1101
1182
|
solver=self.solver,
|
|
1102
1183
|
clusterMethod=self.clusterMethod,
|
|
@@ -1107,7 +1188,7 @@ class TimeSeriesAggregation:
|
|
|
1107
1188
|
)
|
|
1108
1189
|
else:
|
|
1109
1190
|
self.clusterCenters, self._clusterOrder = self._clusterSortedPeriods(
|
|
1110
|
-
candidates
|
|
1191
|
+
candidates, n_clusters=effective_n_clusters
|
|
1111
1192
|
)
|
|
1112
1193
|
self.clusteringDuration = time.time() - cluster_duration
|
|
1113
1194
|
|
|
@@ -1117,7 +1198,6 @@ class TimeSeriesAggregation:
|
|
|
1117
1198
|
self.clusterPeriods.append(cluster_center[:delClusterParams])
|
|
1118
1199
|
|
|
1119
1200
|
if not self.extremePeriodMethod == "None":
|
|
1120
|
-
# overwrite clusterPeriods and clusterOrder
|
|
1121
1201
|
(
|
|
1122
1202
|
self.clusterPeriods,
|
|
1123
1203
|
self._clusterOrder,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsam
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Time series aggregation module (tsam) to create typical periods
|
|
5
5
|
Author-email: Leander Kotzur <leander.kotzur@googlemail.com>, Maximilian Hoffmann <maximilian.hoffmann@julumni.fz-juelich.de>
|
|
6
6
|
Maintainer-email: Julian Belina <j.belina@fz-juelich.de>
|
|
@@ -49,14 +49,8 @@ Requires-Dist: pandas<=3.0.0,>=2.2.0
|
|
|
49
49
|
Requires-Dist: numpy<=2.4.1,>=1.22.4
|
|
50
50
|
Requires-Dist: pyomo<=6.95,>=6.4.8
|
|
51
51
|
Requires-Dist: networkx<=3.6.1,>=2.5
|
|
52
|
-
Requires-Dist: tqdm<=4.67.
|
|
52
|
+
Requires-Dist: tqdm<=4.67.2,>=4.21.0
|
|
53
53
|
Requires-Dist: highspy<=1.12.0,>=1.7.2
|
|
54
|
-
Provides-Extra: plot
|
|
55
|
-
Requires-Dist: plotly>=5.0.0; extra == "plot"
|
|
56
|
-
Provides-Extra: notebooks
|
|
57
|
-
Requires-Dist: notebook>=7.5.0; extra == "notebooks"
|
|
58
|
-
Requires-Dist: plotly>=5.0.0; extra == "notebooks"
|
|
59
|
-
Requires-Dist: matplotlib; extra == "notebooks"
|
|
60
54
|
Provides-Extra: develop
|
|
61
55
|
Requires-Dist: pytest; extra == "develop"
|
|
62
56
|
Requires-Dist: pytest-cov; extra == "develop"
|
|
@@ -65,6 +59,7 @@ Requires-Dist: codecov; extra == "develop"
|
|
|
65
59
|
Requires-Dist: sphinx; extra == "develop"
|
|
66
60
|
Requires-Dist: sphinx-autobuild; extra == "develop"
|
|
67
61
|
Requires-Dist: sphinx_book_theme; extra == "develop"
|
|
62
|
+
Requires-Dist: nbsphinx; extra == "develop"
|
|
68
63
|
Requires-Dist: twine; extra == "develop"
|
|
69
64
|
Requires-Dist: nbval; extra == "develop"
|
|
70
65
|
Requires-Dist: ruff; extra == "develop"
|
|
@@ -73,7 +68,11 @@ Requires-Dist: pandas-stubs; extra == "develop"
|
|
|
73
68
|
Requires-Dist: pre-commit; extra == "develop"
|
|
74
69
|
Requires-Dist: plotly>=5.0.0; extra == "develop"
|
|
75
70
|
Requires-Dist: notebook>=7.5.0; extra == "develop"
|
|
76
|
-
|
|
71
|
+
Provides-Extra: plot
|
|
72
|
+
Requires-Dist: plotly>=5.0.0; extra == "plot"
|
|
73
|
+
Provides-Extra: notebooks
|
|
74
|
+
Requires-Dist: notebook>=7.5.0; extra == "notebooks"
|
|
75
|
+
Requires-Dist: plotly>=5.0.0; extra == "notebooks"
|
|
77
76
|
Dynamic: license-file
|
|
78
77
|
|
|
79
78
|
[](https://pypi.python.org/pypi/tsam) [](https://anaconda.org/conda-forge/tsam) [](https://tsam.readthedocs.io/en/latest/) []((https://github.com/FZJ-IEK3-VSA/tsam/blob/master/LICENSE.txt)) [](https://codecov.io/gh/FZJ-IEK3-VSA/tsam)
|
|
@@ -217,9 +216,9 @@ cluster_representatives = aggregation.createTypicalPeriods()
|
|
|
217
216
|
### Detailed examples
|
|
218
217
|
Detailed examples can be found at:/docs/source/examples_notebooks/
|
|
219
218
|
|
|
220
|
-
A [**
|
|
219
|
+
A [**quickstart example**](/docs/source/examples_notebooks/quickstart.ipynb) shows the capabilities of tsam as a Jupyter notebook.
|
|
221
220
|
|
|
222
|
-
A [**second example**](/docs/source/examples_notebooks/
|
|
221
|
+
A [**second example**](/docs/source/examples_notebooks/optimization_input.ipynb) shows in more detail how to access the relevant aggregation results required for parameterizing e.g. an optimization.
|
|
223
222
|
|
|
224
223
|
The example time series are based on a department [publication](https://www.mdpi.com/1996-1073/10/3/361) and the [test reference years of the DWD](https://www.dwd.de/DE/leistungen/testreferenzjahre/testreferenzjahre.html).
|
|
225
224
|
|
|
@@ -3,7 +3,7 @@ pandas<=3.0.0,>=2.2.0
|
|
|
3
3
|
numpy<=2.4.1,>=1.22.4
|
|
4
4
|
pyomo<=6.95,>=6.4.8
|
|
5
5
|
networkx<=3.6.1,>=2.5
|
|
6
|
-
tqdm<=4.67.
|
|
6
|
+
tqdm<=4.67.2,>=4.21.0
|
|
7
7
|
highspy<=1.12.0,>=1.7.2
|
|
8
8
|
|
|
9
9
|
[develop]
|
|
@@ -14,6 +14,7 @@ codecov
|
|
|
14
14
|
sphinx
|
|
15
15
|
sphinx-autobuild
|
|
16
16
|
sphinx_book_theme
|
|
17
|
+
nbsphinx
|
|
17
18
|
twine
|
|
18
19
|
nbval
|
|
19
20
|
ruff
|
|
@@ -22,12 +23,10 @@ pandas-stubs
|
|
|
22
23
|
pre-commit
|
|
23
24
|
plotly>=5.0.0
|
|
24
25
|
notebook>=7.5.0
|
|
25
|
-
matplotlib
|
|
26
26
|
|
|
27
27
|
[notebooks]
|
|
28
28
|
notebook>=7.5.0
|
|
29
29
|
plotly>=5.0.0
|
|
30
|
-
matplotlib
|
|
31
30
|
|
|
32
31
|
[plot]
|
|
33
32
|
plotly>=5.0.0
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import tsam
|
|
8
|
+
import tsam.timeseriesaggregation as tsam_legacy
|
|
9
|
+
from conftest import TESTDATA_CSV
|
|
10
|
+
from tsam.config import ExtremeConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_extremePeriods():
|
|
14
|
+
hoursPerPeriod = 24
|
|
15
|
+
|
|
16
|
+
noTypicalPeriods = 8
|
|
17
|
+
|
|
18
|
+
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
19
|
+
|
|
20
|
+
aggregation1 = tsam_legacy.TimeSeriesAggregation(
|
|
21
|
+
raw,
|
|
22
|
+
noTypicalPeriods=noTypicalPeriods,
|
|
23
|
+
hoursPerPeriod=hoursPerPeriod,
|
|
24
|
+
clusterMethod="hierarchical",
|
|
25
|
+
rescaleClusterPeriods=False,
|
|
26
|
+
extremePeriodMethod="new_cluster_center",
|
|
27
|
+
addPeakMax=["GHI"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
aggregation2 = tsam_legacy.TimeSeriesAggregation(
|
|
31
|
+
raw,
|
|
32
|
+
noTypicalPeriods=noTypicalPeriods,
|
|
33
|
+
hoursPerPeriod=hoursPerPeriod,
|
|
34
|
+
clusterMethod="hierarchical",
|
|
35
|
+
rescaleClusterPeriods=False,
|
|
36
|
+
extremePeriodMethod="append",
|
|
37
|
+
addPeakMax=["GHI"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
aggregation3 = tsam_legacy.TimeSeriesAggregation(
|
|
41
|
+
raw,
|
|
42
|
+
noTypicalPeriods=noTypicalPeriods,
|
|
43
|
+
hoursPerPeriod=hoursPerPeriod,
|
|
44
|
+
clusterMethod="hierarchical",
|
|
45
|
+
rescaleClusterPeriods=False,
|
|
46
|
+
extremePeriodMethod="replace_cluster_center",
|
|
47
|
+
addPeakMax=["GHI"],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# make sure that the RMSE for new cluster centers (reassigning points to the exxtreme point if the distance to it is
|
|
51
|
+
# smaller)is bigger than for appending just one extreme period
|
|
52
|
+
np.testing.assert_array_less(
|
|
53
|
+
aggregation1.accuracyIndicators().loc["GHI", "RMSE"],
|
|
54
|
+
aggregation2.accuracyIndicators().loc["GHI", "RMSE"],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# make sure that the RMSE for appending the extreme period is smaller than for replacing the cluster center by the
|
|
58
|
+
# extreme period (conservative assumption)
|
|
59
|
+
np.testing.assert_array_less(
|
|
60
|
+
aggregation2.accuracyIndicators().loc["GHI", "RMSE"],
|
|
61
|
+
aggregation3.accuracyIndicators().loc["GHI", "RMSE"],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# check if addMeanMax and addMeanMin are working
|
|
65
|
+
aggregation4 = tsam_legacy.TimeSeriesAggregation(
|
|
66
|
+
raw,
|
|
67
|
+
noTypicalPeriods=noTypicalPeriods,
|
|
68
|
+
hoursPerPeriod=hoursPerPeriod,
|
|
69
|
+
clusterMethod="hierarchical",
|
|
70
|
+
rescaleClusterPeriods=False,
|
|
71
|
+
extremePeriodMethod="append",
|
|
72
|
+
addMeanMax=["GHI"],
|
|
73
|
+
addMeanMin=["GHI"],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
origData = aggregation4.predictOriginalData()
|
|
77
|
+
|
|
78
|
+
np.testing.assert_array_almost_equal(
|
|
79
|
+
raw.groupby(np.arange(len(raw)) // 24).mean().max().loc["GHI"],
|
|
80
|
+
origData.groupby(np.arange(len(origData)) // 24).mean().max().loc["GHI"],
|
|
81
|
+
decimal=6,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
np.testing.assert_array_almost_equal(
|
|
85
|
+
raw.groupby(np.arange(len(raw)) // 24).mean().min().loc["GHI"],
|
|
86
|
+
origData.groupby(np.arange(len(origData)) // 24).mean().min().loc["GHI"],
|
|
87
|
+
decimal=6,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_preserve_n_clusters_exact_clusters_append():
|
|
92
|
+
"""Final n_clusters equals requested when preserve_n_clusters=True with append method."""
|
|
93
|
+
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
94
|
+
|
|
95
|
+
n_clusters = 10
|
|
96
|
+
result = tsam.aggregate(
|
|
97
|
+
raw,
|
|
98
|
+
n_clusters=n_clusters,
|
|
99
|
+
extremes=ExtremeConfig(
|
|
100
|
+
method="append",
|
|
101
|
+
max_value=["GHI"],
|
|
102
|
+
min_value=["T"],
|
|
103
|
+
preserve_n_clusters=True,
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# With preserve_n_clusters=True, final cluster count should equal n_clusters
|
|
108
|
+
assert result.n_clusters == n_clusters
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_preserve_n_clusters_exact_clusters_new_cluster():
|
|
112
|
+
"""Final n_clusters equals requested when preserve_n_clusters=True with new_cluster method."""
|
|
113
|
+
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
114
|
+
|
|
115
|
+
n_clusters = 10
|
|
116
|
+
result = tsam.aggregate(
|
|
117
|
+
raw,
|
|
118
|
+
n_clusters=n_clusters,
|
|
119
|
+
extremes=ExtremeConfig(
|
|
120
|
+
method="new_cluster",
|
|
121
|
+
max_value=["GHI"],
|
|
122
|
+
preserve_n_clusters=True,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# With preserve_n_clusters=True, final cluster count should equal n_clusters
|
|
127
|
+
assert result.n_clusters == n_clusters
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_preserve_n_clusters_false_adds_extra_clusters():
|
|
131
|
+
"""Default behavior: extremes are added on top of n_clusters."""
|
|
132
|
+
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
133
|
+
|
|
134
|
+
n_clusters = 10
|
|
135
|
+
result = tsam.aggregate(
|
|
136
|
+
raw,
|
|
137
|
+
n_clusters=n_clusters,
|
|
138
|
+
extremes=ExtremeConfig(
|
|
139
|
+
method="append",
|
|
140
|
+
max_value=["GHI"],
|
|
141
|
+
min_value=["T"],
|
|
142
|
+
preserve_n_clusters=False, # Default
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# With preserve_n_clusters=False (default), extremes are added on top
|
|
147
|
+
# So final count should be > n_clusters (n_clusters + n_extremes)
|
|
148
|
+
assert result.n_clusters > n_clusters
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_preserve_n_clusters_validation_error():
|
|
152
|
+
"""Error if n_clusters <= n_extremes when preserve_n_clusters=True."""
|
|
153
|
+
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
154
|
+
|
|
155
|
+
with pytest.raises(ValueError, match="must be greater than"):
|
|
156
|
+
tsam.aggregate(
|
|
157
|
+
raw,
|
|
158
|
+
n_clusters=2,
|
|
159
|
+
extremes=ExtremeConfig(
|
|
160
|
+
max_value=["GHI", "T", "Wind"], # 3 extremes
|
|
161
|
+
preserve_n_clusters=True,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_preserve_n_clusters_preserves_extremes():
|
|
167
|
+
"""Extreme values are still preserved with preserve_n_clusters=True."""
|
|
168
|
+
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
169
|
+
|
|
170
|
+
result = tsam.aggregate(
|
|
171
|
+
raw,
|
|
172
|
+
n_clusters=10,
|
|
173
|
+
extremes=ExtremeConfig(
|
|
174
|
+
method="append",
|
|
175
|
+
max_value=["GHI"],
|
|
176
|
+
preserve_n_clusters=True,
|
|
177
|
+
),
|
|
178
|
+
preserve_column_means=False, # Don't rescale to check raw extreme preservation
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# The maximum GHI value should be preserved in the typical periods
|
|
182
|
+
orig_max = raw["GHI"].max()
|
|
183
|
+
typical_max = result.cluster_representatives["GHI"].max()
|
|
184
|
+
|
|
185
|
+
np.testing.assert_almost_equal(orig_max, typical_max, decimal=5)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_preserve_n_clusters_serialization():
|
|
189
|
+
"""ExtremeConfig with preserve_n_clusters serializes correctly."""
|
|
190
|
+
config = ExtremeConfig(
|
|
191
|
+
method="append",
|
|
192
|
+
max_value=["Load"],
|
|
193
|
+
preserve_n_clusters=True,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
d = config.to_dict()
|
|
197
|
+
assert d["preserve_n_clusters"] is True
|
|
198
|
+
|
|
199
|
+
config2 = ExtremeConfig.from_dict(d)
|
|
200
|
+
assert config2.preserve_n_clusters is True
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_preserve_n_clusters_default_none_with_future_warning():
|
|
204
|
+
"""Default value of preserve_n_clusters is None with FutureWarning."""
|
|
205
|
+
# Creating ExtremeConfig with extremes but without explicit preserve_n_clusters
|
|
206
|
+
# should emit a FutureWarning
|
|
207
|
+
with pytest.warns(FutureWarning, match="preserve_n_clusters currently defaults"):
|
|
208
|
+
config = ExtremeConfig(max_value=["Load"])
|
|
209
|
+
|
|
210
|
+
# The raw value should be None
|
|
211
|
+
assert config.preserve_n_clusters is None
|
|
212
|
+
|
|
213
|
+
# But effective value should be False (current default behavior)
|
|
214
|
+
assert config._effective_preserve_n_clusters is False
|
|
215
|
+
|
|
216
|
+
# to_dict should not include it when None
|
|
217
|
+
d = config.to_dict()
|
|
218
|
+
assert "preserve_n_clusters" not in d
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_preserve_n_clusters_explicit_false_no_warning():
|
|
222
|
+
"""Setting preserve_n_clusters=False explicitly should not warn."""
|
|
223
|
+
# No warning when explicitly set
|
|
224
|
+
with warnings.catch_warnings():
|
|
225
|
+
warnings.simplefilter("error", FutureWarning)
|
|
226
|
+
config = ExtremeConfig(max_value=["Load"], preserve_n_clusters=False)
|
|
227
|
+
|
|
228
|
+
assert config.preserve_n_clusters is False
|
|
229
|
+
assert config._effective_preserve_n_clusters is False
|
|
230
|
+
|
|
231
|
+
# to_dict should include it when explicitly False
|
|
232
|
+
d = config.to_dict()
|
|
233
|
+
assert d["preserve_n_clusters"] is False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
test_extremePeriods()
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
|
|
4
|
-
import tsam.timeseriesaggregation as tsam
|
|
5
|
-
from conftest import TESTDATA_CSV
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_extremePeriods():
|
|
9
|
-
hoursPerPeriod = 24
|
|
10
|
-
|
|
11
|
-
noTypicalPeriods = 8
|
|
12
|
-
|
|
13
|
-
raw = pd.read_csv(TESTDATA_CSV, index_col=0)
|
|
14
|
-
|
|
15
|
-
aggregation1 = tsam.TimeSeriesAggregation(
|
|
16
|
-
raw,
|
|
17
|
-
noTypicalPeriods=noTypicalPeriods,
|
|
18
|
-
hoursPerPeriod=hoursPerPeriod,
|
|
19
|
-
clusterMethod="hierarchical",
|
|
20
|
-
rescaleClusterPeriods=False,
|
|
21
|
-
extremePeriodMethod="new_cluster_center",
|
|
22
|
-
addPeakMax=["GHI"],
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
aggregation2 = tsam.TimeSeriesAggregation(
|
|
26
|
-
raw,
|
|
27
|
-
noTypicalPeriods=noTypicalPeriods,
|
|
28
|
-
hoursPerPeriod=hoursPerPeriod,
|
|
29
|
-
clusterMethod="hierarchical",
|
|
30
|
-
rescaleClusterPeriods=False,
|
|
31
|
-
extremePeriodMethod="append",
|
|
32
|
-
addPeakMax=["GHI"],
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
aggregation3 = tsam.TimeSeriesAggregation(
|
|
36
|
-
raw,
|
|
37
|
-
noTypicalPeriods=noTypicalPeriods,
|
|
38
|
-
hoursPerPeriod=hoursPerPeriod,
|
|
39
|
-
clusterMethod="hierarchical",
|
|
40
|
-
rescaleClusterPeriods=False,
|
|
41
|
-
extremePeriodMethod="replace_cluster_center",
|
|
42
|
-
addPeakMax=["GHI"],
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
# make sure that the RMSE for new cluster centers (reassigning points to the exxtreme point if the distance to it is
|
|
46
|
-
# smaller)is bigger than for appending just one extreme period
|
|
47
|
-
np.testing.assert_array_less(
|
|
48
|
-
aggregation1.accuracyIndicators().loc["GHI", "RMSE"],
|
|
49
|
-
aggregation2.accuracyIndicators().loc["GHI", "RMSE"],
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
# make sure that the RMSE for appending the extreme period is smaller than for replacing the cluster center by the
|
|
53
|
-
# extreme period (conservative assumption)
|
|
54
|
-
np.testing.assert_array_less(
|
|
55
|
-
aggregation2.accuracyIndicators().loc["GHI", "RMSE"],
|
|
56
|
-
aggregation3.accuracyIndicators().loc["GHI", "RMSE"],
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# check if addMeanMax and addMeanMin are working
|
|
60
|
-
aggregation4 = tsam.TimeSeriesAggregation(
|
|
61
|
-
raw,
|
|
62
|
-
noTypicalPeriods=noTypicalPeriods,
|
|
63
|
-
hoursPerPeriod=hoursPerPeriod,
|
|
64
|
-
clusterMethod="hierarchical",
|
|
65
|
-
rescaleClusterPeriods=False,
|
|
66
|
-
extremePeriodMethod="append",
|
|
67
|
-
addMeanMax=["GHI"],
|
|
68
|
-
addMeanMin=["GHI"],
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
origData = aggregation4.predictOriginalData()
|
|
72
|
-
|
|
73
|
-
np.testing.assert_array_almost_equal(
|
|
74
|
-
raw.groupby(np.arange(len(raw)) // 24).mean().max().loc["GHI"],
|
|
75
|
-
origData.groupby(np.arange(len(origData)) // 24).mean().max().loc["GHI"],
|
|
76
|
-
decimal=6,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
np.testing.assert_array_almost_equal(
|
|
80
|
-
raw.groupby(np.arange(len(raw)) // 24).mean().min().loc["GHI"],
|
|
81
|
-
origData.groupby(np.arange(len(origData)) // 24).mean().min().loc["GHI"],
|
|
82
|
-
decimal=6,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if __name__ == "__main__":
|
|
87
|
-
test_extremePeriods()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|