diff --git a/dataprocessing/calc.py b/dataprocessing/calc.py deleted file mode 100644 index 29d89b805cd218849b24c232b3069bafff03371a..0000000000000000000000000000000000000000 --- a/dataprocessing/calc.py +++ /dev/null @@ -1,25 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -from .timeseries import * - -def diff(name, ts1, ts2): - """ Calculate difference. - Assumes the same time steps for both timeseries. - """ - ts_diff = TimeSeries(name, ts1.time, (ts1.values - ts2.values)) - return ts_diff - -def scale_ts(name, ts, factor): - """ Scale timeseries. - Assumes the same time steps for both timeseries. - """ - ts_scaled = TimeSeries(name, ts.time, ts.values * factor) - return ts_scaled - -def complex_abs(name, real, imag): - """ Calculate absolute value of complex variable. - Assumes the same time steps for both timeseries. - """ - ts_abs = TimeSeries(name, real.time, np.sqrt(real.values ** 2 + imag.values ** 2)) - return ts_abs - diff --git a/dataprocessing/plotdpsim.py b/dataprocessing/plotdpsim.py index aa57aaf358190ee3658beec9ff46cce2b875f49b..a438b54450822198047daa85f2888b6013380525 100644 --- a/dataprocessing/plotdpsim.py +++ b/dataprocessing/plotdpsim.py @@ -6,8 +6,8 @@ import matplotlib.pyplot as plt matplotlib.rcParams.update({'font.size': 8}) def plot_dpsim_abs_diff(filename1, label1, node1, filename2, label2, node2): - ts_dpsim1 = read_time_series_DPsim(filename1) - ts_dpsim2 = read_time_series_DPsim(filename2) + ts_dpsim1 = read_timeseries_DPsim(filename1) + ts_dpsim2 = read_timeseries_DPsim(filename2) ts_dpsim1_length = len(ts_dpsim1) im_offset1 = int(ts_dpsim1_length / 2) @@ -44,8 +44,8 @@ def plot_dpsim_abs_diff(filename1, label1, node1, filename2, label2, node2): plt.show() def plot_dpsim_abs(filename1, label1, node1, filename2, label2, node2): - ts_dpsim1 = read_time_series_DPsim(filename1) - ts_dpsim2 = read_time_series_DPsim(filename2) + ts_dpsim1 = read_timeseries_DPsim(filename1) + ts_dpsim2 = read_timeseries_DPsim(filename2) ts_dpsim1_length = len(ts_dpsim1) im_offset1 = int(ts_dpsim1_length / 2) @@ -79,8 +79,47 @@ def plot_dpsim_abs(filename1, label1, node1, filename2, label2, node2): plt.show() +def plot_dpsim_emt_abs(filenameDP, nodeDP, filenameEMT, nodeEMT): + ts_dpsimDP = read_timeseries_DPsim(filenameDP) + ts_dpsimEMT = read_timeseries_DPsim(filenameEMT) + + ts_dpsimDP_length = len(ts_dpsimDP) + im_offsetDP = int(ts_dpsimDP_length / 2) + if im_offsetDP <= nodeDP or nodeDP < 0: + print('Node DP not available') + exit() + + ts_dpsimEMT_length = len(ts_dpsimEMT) + if ts_dpsimEMT_length <= nodeEMT or nodeEMT < 0: + print('Node EMT not available') + exit() + + ts_absDP = complex_abs('node ' + str(nodeDP) + 'abs', ts_dpsimDP[nodeDP], ts_dpsimDP[nodeDP + im_offsetDP]) + ts_absDP = scale_ts(ts_absDP.name, ts_absDP, 0.001) + ts_absDP.label = 'DP abs' + + ts_shiftDP = dyn_phasor_shift_to_emt('node ' + str(nodeDP) + 'shift', ts_dpsimDP[nodeDP], ts_dpsimDP[nodeDP + im_offsetDP], 50) + ts_shiftDP = scale_ts(ts_shiftDP.name, ts_shiftDP, 0.001) + ts_shiftDP.label = 'DP shift' + + ts_EMT = TimeSeries('node ' + str(nodeEMT), ts_dpsimEMT[nodeEMT].time, ts_dpsimEMT[nodeEMT].values) + ts_EMT = scale_ts(ts_EMT.name, ts_EMT, 0.001) + ts_EMT.label = 'EMT' + + figure_id = 1 + # plt.figure(figure_id) + plt.figure(figure_id, figsize=(12 / 2.54, 6 / 2.54), facecolor='w', edgecolor='k') + plot_timeseries(figure_id, ts_EMT) + plot_timeseries(figure_id, ts_absDP) + plot_timeseries(figure_id, ts_shiftDP) + plt.xlabel('Time [s]') + plt.ylabel('Voltage [kV]') + plt.grid(True) + plt.tight_layout() + plt.show() + def plot_dpsim_abs_single(filename, node): - ts_dpsim = read_time_series_DPsim(filename) + ts_dpsim = read_timeseries_DPsim(filename) ts_dpsim_length = len(ts_dpsim) print('DPsim results file length:') diff --git a/dataprocessing/plottools.py b/dataprocessing/plottools.py index 3bc716673a906ab48135c03426bc70a077f440a9..5311d174a5116bae66ced21b7acbd86dd950c620 100644 --- a/dataprocessing/plottools.py +++ b/dataprocessing/plottools.py @@ -16,7 +16,7 @@ def plot_timeseries(figure_id, timeseries, plt_linestyle='-', plt_linewidth=2, p else: plt.plot(timeseries.time, timeseries.values, linestyle=plt_linestyle, label=timeseries.label, linewidth=plt_linewidth) plt.gca().autoscale(axis='x', tight=True) - plt.legend() + plt.legend(loc='lower right') else: for ts in timeseries: plt.subplot(len(timeseries), 1, timeseries.index(ts) + 1) diff --git a/dataprocessing/readtools.py b/dataprocessing/readtools.py index 7ea17f81dea18ede7b4e0e6d1d791babb8624c6f..51376b9b618db8574c44dae58042003ab1bcfcd5 100644 --- a/dataprocessing/readtools.py +++ b/dataprocessing/readtools.py @@ -35,16 +35,38 @@ def read_timeseries_PLECS(filename, timeseries_names=None): timeseries_list.append(TimeSeries(name, pd_df['Time'].values, pd_df[name].values)) return timeseries_list -def read_timeseries_DPsim(filename, timeseries_names=None): - pd_df = pd.read_csv(filename) +def read_timeseries_dpsim_real(filename, header=None, timeseries_names=None): + """Reads real time series data from DPsim log file which may have a header. + Timeseries names are assigned according to the header names if available. + :param filename: name of the csv file that has the data + :param header: specifies if the log file has a header + :param timeseries_names: column names which should be read + :return: list of Timeseries objects + """ timeseries_list = [] + if header is True: + pd_df = pd.read_csv(filename) + else: + pd_df = pd.read_csv(filename, header=None) + if timeseries_names is None: # No trajectory names specified, thus read in all - timeseries_names = list(pd_df.columns.values) - timeseries_names.remove('Time') - for name in timeseries_names: - timeseries_list.append(TimeSeries(name, pd_df['Time'].values, pd_df[name].values)) + column_names = list(pd_df.columns.values) + # Remove timestamps column name and store separately + column_names.remove(0) + timestamps = pd_df.iloc[:,0] + + if header is True: + for name in column_names: + timeseries_list.append(TimeSeries(name, timestamps, pd_df[name].values)) + else: + node_number = int(len(column_names)) + node_index = 1 + for column in column_names: + ts_name = 'node ' + str(node_index) + timeseries_list.append(TimeSeries(ts_name, timestamps, pd_df.iloc[:, column])) + node_index = node_index + 1 else: # Read in specified time series print('no column names specified yet') @@ -55,23 +77,68 @@ def read_timeseries_DPsim(filename, timeseries_names=None): print(result.name) return timeseries_list -def read_timeseries_DPsim_node_values(filename, timeseries_names=None): +def read_timeseries_dpsim_cmpl(filename, timeseries_names=None): + """Reads complex time series data from DPsim log file. Real and + imaginary part are stored in one complex variable. + :param filename: name of the csv file that has the data + :param timeseries_names: column name which should be read + :return: list of Timeseries objects + """ pd_df = pd.read_csv(filename, header=None) timeseries_list = [] if timeseries_names is None: # No trajectory names specified, thus read in all column_names = list(pd_df.columns.values) + # Remove timestamps column name and store separately column_names.remove(0) + timestamps = pd_df.iloc[:,0] + # Calculate number of network nodes since array is [real, imag] + node_number = int(len(column_names) / 2) node_index = 1 + for column in column_names: + if node_index <= node_number: + ts_name = 'node '+ str(node_index) + timeseries_list.append(TimeSeries(ts_name, timestamps, np.vectorize(complex)(pd_df.iloc[:,column],pd_df.iloc[:,column + node_number]))) + else: + break + node_index = node_index + 1 + else: + # Read in specified time series + print('cannot read specified columns yet') + + print('DPsim results file length:') + print(len(timeseries_list)) + for result in timeseries_list: + print(result.name) + return timeseries_list + +def read_timeseries_dpsim_cmpl_separate(filename, timeseries_names=None): + """Deprecated - Reads complex time series data from DPsim log file. Real and + imaginary part are stored separately. + :param filename: name of the csv file that has the data + :param timeseries_names: column name which should be read + :return: list of Timeseries objects + """ + pd_df = pd.read_csv(filename, header=None) + timeseries_list = [] + + if timeseries_names is None: + # No trajectory names specified, thus read in all + column_names = list(pd_df.columns.values) + # Remove timestamps column name and store separately + column_names.remove(0) + timestamps = pd_df.iloc[:, 0] + # Calculate number of network nodes since array is [real, imag] node_number = int(len(column_names) / 2) + node_index = 1 for column in column_names: if node_index <= node_number: - node_name = node_index - timeseries_list.append(TimeSeries('node '+ str(node_name) +' Re', pd_df.iloc[:,0], pd_df.iloc[:,column])) + node_name = 'node '+ str(node_index) +' Re' + timeseries_list.append(TimeSeries(node_name, timestamps, pd_df.iloc[:,column])) else: - node_name = node_index - node_number - timeseries_list.append(TimeSeries('node '+ str(node_name) +' Im', pd_df.iloc[:,0], pd_df.iloc[:,column])) + node_name = 'node '+ str(node_index - node_number) +' Im' + timeseries_list.append(TimeSeries(node_name, timestamps, pd_df.iloc[:,column])) node_index = node_index + 1 else: diff --git a/dataprocessing/timeseries.py b/dataprocessing/timeseries.py index 0bdea8a7d417b3b98d521e65d2e9a51780d6d9b6..358c4390b56cab05afc7770592bca64ba85f458f 100644 --- a/dataprocessing/timeseries.py +++ b/dataprocessing/timeseries.py @@ -1,8 +1,98 @@ import numpy as np class TimeSeries: + """Stores data from different simulation sources. + A TimeSeries object always consists of timestamps and datapoints. + """ def __init__(self, name, time, values, label=""): self.time = np.array(time) self.values = np.array(values) self.name = name - self.label = name \ No newline at end of file + self.label = name + + @staticmethod + def diff(name, ts1, ts2): + """Returns difference between values of two Timeseries objects. + Assumes the same time steps for both timeseries. + """ + ts_diff = TimeSeries(name, ts1.time, (ts1.values - ts2.values)) + return ts_diff + + + def scale_ts(self, name, factor): + """Returns scaled timeseries. + Assumes the same time steps for both timeseries. + """ + ts_scaled = TimeSeries(name, self.time, self.values * factor) + return ts_scaled + + @staticmethod + def complex_abs_dep(name, ts_real, ts_imag): + """ Calculate absolute value of complex variable. + Assumes the same time steps for both timeseries. + """ + ts_abs = TimeSeries(name, ts_real.time, np.sqrt(ts_real.values ** 2 + ts_imag.values ** 2)) + return ts_abs + + @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) + ts_abs = TimeSeries(name, ts_real.time, ts_complex.abs()) + return ts_abs + + def abs(self, name): + """ Calculate absolute value of complex variable. + Assumes the same time steps for both timeseries. + """ + ts_abs = TimeSeries(name, self.time, self.values.abs()) + return ts_abs + + def complex_phase(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) + ts_abs = TimeSeries(name, ts_real.time, ts_complex.phase()) + return ts_abs + + @staticmethod + def dyn_phasor_shift_to_emt(name, real, imag, freq): + """ Shift dynamic phasor values to EMT by frequency freq. + Assumes the same time steps for both timeseries. + """ + ts_shift = TimeSeries(name, real.time, real.values*np.cos(2*np.pi*freq*real.time) - imag.values*np.sin(2*np.pi*freq*real.time)) + return ts_shift + + @staticmethod + def check_node_number_comp(ts_comp, node): + """ + Check if node number is available in complex time series. + :param ts_comp: complex time series + :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 + def check_node_number(ts, node): + """ + Check if node number is available in time series. + :param ts: time series + :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: + return true \ No newline at end of file