timeseries.py 6.82 KB
Newer Older
1
import numpy as np
Markus Mirz's avatar
Markus Mirz committed
2
import cmath
3 4

class TimeSeries:
5 6 7
    """Stores data from different simulation sources.
    A TimeSeries object always consists of timestamps and datapoints.
    """
8 9 10 11
    def __init__(self, name, time, values, label=""):
        self.time = np.array(time)
        self.values = np.array(values)
        self.name = name
12 13
        self.label = name

14
    def scale(self, name, factor):
15 16 17 18 19 20 21
        """Returns scaled timeseries.
        Assumes the same time steps for both timeseries.
        """
        ts_scaled = TimeSeries(name, self.time, self.values * factor)
        return ts_scaled

    def abs(self, name):
Markus Mirz's avatar
Markus Mirz committed
22
        """ Calculate absolute value of complex time series.
23
        """
Markus Mirz's avatar
Markus Mirz committed
24 25 26 27
        abs_values = []
        for value in self.values:
            abs_values.append(np.abs(value))
        ts_abs = TimeSeries(name, self.time, abs_values)
28 29
        return ts_abs

Markus Mirz's avatar
Markus Mirz committed
30 31
    def phase(self, name):
        """ Calculate absolute value of complex time series.
32
        """
Markus Mirz's avatar
Markus Mirz committed
33 34 35 36 37 38
        phase_values = []
        for value in self.values:
            phase_values.append(np.angle(value, deg=True))
        ts_abs = TimeSeries(name, self.time, phase_values)
        ts_phase = TimeSeries(name, self.time, phase_values)
        return ts_phase
39

40 41 42
    def phasor(self, name):
        """Calculate phasor of complex time series and return dict with abs and phase.
        """
43 44
        ts_abs = self.abs(self.name + '_abs')
        ts_phase = self.phase(self.name + '_phase')
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
        ts_phasor = {}
        ts_phasor['abs'] = ts_abs
        ts_phasor['phase'] = ts_phase
        
        return ts_phasor

    @staticmethod
    def frequency_shift_list(timeseries_list, freq):
        """Calculate shifted frequency results of all time series
        :param timeseries_list: timeseries list retrieved from dpsim results
        :param freq: frequency by which the timeseries should be shifted
        :return: list of shifted time series
        """
        result_list = {}
        for name, ts in timeseries_list.items():
            ts_emt = ts.frequency_shift(ts.name, freq)
            result_list[ts.name] = ts_emt

        return result_list

65 66 67 68 69 70
    @staticmethod
    def rmse(ts1, ts2):
        """ Calculate root mean square error between two time series
        """
        return np.sqrt((TimeSeries.diff('diff', ts1, ts2).values ** 2).mean())

Jan Dinkelbach's avatar
Jan Dinkelbach committed
71 72 73 74 75 76 77 78 79 80 81 82 83
    @staticmethod
    def norm_rmse(ts1, ts2):
        """ Calculate root mean square error between two time series,
        normalized using the mean value of both mean values of ts1 and ts2 
        """
        if np.mean(np.array(ts1.values.mean(),ts2.values.mean())) != 0:
          nrmse = np.sqrt((TimeSeries.diff('diff', ts1, ts2).values ** 2).mean())/np.mean(np.array(ts1.values.mean(),ts2.values.mean()))
          is_norm = True
        else:
          nrmse = np.sqrt((TimeSeries.diff('diff', ts1, ts2).values ** 2).mean())
          is_norm = False
        return (nrmse,is_norm)

84
    @staticmethod
85 86 87
    def diff(name, ts1, ts2):
        """Returns difference between values of two Timeseries objects.
        """
Steffen Vogel's avatar
Steffen Vogel committed
88
        if len(ts1.time) == len(ts2.time):
