tsam 2.3.8__py3-none-any.whl → 3.0.0__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.
tsam/exceptions.py ADDED
@@ -0,0 +1,17 @@
1
+ """Custom exceptions and warnings for tsam."""
2
+
3
+
4
+ class LegacyAPIWarning(DeprecationWarning):
5
+ """Warning for deprecated tsam legacy API usage.
6
+
7
+ This warning is raised when using the old class-based API
8
+ (TimeSeriesAggregation) instead of the new functional API (aggregate).
9
+
10
+ Users can suppress this warning during migration with:
11
+
12
+ import warnings
13
+ from tsam.exceptions import LegacyAPIWarning
14
+ warnings.filterwarnings("ignore", category=LegacyAPIWarning)
15
+ """
16
+
17
+ pass
@@ -1,245 +1,289 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- import copy
4
-
5
- import numpy as np
6
-
7
- import tqdm
8
-
9
- from tsam.timeseriesaggregation import TimeSeriesAggregation
10
-
11
- def getNoPeriodsForDataReduction(noRawTimeSteps, segmentsPerPeriod, dataReduction):
12
- """
13
- Identifies the maximum number of periods which can be set to achieve the required data reduction.
14
-
15
- :param noRawTimeSteps: Number of original time steps. required
16
- :type noRawTimeSteps: int
17
-
18
- :param segmentsPerPeriod: Segments per period. required
19
- :type segmentsPerPeriod: int
20
-
21
- :param dataReduction: Factor by which the resulting dataset should be reduced. required
22
- :type dataReduction: float
23
-
24
- :returns: **noTypicalPeriods** -- Number of typical periods that can be set.
25
- """
26
- return int(np.floor(dataReduction * float(noRawTimeSteps)/segmentsPerPeriod))
27
-
28
- def getNoSegmentsForDataReduction(noRawTimeSteps, typicalPeriods, dataReduction):
29
- """
30
- Identifies the maximum number of segments which can be set to achieve the required data reduction.
31
-
32
- :param noRawTimeSteps: Number of original time steps. required
33
- :type noRawTimeSteps: int
34
-
35
- :param typicalPeriods: Number of typical periods. required
36
- :type typicalPeriods: int
37
-
38
- :param dataReduction: Factor by which the resulting dataset should be reduced. required
39
- :type dataReduction: float
40
-
41
- :returns: **segmentsPerPeriod** -- Number of segments per period that can be set.
42
- """
43
- return int(np.floor(dataReduction * float(noRawTimeSteps)/typicalPeriods))
44
-
45
-
46
-
47
-
48
- class HyperTunedAggregations(object):
49
-
50
- def __init__(self, base_aggregation, saveAggregationHistory=True):
51
- """
52
- A class that does a parameter variation and tuning of the aggregation itself.
53
-
54
- :param base_aggregation: TimeSeriesAggregation object which is used as basis for tuning the hyper parameters. required
55
- :type base_aggregation: TimeSeriesAggregation
56
-
57
- :param saveAggregationHistory: Defines if all aggregations that are created during the tuning and iterations shall be saved under self.aggregationHistory.
58
- :type saveAggregationHistory: boolean
59
- """
60
- self.base_aggregation = base_aggregation
61
-
62
- if not isinstance(self.base_aggregation, TimeSeriesAggregation):
63
- raise ValueError(
64
- "base_aggregation has to be an TimeSeriesAggregation object"
65
- )
66
-
67
- self._alterableAggregation=copy.deepcopy(self.base_aggregation)
68
-
69
- self.saveAggregationHistory=saveAggregationHistory
70
-
71
- self._segmentHistory=[]
72
-
73
- self._periodHistory=[]
74
-
75
- self._RMSEHistory=[]
76
-
77
- if self.saveAggregationHistory:
78
- self.aggregationHistory=[]
79
-
80
-
81
-
82
-
83
- def _testAggregation(self, noTypicalPeriods, noSegments):
84
- """
85
- Tests the aggregation for a set of typical periods and segments and returns the RMSE
86
- """
87
- self._segmentHistory.append(noSegments)
88
-
89
- self._periodHistory.append(noTypicalPeriods)
90
-
91
- self._alterableAggregation.noTypicalPeriods=noTypicalPeriods
92
-
93
- self._alterableAggregation.noSegments=noSegments
94
-
95
- self._alterableAggregation.createTypicalPeriods()
96
-
97
- self._alterableAggregation.predictOriginalData()
98
-
99
- RMSE=self._alterableAggregation.totalAccuracyIndicators()["RMSE"]
100
-
101
- self._RMSEHistory.append(RMSE)
102
-
103
- if self.saveAggregationHistory:
104
- self.aggregationHistory.append(copy.copy(self._alterableAggregation))
105
-
106
- return RMSE
107
-
108
- def _deleteTestHistory(self, index):
109
- """
110
- Delelets the defined index from the test history
111
- """
112
- del self._segmentHistory[index]
113
- del self._periodHistory[index]
114
- del self._RMSEHistory[index]
115
-
116
- if self.saveAggregationHistory:
117
- del self.aggregationHistory[index]
118
-
119
-
120
- def identifyOptimalSegmentPeriodCombination(self, dataReduction):
121
- """
122
- Identifies the optimal combination of number of typical periods and number of segments for a given data reduction set.
123
-
124
- :param dataReduction: Factor by which the resulting dataset should be reduced. required
125
- :type dataReduction: float
126
-
127
- :returns: **noSegments, noTypicalperiods** -- The optimal combination of segments and typical periods for the given optimization set.
128
- """
129
- if not self.base_aggregation.segmentation:
130
- raise ValueError("This function does only make sense in combination with 'segmentation' activated.")
131
-
132
- noRawTimeSteps=len(self.base_aggregation.timeSeries.index)
133
-
134
- _maxPeriods = int(float(noRawTimeSteps)/self.base_aggregation.timeStepsPerPeriod)
135
- _maxSegments = self.base_aggregation.timeStepsPerPeriod
136
-
137
- # save RMSE
138
- RMSE_history = []
139
-
140
- # correct 0 index of python
141
- possibleSegments = np.arange(_maxSegments)+1
142
- possiblePeriods = np.arange(_maxPeriods)+1
143
-
144
- # number of time steps of all combinations of segments and periods
145
- combinedTimeSteps = np.outer(possibleSegments, possiblePeriods)
146
- # reduce to valid combinations for targeted data reduction
147
- reductionValidCombinations = combinedTimeSteps <= noRawTimeSteps * dataReduction
148
-
149
- # number of time steps for all feasible combinations
150
- reductionValidTimsteps = combinedTimeSteps * reductionValidCombinations
151
-
152
- # identify max segments and max period combination
153
- optimalPeriods = np.zeros_like(reductionValidTimsteps)
154
- optimalPeriods[np.arange(reductionValidTimsteps.shape[0]), reductionValidTimsteps.argmax(axis=1)] = 1
155
- optimalSegments = np.zeros_like(reductionValidTimsteps)
156
- optimalSegments[reductionValidTimsteps.argmax(axis=0), np.arange(reductionValidTimsteps.shape[1])] = 1
157
-
158
- optimalIndexCombo = np.nonzero(optimalPeriods*optimalSegments)
159
-
160
-
161
- for segmentIx, periodIx in tqdm.tqdm(zip(optimalIndexCombo[0],optimalIndexCombo[1])):
162
-
163
- # derive new typical periods and derive rmse
164
- RMSE_history.append(self._testAggregation(possiblePeriods[periodIx], possibleSegments[segmentIx]))
165
-
166
- # take the negative backwards index with the minimal RMSE
167
- min_index = - list(reversed(RMSE_history)).index(min(RMSE_history)) - 1
168
- RMSE_min = RMSE_history[min_index]
169
-
170
-
171
- noTypicalPeriods=self._periodHistory[min_index]
172
- noSegments=self._segmentHistory[min_index]
173
-
174
- # and return the segment and typical period pair
175
- return noSegments, noTypicalPeriods, RMSE_min
176
-
177
-
178
- def identifyParetoOptimalAggregation(self, untilTotalTimeSteps=None):
179
- """
180
- Identifies the pareto-optimal combination of number of typical periods and number of segments along with a steepest decent approach, starting from the aggregation to a single period and a single segment up to the representation of the full time series.
181
-
182
- :param untilTotalTimeSteps: Number of timesteps until which the pareto-front should be determined. If None, the maximum number of timesteps is chosen.
183
- :type untilTotalTimeSteps: int
184
-
185
-
186
- :returns: **** -- Nothing. Check aggregation history for results. All typical Periods in scaled form.
187
- """
188
- if not self.base_aggregation.segmentation:
189
- raise ValueError("This function does only make sense in combination with 'segmentation' activated.")
190
-
191
- noRawTimeSteps=len(self.base_aggregation.timeSeries.index)
192
-
193
- _maxPeriods = int(float(noRawTimeSteps)/self.base_aggregation.timeStepsPerPeriod)
194
- _maxSegments = self.base_aggregation.timeStepsPerPeriod
195
-
196
- if untilTotalTimeSteps is None:
197
- untilTotalTimeSteps=noRawTimeSteps
198
-
199
-
200
- progressBar = tqdm.tqdm(total=untilTotalTimeSteps)
201
-
202
- # starting point
203
- noTypicalPeriods=1
204
- noSegments=1
205
- _RMSE_0=self._testAggregation(noTypicalPeriods, noSegments)
206
-
207
- # loop until either segments or periods have reached their maximum
208
- while (noTypicalPeriods<_maxPeriods and noSegments<_maxSegments
209
- and (noSegments+1)*noTypicalPeriods<=untilTotalTimeSteps
210
- and noSegments*(noTypicalPeriods+1)<=untilTotalTimeSteps):
211
- # test for more segments
212
- RMSE_segments = self._testAggregation(noTypicalPeriods, noSegments+1)
213
- # test for more periods
214
- RMSE_periods = self._testAggregation(noTypicalPeriods+1, noSegments)
215
-
216
- # RMSE old
217
- RMSE_old = self._RMSEHistory[-3]
218
-
219
- # segment gradient (RMSE improvement per increased time step number)
220
- # for segments: for each period on segment added
221
- RMSE_segment_gradient = (RMSE_old - RMSE_segments) / noTypicalPeriods
222
- # for periods: one period with no of segments
223
- RMSE_periods_gradient = (RMSE_old - RMSE_periods) / noSegments
224
-
225
- # go along the steeper gradient
226
- if RMSE_periods_gradient>RMSE_segment_gradient:
227
- noTypicalPeriods+=1
228
- # and delete the search direction which was not persued
229
- self._deleteTestHistory(-2)
230
- else:
231
- noSegments+=1
232
- self._deleteTestHistory(-1)
233
- progressBar.update(noSegments*noTypicalPeriods-progressBar.n)
234
-
235
- # afterwards loop over periods and segments exclusively until maximum is reached
236
- while noTypicalPeriods<_maxPeriods and noSegments*(noTypicalPeriods+1)<=untilTotalTimeSteps:
237
- noTypicalPeriods+=1
238
- RMSE = self._testAggregation(noTypicalPeriods, noSegments)
239
- progressBar.update(noSegments*noTypicalPeriods-progressBar.n)
240
-
241
- while noSegments<_maxSegments and (noSegments+1)*noTypicalPeriods<=untilTotalTimeSteps:
242
- noSegments+=1
243
- RMSE = self._testAggregation(noTypicalPeriods, noSegments)
244
- progressBar.update(noSegments*noTypicalPeriods-progressBar.n)
245
- return
1
+ import copy
2
+ import warnings
3
+
4
+ import numpy as np
5
+ import tqdm
6
+
7
+ from tsam.exceptions import LegacyAPIWarning
8
+ from tsam.timeseriesaggregation import TimeSeriesAggregation
9
+
10
+
11
+ def getNoPeriodsForDataReduction(noRawTimeSteps, segmentsPerPeriod, dataReduction):
12
+ """
13
+ Identifies the maximum number of periods which can be set to achieve the required data reduction.
14
+
15
+ :param noRawTimeSteps: Number of original time steps. required
16
+ :type noRawTimeSteps: int
17
+
18
+ :param segmentsPerPeriod: Segments per period. required
19
+ :type segmentsPerPeriod: int
20
+
21
+ :param dataReduction: Factor by which the resulting dataset should be reduced. required
22
+ :type dataReduction: float
23
+
24
+ :returns: **noTypicalPeriods** -- Number of typical periods that can be set.
25
+
26
+ .. deprecated::
27
+ This function is deprecated along with the HyperTunedAggregations class.
28
+ """
29
+ warnings.warn(
30
+ "getNoPeriodsForDataReduction is deprecated along with HyperTunedAggregations.",
31
+ LegacyAPIWarning,
32
+ stacklevel=2,
33
+ )
34
+ return int(np.floor(dataReduction * float(noRawTimeSteps) / segmentsPerPeriod))
35
+
36
+
37
+ def getNoSegmentsForDataReduction(noRawTimeSteps, typicalPeriods, dataReduction):
38
+ """
39
+ Identifies the maximum number of segments which can be set to achieve the required data reduction.
40
+
41
+ :param noRawTimeSteps: Number of original time steps. required
42
+ :type noRawTimeSteps: int
43
+
44
+ :param typicalPeriods: Number of typical periods. required
45
+ :type typicalPeriods: int
46
+
47
+ :param dataReduction: Factor by which the resulting dataset should be reduced. required
48
+ :type dataReduction: float
49
+
50
+ :returns: **segmentsPerPeriod** -- Number of segments per period that can be set.
51
+
52
+ .. deprecated::
53
+ This function is deprecated along with the HyperTunedAggregations class.
54
+ """
55
+ warnings.warn(
56
+ "getNoSegmentsForDataReduction is deprecated along with HyperTunedAggregations.",
57
+ LegacyAPIWarning,
58
+ stacklevel=2,
59
+ )
60
+ return int(np.floor(dataReduction * float(noRawTimeSteps) / typicalPeriods))
61
+
62
+
63
+ class HyperTunedAggregations:
64
+ def __init__(self, base_aggregation, saveAggregationHistory=True):
65
+ """
66
+ A class that does a parameter variation and tuning of the aggregation itself.
67
+
68
+ :param base_aggregation: TimeSeriesAggregation object which is used as basis for tuning the hyper parameters. required
69
+ :type base_aggregation: TimeSeriesAggregation
70
+
71
+ :param saveAggregationHistory: Defines if all aggregations that are created during the tuning and iterations shall be saved under self.aggregationHistory.
72
+ :type saveAggregationHistory: boolean
73
+
74
+ .. deprecated::
75
+ Use :func:`tsam.tuning.find_optimal_combination` or
76
+ :func:`tsam.tuning.find_pareto_front` instead.
77
+ """
78
+ warnings.warn(
79
+ "HyperTunedAggregations is deprecated. "
80
+ "Use tsam.tuning.find_optimal_combination() or tsam.tuning.find_pareto_front() instead.",
81
+ LegacyAPIWarning,
82
+ stacklevel=2,
83
+ )
84
+ self.base_aggregation = base_aggregation
85
+
86
+ if not isinstance(self.base_aggregation, TimeSeriesAggregation):
87
+ raise ValueError(
88
+ "base_aggregation has to be an TimeSeriesAggregation object"
89
+ )
90
+
91
+ self._alterableAggregation = copy.deepcopy(self.base_aggregation)
92
+
93
+ self.saveAggregationHistory = saveAggregationHistory
94
+
95
+ self._segmentHistory = []
96
+
97
+ self._periodHistory = []
98
+
99
+ self._RMSEHistory = []
100
+
101
+ if self.saveAggregationHistory:
102
+ self.aggregationHistory = []
103
+
104
+ def _testAggregation(self, noTypicalPeriods, noSegments):
105
+ """
106
+ Tests the aggregation for a set of typical periods and segments and returns the RMSE
107
+ """
108
+ self._segmentHistory.append(noSegments)
109
+
110
+ self._periodHistory.append(noTypicalPeriods)
111
+
112
+ self._alterableAggregation.noTypicalPeriods = noTypicalPeriods
113
+
114
+ self._alterableAggregation.noSegments = noSegments
115
+
116
+ self._alterableAggregation.createTypicalPeriods()
117
+
118
+ self._alterableAggregation.predictOriginalData()
119
+
120
+ RMSE = self._alterableAggregation.totalAccuracyIndicators()["RMSE"]
121
+
122
+ self._RMSEHistory.append(RMSE)
123
+
124
+ if self.saveAggregationHistory:
125
+ self.aggregationHistory.append(copy.copy(self._alterableAggregation))
126
+
127
+ return RMSE
128
+
129
+ def _deleteTestHistory(self, index):
130
+ """
131
+ Delelets the defined index from the test history
132
+ """
133
+ del self._segmentHistory[index]
134
+ del self._periodHistory[index]
135
+ del self._RMSEHistory[index]
136
+
137
+ if self.saveAggregationHistory:
138
+ del self.aggregationHistory[index]
139
+
140
+ def identifyOptimalSegmentPeriodCombination(self, dataReduction):
141
+ """
142
+ Identifies the optimal combination of number of typical periods and number of segments for a given data reduction set.
143
+
144
+ :param dataReduction: Factor by which the resulting dataset should be reduced. required
145
+ :type dataReduction: float
146
+
147
+ :returns: **noSegments, noTypicalperiods** -- The optimal combination of segments and typical periods for the given optimization set.
148
+ """
149
+ if not self.base_aggregation.segmentation:
150
+ raise ValueError(
151
+ "This function does only make sense in combination with 'segmentation' activated."
152
+ )
153
+
154
+ noRawTimeSteps = len(self.base_aggregation.timeSeries.index)
155
+
156
+ _maxPeriods = int(
157
+ float(noRawTimeSteps) / self.base_aggregation.timeStepsPerPeriod
158
+ )
159
+ _maxSegments = self.base_aggregation.timeStepsPerPeriod
160
+
161
+ # save RMSE
162
+ RMSE_history = []
163
+
164
+ # correct 0 index of python
165
+ possibleSegments = np.arange(_maxSegments) + 1
166
+ possiblePeriods = np.arange(_maxPeriods) + 1
167
+
168
+ # number of time steps of all combinations of segments and periods
169
+ combinedTimeSteps = np.outer(possibleSegments, possiblePeriods)
170
+ # reduce to valid combinations for targeted data reduction
171
+ reductionValidCombinations = combinedTimeSteps <= noRawTimeSteps * dataReduction
172
+
173
+ # number of time steps for all feasible combinations
174
+ reductionValidTimsteps = combinedTimeSteps * reductionValidCombinations
175
+
176
+ # identify max segments and max period combination
177
+ optimalPeriods = np.zeros_like(reductionValidTimsteps)
178
+ optimalPeriods[
179
+ np.arange(reductionValidTimsteps.shape[0]),
180
+ reductionValidTimsteps.argmax(axis=1),
181
+ ] = 1
182
+ optimalSegments = np.zeros_like(reductionValidTimsteps)
183
+ optimalSegments[
184
+ reductionValidTimsteps.argmax(axis=0),
185
+ np.arange(reductionValidTimsteps.shape[1]),
186
+ ] = 1
187
+
188
+ optimalIndexCombo = np.nonzero(optimalPeriods * optimalSegments)
189
+
190
+ for segmentIx, periodIx in tqdm.tqdm(
191
+ zip(optimalIndexCombo[0], optimalIndexCombo[1])
192
+ ):
193
+ # derive new typical periods and derive rmse
194
+ RMSE_history.append(
195
+ self._testAggregation(
196
+ possiblePeriods[periodIx], possibleSegments[segmentIx]
197
+ )
198
+ )
199
+
200
+ # take the negative backwards index with the minimal RMSE
201
+ min_index = -list(reversed(RMSE_history)).index(min(RMSE_history)) - 1
202
+ RMSE_min = RMSE_history[min_index]
203
+
204
+ noTypicalPeriods = self._periodHistory[min_index]
205
+ noSegments = self._segmentHistory[min_index]
206
+
207
+ # and return the segment and typical period pair
208
+ return noSegments, noTypicalPeriods, RMSE_min
209
+
210
+ def identifyParetoOptimalAggregation(self, untilTotalTimeSteps=None):
211
+ """
212
+ Identifies the pareto-optimal combination of number of typical periods and number of segments along with a steepest decent approach, starting from the aggregation to a single period and a single segment up to the representation of the full time series.
213
+
214
+ :param untilTotalTimeSteps: Number of timesteps until which the pareto-front should be determined. If None, the maximum number of timesteps is chosen.
215
+ :type untilTotalTimeSteps: int
216
+
217
+
218
+ :returns: None. Check aggregation history for results. All typical Periods in scaled form.
219
+ """
220
+ if not self.base_aggregation.segmentation:
221
+ raise ValueError(
222
+ "This function does only make sense in combination with 'segmentation' activated."
223
+ )
224
+
225
+ noRawTimeSteps = len(self.base_aggregation.timeSeries.index)
226
+
227
+ _maxPeriods = int(
228
+ float(noRawTimeSteps) / self.base_aggregation.timeStepsPerPeriod
229
+ )
230
+ _maxSegments = self.base_aggregation.timeStepsPerPeriod
231
+
232
+ if untilTotalTimeSteps is None:
233
+ untilTotalTimeSteps = noRawTimeSteps
234
+
235
+ progressBar = tqdm.tqdm(total=untilTotalTimeSteps)
236
+
237
+ # starting point
238
+ noTypicalPeriods = 1
239
+ noSegments = 1
240
+ _RMSE_0 = self._testAggregation(noTypicalPeriods, noSegments)
241
+
242
+ # loop until either segments or periods have reached their maximum
243
+ while (
244
+ noTypicalPeriods < _maxPeriods
245
+ and noSegments < _maxSegments
246
+ and (noSegments + 1) * noTypicalPeriods <= untilTotalTimeSteps
247
+ and noSegments * (noTypicalPeriods + 1) <= untilTotalTimeSteps
248
+ ):
249
+ # test for more segments
250
+ RMSE_segments = self._testAggregation(noTypicalPeriods, noSegments + 1)
251
+ # test for more periods
252
+ RMSE_periods = self._testAggregation(noTypicalPeriods + 1, noSegments)
253
+
254
+ # RMSE old
255
+ RMSE_old = self._RMSEHistory[-3]
256
+
257
+ # segment gradient (RMSE improvement per increased time step number)
258
+ # for segments: for each period on segment added
259
+ RMSE_segment_gradient = (RMSE_old - RMSE_segments) / noTypicalPeriods
260
+ # for periods: one period with no of segments
261
+ RMSE_periods_gradient = (RMSE_old - RMSE_periods) / noSegments
262
+
263
+ # go along the steeper gradient
264
+ if RMSE_periods_gradient > RMSE_segment_gradient:
265
+ noTypicalPeriods += 1
266
+ # and delete the search direction which was not persued
267
+ self._deleteTestHistory(-2)
268
+ else:
269
+ noSegments += 1
270
+ self._deleteTestHistory(-1)
271
+ progressBar.update(noSegments * noTypicalPeriods - progressBar.n)
272
+
273
+ # afterwards loop over periods and segments exclusively until maximum is reached
274
+ while (
275
+ noTypicalPeriods < _maxPeriods
276
+ and noSegments * (noTypicalPeriods + 1) <= untilTotalTimeSteps
277
+ ):
278
+ noTypicalPeriods += 1
279
+ self._testAggregation(noTypicalPeriods, noSegments)
280
+ progressBar.update(noSegments * noTypicalPeriods - progressBar.n)
281
+
282
+ while (
283
+ noSegments < _maxSegments
284
+ and (noSegments + 1) * noTypicalPeriods <= untilTotalTimeSteps
285
+ ):
286
+ noSegments += 1
287
+ self._testAggregation(noTypicalPeriods, noSegments)
288
+ progressBar.update(noSegments * noTypicalPeriods - progressBar.n)
289
+ return