diff --git a/dataprocessing/calc.py b/dataprocessing/calc.py
deleted file mode 100644
index 582b3cb526aec7dfd64a9afe13c60f99a83b8c5d..0000000000000000000000000000000000000000
--- a/dataprocessing/calc.py
+++ /dev/null
@@ -1,60 +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
-
-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
-
-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
-
-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
diff --git a/dataprocessing/readtools.py b/dataprocessing/readtools.py
index 6bd5a055f9970ff7f524794967e59892bc88ef25..50816497c6e6a21885d2205c08d45658ddfe0960 100644
--- a/dataprocessing/readtools.py
+++ b/dataprocessing/readtools.py
@@ -31,16 +31,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')
@@ -51,23 +73,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