89 90 91 92 93 94 95 96
            ts_diff = TimeSeries(name, ts1.time, (ts1.values - ts2.values))
        else:  # different timestamps, common time vector and interpolation required before substraction
            time = sorted(set(list(ts1.time) + list(ts2.time)))
            interp_vals_ts1 = np.interp(time, ts1.time, ts1.values)
            interp_vals_ts2 = np.interp(time, ts2.time, ts2.values)
            ts_diff = TimeSeries(name, time, (interp_vals_ts2 - interp_vals_ts1))
        return ts_diff

97
    def frequency_shift(self, name, freq):
98 99 100 101 102 103 104 105 106 107
        """ Shift dynamic phasor values to EMT by frequency freq.
            Assumes the same time steps for both timeseries.
        :param name: name of returned time series
        :param freq: shift frequency
        :return: new timeseries with shifted time domain values
        """
        ts_shift = TimeSeries(name, self.time, self.values.real*np.cos(2*np.pi*freq*self.time)
                              - self.values.imag*np.sin(2*np.pi*freq*self.time))
        return ts_shift

108 109 110 111 112 113 114 115 116 117 118 119
    def calc_freq_spectrum(self):
        """ Calculates frequency spectrum of the time series using FFT
        :param name: name of returned time series
        :param freq: shift frequency
        :return: new timeseries with shifted time domain values
        """
        Ts = self.time[1]-self.time[0]
        fft_values = np.fft.fft(self.values)
        freqs_num = int(len(fft_values)/2)
        fft_freqs = np.fft.fftfreq(len(fft_values),d=Ts)
        return fft_freqs[:freqs_num], np.abs(fft_values[:freqs_num])/freqs_num

120 121 122 123 124 125 126 127 128 129 130 131
    def interpolate_cmpl(self, name, timestep):
        """ Not tested yet!
        Interpolates complex timeseries with timestep
        :param name:
        :param timestep:
        :return:
        """
        interpl_time = np.arange(self.time[0], self.time[-1], timestep)
        realValues = interp1d(interpl_time, self.values.real)
        imagValues = interp1d(interpl_time, self.values.imag)
        ts_return = TimeSeries(name, time, np.vectorize(complex)(realValues, imagValues))
        return timeseries
132
    
133
    @staticmethod
134
    def check_node_number_comp(ts_list_comp, node):
135 136
        """
        Check if node number is available in complex time series.
137
        :param ts_comp: complex time series list
138 139 140 141 142 143 144 145 146 147 148 149
        :param node: node number to be checked
        :return: true if node number is available, false if out of range
        """
        ts_comp_length = len(ts_comp)
        im_offset = int(ts_comp_length / 2)
        if im_offset <= node or node < 0:
            print('Complex node not available')
            return false
        else:
            return true

    @staticmethod
150
    def check_node_number(ts_list, node):
151 152
        """
        Check if node number is available in time series.
153
        :param ts: time series list
154 155 156 157 158 159 160 161
        :param node: node number to be checked
        :return: true if node number is available, false if out of range
        """
        ts_length = len(ts)
        if ts_length <= node or node < 0:
            print('Node not available')
            return false
        else:
162 163 164 165 166 167 168 169
            return true

    @staticmethod
    def complex_abs(name, ts_real, ts_imag):
        """ Calculate absolute value of complex variable.
        Assumes the same time steps for both timeseries.
        """
        ts_complex = np.vectorize(complex)(ts_real.values, ts_imag.values)
Jan Dinkelbach's avatar
Jan Dinkelbach committed
170
        ts_abs = TimeSeries(name, ts_real.time, np.absolute(ts_complex))
171 172 173 174 175 176 177 178 179 180 181 182 183
        return ts_abs

    @staticmethod
    def phasors(timeseries_list):
        """Calculate voltage phasors of all nodes
        :param timeseries_list: timeseries list with real and imaginary parts
        :return: timeseries list with abs and phase
        """
        phasor_list = {}
        for name, ts in timeseries_list.items():
            phasor_list[name] = ts.phasor(name)

        return phasor_list