Repository: zincware/MDSuite (language: python)

Issue:
do not repeat calculations
If the result exists (and no new data is there, and no parameters changed and not ``force``), do not do the calculation if called a second time


Discussion / hints:

Fix patch:
diff --git a/mdsuite/calculators/angular_distribution_function.py b/mdsuite/calculators/angular_distribution_function.py
index 48a654c5..9dee8edf 100644
--- a/mdsuite/calculators/angular_distribution_function.py
+++ b/mdsuite/calculators/angular_distribution_function.py
@@ -16,8 +16,8 @@
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.neighbour_list import get_neighbour_list, get_triu_indicies, get_triplets
 from mdsuite.utils.linalg import get_angles
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.utils.meta_functions import join_path
+from mdsuite.database.scheme import Computation
 
 log = logging.getLogger(__name__)
 
@@ -89,6 +89,7 @@ def __init__(self, **kwargs):
         self.species = None
         self.number_of_atoms = None
         self.norm_power = None
+        self.result_series_keys = ['angle', 'adf']
 
         # TODO _n_batches is used instead of n_batches because the memory management is not yet implemented correctly
         self.n_minibatches = None  # memory management for triples generation per batch.
@@ -113,7 +114,7 @@ def __call__(self,
                  gpu: bool = False,
                  plot: bool = True,
                  norm_power: int = 4,
-                 **kwargs):
+                 **kwargs) -> Computation:
         """
         Parameters
         ----------
@@ -174,6 +175,17 @@ def __call__(self,
         self.bin_range = [0.0, 3.15]  # from 0 to a chemists pi
         self.norm_power = norm_power
 
+        return self.update_db_entry_with_kwargs(
+            r_cut=r_cut,
+            start=start,
+            stop=stop,
+            molecules=molecules,
+            n_confs=n_confs,
+            bins=bins,
+            species=species,
+            norm_power=norm_power
+        )
+
     def _check_inputs(self):
         """
         Check the inputs and set defaults if necessary.
@@ -426,24 +438,17 @@ def _compute_adfs(self, angles, species_indices):
                                               self.bin_range[1] * (180 / 3.14159),
                                               self.bins)
 
+            self.selected_species = [_species[0] for _species in species]
+
             self.data_range = self.n_confs
             log.debug(f"species are {species}")
 
-            properties = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=[{'angle': x, 'adf': y} for x, y in zip(bin_range_to_angles, hist)],
-                Subject=[_species[0] for _species in species]
-            )
-            self.update_database(properties)
+            data = {
+                self.result_series_keys[0]: bin_range_to_angles.tolist(),
+                self.result_series_keys[1]: hist.numpy().tolist()
+            }
 
-            if self.plot:
-                self.run_visualization(
-                    x_data=bin_range_to_angles,
-                    y_data=hist,
-                    title=f"{name} - Max: {bin_range_to_angles[tf.math.argmax(hist)]:.3f} degrees ",
-                )
+            self.queue_data(data=data, subjects=self.selected_species)
 
     def run_experimental_analysis(self):
         """
@@ -458,3 +463,16 @@ def run_experimental_analysis(self):
         sample_configs, species_indices = self._prepare_data_structure()
         angles = self._build_histograms(sample_configs, species_indices)
         self._compute_adfs(angles, species_indices)
+
+    def plot_data(self, data):
+        """Plot data"""
+        for selected_species, val in data.items():
+            bin_range_to_angles = np.linspace(self.bin_range[0] * (180 / 3.14159),
+                                              self.bin_range[1] * (180 / 3.14159),
+                                              len(val[self.result_series_keys[0]]))
+
+            self.run_visualization(
+                x_data=np.array(val[self.result_series_keys[0]]),
+                y_data=np.array(val[self.result_series_keys[1]]),
+                title=f"{selected_species} - Max: {bin_range_to_angles[tf.math.argmax(val[self.result_series_keys[1]])]:.3f} degrees ",
+            )
diff --git a/mdsuite/calculators/calculator.py b/mdsuite/calculators/calculator.py
index 2b2c660e..ef74859c 100644
--- a/mdsuite/calculators/calculator.py
+++ b/mdsuite/calculators/calculator.py
@@ -17,7 +17,6 @@
 import logging
 import numpy as np
 import random
-import matplotlib.pyplot as plt
 from pathlib import Path
 import tensorflow as tf
 import pandas as pd
@@ -84,13 +83,27 @@ def inner(self, *args, **kwargs):
         out = {}
         for experiment in self.experiments:
             self.experiment = experiment
-            func(self, *args, **kwargs)
-            if self.load_data:
-                out[self.experiment.name] = self.experiment.export_property_data(
-                    {"Analysis": self.analysis_name, "experiment": self.experiment.name}
-                )
+            self.clean_cache()
+            data = func(self, *args, **kwargs)
+            if data is None:
+                # new calculation will be performed
+                self.prepare_db_entry()
+                self.run_analysis()
+                self.save_db_data()
+                data = func(self, *args, **kwargs)
+            elif self.load_data:
+                raise NotImplementedError("Please user <exp/proj>.run.<method> instead of load!")
             else:
-                out[self.experiment.name] = self.run_analysis()
+                # Calculation already performed
+                pass
+
+            out[self.experiment.name] = data
+
+            if self.plot:
+                """Plot the data"""
+                self.plotter = DataVisualizer2D(title=self.analysis_name)
+                self.plot_data(data.data_dict)
+                self.plotter.grid_show(self.plot_array)
 
         if return_dict:
             return out
@@ -186,11 +199,19 @@ def __init__(self, experiment: Experiment = None, experiments: List[Experiment]
         self.batch_output_signature = None
         self.ensemble_output_signature = None
         self.species = None
+        # all species that are used in the calculation
+        self.selected_species = None
+        # the selected species which the current calculation iteration is performed on
         self.database_group = None
         self.analysis_name = None
         self.tau_values = None
         self.time = None
         self.data_resolution = None
+        self.plotter = None
+        # e.g. [diffusion_coefficient, uncertainty]
+        self.result_keys = None
+        # e.g., [time, msd]
+        self.result_series_keys = None
 
         # Set during operation or by child class
         self.batch_size: int
@@ -265,8 +286,6 @@ def update_user_args(self,
         # attributes based on user args
         self.time = self._handle_tau_values()  # process selected tau values.
 
-        self.plotter = DataVisualizer2D(title=self.analysis_name)
-
     def _calculate_prefactor(self, species: Union[str, tuple] = None):
         """
         calculate the calculator pre-factor.
@@ -820,9 +839,6 @@ def perform_computation(self):
                 self._apply_averaging_factor()
                 self._post_operation_processes(species)
 
-        if self.plot:
-            self.plotter.grid_show(self.plot_array)
-
     def run_experimental_analysis(self):
         """
         For experimental methods
@@ -857,3 +873,17 @@ def run_analysis(self):
     def dtype(self):
         """Get the dtype used for the calculator"""
         return self._dtype
+
+    def plot_data(self, data):
+        """Plot the data coming from the database
+
+        Parameters
+        ----------
+        data: db.Compution.data_dict associated with the current project
+        """
+        for selectected_species, val in data.items():
+            self.run_visualization(
+                x_data=np.array(val[self.result_series_keys[0]]) * self.experiment.units['time'],
+                y_data=np.array(val[self.result_series_keys[1]]) * self.experiment.units['time'],
+                title=f"{selectected_species}: {val[self.result_keys[0]]: 0.3E} +- {val[self.result_keys[1]]: 0.3E}"
+            )
diff --git a/mdsuite/calculators/coordination_number_calculation.py b/mdsuite/calculators/coordination_number_calculation.py
index 1bd37e0c..3c29b2d9 100644
--- a/mdsuite/calculators/coordination_number_calculation.py
+++ b/mdsuite/calculators/coordination_number_calculation.py
@@ -15,7 +15,6 @@
 import logging
 import numpy as np
 from scipy.signal import find_peaks
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.utils.exceptions import NotApplicableToAnalysis, CannotPerformThisAnalysis
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.meta_functions import golden_section_search
@@ -91,6 +90,8 @@ def __init__(self, **kwargs):
         self.x_label = r'$$\text{r}$$'
         self.y_label = 'CN'
         self.analysis_name = 'Coordination_Numbers'
+        self.result_keys = ['coordination_number', 'uncertainty', 'left1', 'right1', 'left2', 'right2']
+        self.result_series_keys = ["r", "cn"]
 
     @call
     def __call__(self, plot: bool = True, save: bool = True, data_range: int = 1, export: bool = False,
@@ -129,6 +130,12 @@ def __call__(self, plot: bool = True, save: bool = True, data_range: int = 1, ex
         self.savgol_order = savgol_order
         self.savgol_window_length = savgol_window_length
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            savgol_order=savgol_order,
+            savgol_window_length=savgol_window_length
+        )
+
     def _get_density(self, species: str) -> float:
         """
         Use the species_tuple in front of the name to get information about the pair
@@ -214,60 +221,63 @@ def _get_coordination_numbers(self):
         first_shell_error = np.std([self.integral_data[self.indices[0][0]],
                                     self.integral_data[self.indices[0][1]]]) / np.sqrt(2)
 
+        # TODO what about second shell?!
         second_shell = np.mean([self.integral_data[self.indices[1][0]],
                                 self.integral_data[self.indices[1][1]]]) - first_shell
         second_shell_error = np.std([self.integral_data[self.indices[1][0]],
                                      self.integral_data[self.indices[1][1]]]) / np.sqrt(2)
 
-
         return first_shell, first_shell_error
 
     def run_post_generation_analysis(self):
         """
         Calculate the coordination numbers and perform error analysis
         """
-        calculations = self._get_rdf_data()
-        for data in calculations:  # Loop over all existing RDFs
-            log.debug(f"Computing coordination numbers for {data.subjects}")
-            self.data_range = data.data_range
-            self._load_rdf_from_file(data)  # load the tensor_values from it
-            density = self._get_density(data.subjects[0])  # calculate the density
+        calculations = self.experiment.run.RadialDistributionFunction(plot=False)
+        self.data_range = calculations.data_range
+        for selected_species, vals in calculations.data_dict.items():  # Loop over all existing RDFs
+            log.debug(f"Computing coordination numbers for {selected_species}")
+            self.radii = np.array(vals["x"]).astype(float)[1:]
+            self.rdf = np.array(vals["y"]).astype(float)[1:]
+            self.selected_species = selected_species.split("_")
+            self.species_tuple = selected_species  # depreciated
 
-            self.species_tuple = "_".join(data.subjects)
+            density = self._get_density(self.selected_species[0])  # calculate the density
 
             self._integrate_rdf(density)  # integrate the rdf
             self._find_minimums()  # get the minimums of the rdf being studied
             _data = self._get_coordination_numbers()  # calculate the coordination numbers and update the experiment
 
-            properties = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=[{'coordination_number': _data[0],
-                       'uncertainty': _data[1]}],
-                Subject=[self.species_tuple]
+            data = {
+                self.result_keys[0]: _data[0],
+                self.result_keys[1]: _data[1],
+                self.result_keys[2]: self.radii[self.indices[0][0]],
+                self.result_keys[3]: self.radii[self.indices[0][1]],
+                self.result_keys[4]: self.radii[self.indices[1][0]],
+                self.result_keys[5]: self.radii[self.indices[1][1]],
+                self.result_series_keys[0]: self.radii[1:].tolist(),
+                self.result_series_keys[1]: self.integral_data.tolist()
+            }
+
+            self.queue_data(data=data, subjects=self.selected_species)
+
+    def plot_data(self, data):
+        """Plot the CN"""
+        # Plot the tensor_values if required
+        for selected_species, val in data.items():
+            model_1 = BoxAnnotation(
+                left=val[self.result_keys[2]],
+                right=val[self.result_keys[3]],
+                fill_alpha=0.1,
+                fill_color='red')
+            model_2 = BoxAnnotation(
+                left=val[self.result_keys[4]],
+                right=val[self.result_keys[5]],
+                fill_alpha=0.1,
+                fill_color='red')
+            self.run_visualization(
+                x_data=val[self.result_series_keys[0]],
+                y_data=val[self.result_series_keys[1]],
+                title=fr'{selected_species}: {val[self.result_keys[0]]: 0.3E} +- {val[self.result_keys[1]]: 0.3E}',
+                layouts=[model_1, model_2]
             )
-            data = properties.data
-            data += [{'r': x, 'cn': y} for x, y in
-                     zip(self.radii[1:], self.integral_data)]
-            properties.data = data
-            self.update_database(properties)
-
-            # Plot the tensor_values if required
-            if self.plot:
-                model_1 = BoxAnnotation(
-                    left=self.radii[self.indices[0][0]],
-                    right=self.radii[self.indices[0][1]],
-                    fill_alpha=0.1,
-                    fill_color='red')
-                model_2 = BoxAnnotation(
-                    left=self.radii[self.indices[1][0]],
-                    right=self.radii[self.indices[1][1]],
-                    fill_alpha=0.1,
-                    fill_color='red')
-                self.run_visualization(
-                    x_data=self.radii[1:],
-                    y_data=np.array(self.integral_data),
-                    title=fr'{self.species_tuple}: {_data[0]: 0.3E} +- {_data[1]: 0.3E}',
-                    layouts=[model_1, model_2]
-                )
diff --git a/mdsuite/calculators/einstein_diffusion_coefficients.py b/mdsuite/calculators/einstein_diffusion_coefficients.py
index 13ace76c..3e6e033b 100644
--- a/mdsuite/calculators/einstein_diffusion_coefficients.py
+++ b/mdsuite/calculators/einstein_diffusion_coefficients.py
@@ -24,12 +24,12 @@
 from tqdm import tqdm
 import tensorflow as tf
 from mdsuite.calculators.calculator import Calculator, call
-from mdsuite.database.calculator_database import Parameters
 
 from typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
     from mdsuite.experiment import Experiment
+    from mdsuite.database.scheme import Computation
 
 tqdm.monitor_interval = 0
 warnings.filterwarnings("ignore")
@@ -92,6 +92,8 @@ def __init__(self, **kwargs):
         self.database_group = 'Diffusion_Coefficients'
         self.x_label = r'$$ \text{Time} / s$$'
         self.y_label = r'$$ \text{MSD} / m^{2}$$'
+        self.result_keys = ['diffusion_coefficient', 'uncertainty']
+        self.result_series_keys = ['time', 'msd']
         self.analysis_name = 'Einstein Self-Diffusion Coefficients'
         self.loop_condition = False
         self.optimize = None
@@ -111,7 +113,7 @@ def __call__(self, plot: bool = True,
                  export: bool = False,
                  molecules: bool = False,
                  tau_values: Union[int, List, Any] = np.s_[:],
-                 gpu: bool = False):
+                 gpu: bool = False) -> Computation:
 
         """
 
@@ -145,7 +147,6 @@ def __call__(self, plot: bool = True,
         -------
         None
         """
-
         self.update_user_args(plot=plot,
                               data_range=data_range,
                               save=save,
@@ -154,6 +155,7 @@ def __call__(self, plot: bool = True,
                               tau_values=tau_values,
                               export=export,
                               gpu=gpu)
+
         self.species = species
         self.molecules = molecules
         self.optimize = optimize
@@ -166,6 +168,15 @@ def __call__(self, plot: bool = True,
             else:
                 self.species = list(self.experiment.species)
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            correlation_time=correlation_time,
+            molecules=molecules,
+            atom_selection=atom_selection,
+            tau_values=tau_values,
+            species=species
+        )
+
     def _update_output_signatures(self):
         """
         After having run _prepare managers, update the output signatures.
@@ -241,20 +252,12 @@ def _post_operation_processes(self, species: str = None):
 
         result = self._fit_einstein_curve([self.time, self.msd_array])
         log.debug(f"Saving {species}")
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'diffusion_coefficient': result[0], 'uncertainty': result[1]}],
-            Subject=[species]
-        )
-        data = properties.data
-        data += [{'time': x, 'msd': y} for x, y in zip(self.time, self.msd_array)]
-        properties.data = data
 
-        self.update_database(properties)
+        data = {
+            self.result_keys[0]: result[0],
+            self.result_keys[1]: result[1],
+            self.result_series_keys[0]: self.time.tolist(),
+            self.result_series_keys[1]: self.msd_array.tolist()
+        }
 
-        if self.plot:
-            self.run_visualization(x_data=np.array(self.time) * self.experiment.units['time'],
-                                   y_data=self.msd_array * self.experiment.units['time'],
-                                   title=f"{species}: {result[0]: 0.3E} +- {result[1]: 0.3E}")
+        self.queue_data(data=data, subjects=[species])
diff --git a/mdsuite/calculators/einstein_distinct_diffusion_coefficients.py b/mdsuite/calculators/einstein_distinct_diffusion_coefficients.py
index b9ea77a5..d324d039 100644
--- a/mdsuite/calculators/einstein_distinct_diffusion_coefficients.py
+++ b/mdsuite/calculators/einstein_distinct_diffusion_coefficients.py
@@ -22,7 +22,6 @@
 from tqdm import tqdm
 import tensorflow as tf
 import itertools
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.meta_functions import join_path
 
@@ -234,31 +233,21 @@ def _post_operation_processes(self, species: Union[str, tuple] = None):
         """
         if np.sign(self.msd_array[-1]) == -1:
             result = self._fit_einstein_curve([self.time, abs(self.msd_array)])
-            properties = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=[{'diffusion_coefficient': -1*result[0],
-                       'uncertainty': result[1]}],
-                Subject=list(species)
-            )
+
+            data = {
+                'diffusion_coefficient': -1 * result[0],
+                'uncertainty': result[1]
+            }
         else:
             result = self._fit_einstein_curve([self.time, self.msd_array])
-            properties = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=[{'diffusion_coefficient': result[0],
-                       'uncertainty': result[1]}],
-                Subject=list(species)
-            )
+            data = {
+                'diffusion_coefficient': result[0],
+                'uncertainty': result[1]
+            }
 
-        data = properties.data
-        data += [{'time': x, 'msd': y} for x, y in
-                 zip(self.time, self.msd_array)]
-        properties.data = data
+        data.update({'time': self.time.tolist(), 'msd': self.msd_array.tolist()})
 
-        self.update_database(properties)
+        self.queue_data(data=data, subjects=list(species))
 
         # Update the plot if required
         if self.plot:
diff --git a/mdsuite/calculators/einstein_helfand_ionic_conductivity.py b/mdsuite/calculators/einstein_helfand_ionic_conductivity.py
index f6368e70..0555dbbc 100644
--- a/mdsuite/calculators/einstein_helfand_ionic_conductivity.py
+++ b/mdsuite/calculators/einstein_helfand_ionic_conductivity.py
@@ -21,8 +21,6 @@
 from tqdm import tqdm
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.units import elementary_charge, boltzmann_constant
-from mdsuite.database.calculator_database import Parameters
-
 
 tqdm.monitor_interval = 0
 warnings.filterwarnings("ignore")
@@ -78,6 +76,9 @@ def __init__(self, **kwargs):
         self.analysis_name = 'Einstein Helfand Ionic Conductivity'
         self.prefactor: float
 
+        self.result_keys = ["ionic_conductivity", "uncertainty"]
+        self.result_series_keys = ["time", "msd"]
+
     @call
     def __call__(self, plot=True, data_range=500, save=True, correlation_time=1,
                  export: bool = False, gpu: bool = False):
@@ -105,6 +106,11 @@ def __call__(self, plot=True, data_range=500, save=True, correlation_time=1,
                               export=export, gpu=gpu)
         self.msd_array = np.zeros(self.data_range)
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            correlation_time=correlation_time
+        )
+
     def _update_output_signatures(self):
         """
         Update the output signature for the IC.
@@ -169,23 +175,12 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self._fit_einstein_curve([self.time, self.msd_array])
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[
-                {'ionic_conductivity': result[0], 'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'msd': y} for x, y in
-                 zip(self.time, self.msd_array)]
-        properties.data = data
-        self.update_database(properties)
-
-        # Update the plot if required
-        if self.plot:
-            self.run_visualization(
-                x_data=np.array(self.time) * self.experiment.units['time'],
-                y_data=self.msd_array * self.experiment.units['time'],
-                title=f'{result[0]:.3E} +- {result[1]:.3E}')
+        data = {
+            self.result_keys[0]: result[0].tolist(),
+            self.result_keys[1]: result[1].tolist(),
+            self.result_series_keys[0]: self.time.tolist(),
+            self.result_series_keys[1]: self.msd_array.tolist()
+        }
+
+        self.queue_data(data=data, subjects=["System"])
+
diff --git a/mdsuite/calculators/einstein_helfand_thermal_conductivity.py b/mdsuite/calculators/einstein_helfand_thermal_conductivity.py
index 67c4c17b..73466411 100644
--- a/mdsuite/calculators/einstein_helfand_thermal_conductivity.py
+++ b/mdsuite/calculators/einstein_helfand_thermal_conductivity.py
@@ -18,7 +18,6 @@
 from tqdm import tqdm
 from mdsuite.calculators.calculator import Calculator, call
 
-from mdsuite.database.calculator_database import Parameters
 
 tqdm.monitor_interval = 0
 warnings.filterwarnings("ignore")
@@ -172,18 +171,13 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self._fit_einstein_curve([self.time, self.msd_array])
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'thermal_conductivity': result[0], 'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'msd': y} for x, y in
-                 zip(self.time, self.msd_array)]
-        properties.data = data
-        self.update_database(properties)
+        data = {
+            'thermal_conductivity': result[0],
+            'uncertainty': result[1],
+            'time': self.time.tolist(),
+            'msd': self.msd_array.tolist()
+        }
+        self.queue_data(data=data, subjects=['System'])
 
         # Update the plot if required
         if self.plot:
diff --git a/mdsuite/calculators/einstein_helfand_thermal_kinaci.py b/mdsuite/calculators/einstein_helfand_thermal_kinaci.py
index b1f6f35f..fcaa3b5a 100644
--- a/mdsuite/calculators/einstein_helfand_thermal_kinaci.py
+++ b/mdsuite/calculators/einstein_helfand_thermal_kinaci.py
@@ -13,13 +13,11 @@
 -------
 """
 import warnings
-import matplotlib.pyplot as plt
 import numpy as np
 import tensorflow as tf
 from tqdm import tqdm
 
 from mdsuite.calculators.calculator import Calculator, call
-from mdsuite.database.calculator_database import Parameters
 
 
 tqdm.monitor_interval = 0
@@ -171,19 +169,14 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self._fit_einstein_curve([self.time, self.msd_array])
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[
-                {'thermal_conductivity': result[0], 'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'msd': y} for x, y in
-                 zip(self.time, self.msd_array)]
-        properties.data = data
-        self.update_database(properties)
+        data = {
+            'thermal_conductivity': result[0],
+            'uncertainty': result[1],
+            'time': self.time.tolist(),
+            'msd': self.msd_array.tolist()
+        }
+
+        self.queue_data(data=data, subjects=['System'])
 
         # Update the plot if required
         if self.plot:
diff --git a/mdsuite/calculators/green_kubo_distinct_diffusion_coefficients.py b/mdsuite/calculators/green_kubo_distinct_diffusion_coefficients.py
index 09905ec8..96085020 100644
--- a/mdsuite/calculators/green_kubo_distinct_diffusion_coefficients.py
+++ b/mdsuite/calculators/green_kubo_distinct_diffusion_coefficients.py
@@ -23,7 +23,6 @@ class can then be called by the Experiment.green_kubo_diffusion_coefficients
 import itertools
 from scipy import signal
 from bokeh.models import Span
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.meta_functions import join_path
 
@@ -274,20 +273,14 @@ def _post_operation_processes(self, species: Union[str, tuple] = None):
         """
         result = self.prefactor * np.array(self.sigma)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'diffusion_coefficient': np.mean(result),
-                   'uncertainty': np.std(result) / (np.sqrt(len(result)))}],
-            Subject=list(species)
-            )
+        data = {
+            'diffusion_coefficient': np.mean(result).tolist(),
+            'uncertainty': (np.std(result) / (np.sqrt(len(result)))).tolist(),
+            'time': self.time.tolist(),
+            'acf': self.vacf.tolist()
+        }
 
-        data = properties.data
-        data += [{'time': x, 'acf': y} for x, y in
-                 zip(self.time, self.vacf)]
-        properties.data = data
-        self.update_database(properties)
+        self.queue_data(data=data, subjects=list(species))
 
         # Update the plot if required
         if self.plot:
diff --git a/mdsuite/calculators/green_kubo_ionic_conductivity.py b/mdsuite/calculators/green_kubo_ionic_conductivity.py
index ade23b06..8c93dd64 100644
--- a/mdsuite/calculators/green_kubo_ionic_conductivity.py
+++ b/mdsuite/calculators/green_kubo_ionic_conductivity.py
@@ -16,14 +16,13 @@
 can then be called by the Experiment.green_kubo_ionic_conductivity method and
 all necessary calculations performed.
 """
-import matplotlib.pyplot as plt
 import numpy as np
 import tensorflow as tf
 import tensorflow_probability as tfp
 from mdsuite.utils.units import boltzmann_constant, elementary_charge
 from mdsuite.calculators.calculator import Calculator, call
-from mdsuite.database.calculator_database import Parameters
 from bokeh.models import Span
+from mdsuite.database.scheme import Computation
 
 
 class GreenKuboIonicConductivity(Calculator):
@@ -74,19 +73,22 @@ def __init__(self, **kwargs):
         self.y_label = r"$$\text{JACF} / C^{2}\cdot m^{2}/s^{2}$$"
         self.analysis_name = "Green_Kubo_Ionic_Conductivity"
 
+        self.result_keys = ['ionic_conductivity', 'uncertainty']
+        self.result_series_keys = ['time', 'acf']
+
         self.prefactor: float
 
     @call
     def __call__(
-        self,
-        plot=True,
-        data_range=500,
-        save=True,
-        correlation_time=1,
-        export: bool = False,
-        gpu: bool = False,
-        integration_range: int = None,
-    ):
+            self,
+            plot=True,
+            data_range=500,
+            save=True,
+            correlation_time=1,
+            export: bool = False,
+            gpu: bool = False,
+            integration_range: int = None,
+    ) -> Computation:
         """
 
         Parameters
@@ -125,6 +127,11 @@ def __call__(
         else:
             self.integration_range = integration_range
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            correlation_time=correlation_time
+        )
+
     def _update_output_signatures(self):
         """
         Update the output signature for the IC.
@@ -155,13 +162,13 @@ def _calculate_prefactor(self, species: str = None):
         # Calculate the prefactor
         numerator = (elementary_charge ** 2) * (self.experiment.units["length"] ** 2)
         denominator = (
-            3
-            * boltzmann_constant
-            * self.experiment.temperature
-            * self.experiment.volume
-            * (self.experiment.units["length"] ** 3)
-            * self.data_range
-            * self.experiment.units["time"]
+                3
+                * boltzmann_constant
+                * self.experiment.temperature
+                * self.experiment.volume
+                * (self.experiment.units["length"] ** 3)
+                * self.data_range
+                * self.experiment.units["time"]
         )
         self.prefactor = numerator / denominator
 
@@ -206,31 +213,27 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self.prefactor * np.array(self.sigma)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'ionic_conductivity': result[0],
-                   'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'acf': y} for x, y in
-                 zip(self.time, self.jacf)]
-        properties.data = data
-        self.update_database(properties)
-
-        # Update the plot if required
-        if self.plot:
+        data = {
+            self.result_keys[0]: np.mean(result).tolist(),
+            self.result_keys[1]: (np.std(result) / np.sqrt(len(result))).tolist(),
+            self.result_series_keys[0]: self.time.tolist(),
+            self.result_series_keys[1]: self.jacf.numpy().tolist()
+        }
+
+        self.queue_data(data=data, subjects=['System'])
+
+    def plot_data(self, data):
+        """Plot the data"""
+        for selected_species, val in data.items():
             span = Span(
-                location=(np.array(self.time) * self.experiment.units["time"])[
+                location=(np.array(val[self.result_series_keys[0]]) * self.experiment.units["time"])[
                     self.integration_range - 1],
                 dimension='height',
                 line_dash='dashed'
             )
             self.run_visualization(
-                x_data=np.array(self.time) * self.experiment.units['time'],
-                y_data=self.jacf.numpy(),
-                title=f"{np.mean(result): 0.3E} +- {np.std(result) / np.sqrt(len(result)): 0.3E}",
+                x_data=np.array(val[self.result_series_keys[0]]) * self.experiment.units['time'],
+                y_data=np.array(val[self.result_series_keys[1]]),
+                title=f"{val[self.result_keys[0]]: 0.3E} +- {val[self.result_keys[1]]: 0.3E}",
                 layouts=[span]
             )
diff --git a/mdsuite/calculators/green_kubo_self_diffusion_coefficients.py b/mdsuite/calculators/green_kubo_self_diffusion_coefficients.py
index 2a60ebab..06576999 100644
--- a/mdsuite/calculators/green_kubo_self_diffusion_coefficients.py
+++ b/mdsuite/calculators/green_kubo_self_diffusion_coefficients.py
@@ -20,7 +20,7 @@ class can then be called by the Experiment.green_kubo_diffusion_coefficients
 import tensorflow as tf
 from bokeh.models import Span
 from mdsuite.calculators.calculator import Calculator, call
-from mdsuite.database.calculator_database import Parameters
+from mdsuite.database.scheme import Computation
 
 
 class GreenKuboDiffusionCoefficients(Calculator):
@@ -69,20 +69,23 @@ def __init__(self, **kwargs):
         self.y_label = r"$$\text{VACF} / m^{2}/s^{2}$$"
         self.analysis_name = "Green Kubo Self-Diffusion Coefficients"
 
+        self.result_keys = ['diffusion_coefficient', 'uncertainty']
+        self.result_series_keys = ['time', 'acf']
+
     @call
     def __call__(
-        self,
-        plot: bool = False,
-        species: list = None,
-        data_range: int = 500,
-        save: bool = True,
-        correlation_time: int = 1,
-        atom_selection=np.s_[:],
-        export: bool = False,
-        molecules: bool = False,
-        gpu: bool = False,
-        integration_range: int = None,
-    ):
+            self,
+            plot: bool = True,
+            species: list = None,
+            data_range: int = 500,
+            save: bool = True,
+            correlation_time: int = 1,
+            atom_selection=np.s_[:],
+            export: bool = False,
+            molecules: bool = False,
+            gpu: bool = False,
+            integration_range: int = None,
+    ) -> Computation:
         """
         Constructor for the Green-Kubo diffusion coefficients class.
 
@@ -138,6 +141,13 @@ def __call__(
             else:
                 self.species = list(self.experiment.species)
 
+        return self.update_db_entry_with_kwargs(
+            species=species,
+            data_range=data_range,
+            correlation_time=correlation_time,
+            atom_selection=atom_selection
+        )
+
     def _update_output_signatures(self):
         """
         After having run _prepare managers, update the output signatures.
@@ -173,19 +183,19 @@ def _calculate_prefactor(self, species: str = None):
         if self.molecules:
             numerator = self.experiment.units["length"] ** 2
             denominator = (
-                3
-                * self.experiment.units["time"]
-                * (self.integration_range - 1)
-                * len(self.experiment.molecules[species]["indices"])
+                    3
+                    * self.experiment.units["time"]
+                    * (self.integration_range - 1)
+                    * len(self.experiment.molecules[species]["indices"])
             )
             self.prefactor = numerator / denominator
         else:
             numerator = self.experiment.units["length"] ** 2
             denominator = (
-                3
-                * self.experiment.units["time"]
-                * self.integration_range
-                * len(self.experiment.species[species]["indices"])
+                    3
+                    * self.experiment.units["time"]
+                    * self.integration_range
+                    * len(self.experiment.species[species]["indices"])
             )
             self.prefactor = numerator / denominator
 
@@ -232,30 +242,27 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self.prefactor * np.array(self.sigma)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'diffusion_coefficient': result[0],
-                   'uncertainty': result[1]}],
-            Subject=[species]
-        )
-        data = properties.data
-        data += [{'time': x, 'acf': y} for x, y in
-                 zip(self.time, self.vacf)]
-        properties.data = data
-        self.update_database(properties)
-
-        # Update the plot if required
-        if self.plot:
+        data = {
+            self.result_keys[0]: np.mean(result).tolist(),
+            self.result_keys[1]: (np.std(result) / np.sqrt(len(result))).tolist(),
+            self.result_series_keys[0]: self.time.tolist(),
+            self.result_series_keys[1]: self.vacf.numpy().tolist()
+        }
+
+        self.queue_data(data=data, subjects=[species])
+
+    def plot_data(self, data):
+        """Plot the data"""
+        for selected_species, val in data.items():
             span = Span(
-                location=(np.array(self.time) * self.experiment.units["time"])[self.integration_range - 1],
+                location=(np.array(val[self.result_series_keys[0]]) * self.experiment.units["time"])[
+                    self.integration_range - 1],
                 dimension='height',
                 line_dash='dashed'
             )
             self.run_visualization(
-                x_data=np.array(self.time) * self.experiment.units['time'],
-                y_data=self.vacf.numpy(),
-                title=f"{species}: {np.mean(result): .3E} +- {np.std(result) / (np.sqrt(len(result))): .3E}",
+                x_data=np.array(val[self.result_series_keys[0]]) * self.experiment.units['time'],
+                y_data=np.array(val[self.result_series_keys[1]]),
+                title=f"{val[self.result_keys[0]]: 0.3E} +- {val[self.result_keys[1]]: 0.3E}",
                 layouts=[span]
             )
diff --git a/mdsuite/calculators/green_kubo_thermal_conductivity.py b/mdsuite/calculators/green_kubo_thermal_conductivity.py
index 37ae7253..116d76e5 100644
--- a/mdsuite/calculators/green_kubo_thermal_conductivity.py
+++ b/mdsuite/calculators/green_kubo_thermal_conductivity.py
@@ -23,7 +23,6 @@ class can then be called by the Experiment.green_kubo_thermal_conductivity
 from tqdm import tqdm
 from mdsuite.calculators.calculator import Calculator, call
 import tensorflow_probability as tfp
-from mdsuite.database.calculator_database import Parameters
 from bokeh.models import Span
 
 tqdm.monitor_interval = 0
@@ -123,6 +122,11 @@ def __call__(
         else:
             self.integration_range = integration_range
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            correlation_time=correlation_time
+        )
+
     def _update_output_signatures(self):
         """
         Update the output signature for the IC.
@@ -211,19 +215,14 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self.prefactor * np.array(self.sigma)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'thermal_conductivity': result[0],
-                   'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'acf': y} for x, y in
-                 zip(self.time, self.jacf)]
-        properties.data = data
-        self.update_database(properties)
+        data = {
+            "computation_results": result[0],
+            "uncertainty": result[1],
+            'time': self.time.tolist(),
+            'acf': self.jacf.numpy().tolist()
+        }
+
+        self.queue_data(data=data, subjects=['System'])
 
         # Update the plot if required
         if self.plot:
diff --git a/mdsuite/calculators/green_kubo_viscosity.py b/mdsuite/calculators/green_kubo_viscosity.py
index 41c7d3b1..db176ab2 100644
--- a/mdsuite/calculators/green_kubo_viscosity.py
+++ b/mdsuite/calculators/green_kubo_viscosity.py
@@ -17,7 +17,6 @@
 import tensorflow as tf
 import tensorflow_probability as tfp
 from mdsuite.calculators.calculator import Calculator, call
-from mdsuite.database.calculator_database import Parameters
 from bokeh.models import Span
 
 
@@ -105,6 +104,11 @@ def __call__(
         else:
             self.integration_range = integration_range
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            correlation_time=correlation_time
+        )
+
     def _update_output_signatures(self):
         """
         Update the output signature for the IC.
@@ -194,19 +198,15 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self.prefactor * np.array(self.sigma)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'viscosity': result[0],
-                   'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'acf': y} for x, y in
-                 zip(self.time, self.jacf)]
-        properties.data = data
-        self.update_database(properties)
+        data = {
+            'viscosity': result[0],
+            'uncertainty': result[1],
+            'time': self.time.tolist(),
+            'acf': self.jacf.numpy().tolist()
+        }
+
+        self.queue_data(data=data, subjects=['System'])
+
         # Update the plot if required
         if self.plot:
             span = Span(
diff --git a/mdsuite/calculators/green_kubo_viscosity_flux.py b/mdsuite/calculators/green_kubo_viscosity_flux.py
index b077ccd4..241b8093 100644
--- a/mdsuite/calculators/green_kubo_viscosity_flux.py
+++ b/mdsuite/calculators/green_kubo_viscosity_flux.py
@@ -21,7 +21,6 @@
 from tqdm import tqdm
 import tensorflow as tf
 import tensorflow_probability as tfp
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.calculators.calculator import Calculator, call
 from bokeh.models import Span
 
@@ -107,6 +106,11 @@ def __call__(self,
         else:
             self.integration_range = integration_range
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            correlation_time=correlation_time
+        )
+
     def _update_output_signatures(self):
         """
         Update the output signature for the IC.
@@ -182,19 +186,14 @@ def _post_operation_processes(self, species: str = None):
         """
         result = self.prefactor*np.array(self.sigma)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'viscosity': result[0],
-                   'uncertainty': result[1]}],
-            Subject=["System"]
-        )
-        data = properties.data
-        data += [{'time': x, 'acf': y} for x, y in
-                 zip(self.time, self.jacf)]
-        properties.data = data
-        self.update_database(properties)
+        data = {
+            'viscosity': result[0],
+            'uncertainty': result[1],
+            'time': self.time.tolist(),
+            'acf': self.jacf.numpy().tolist()
+        }
+
+        self.queue_data(data=data, subjects=['System'])
 
         # Update the plot if required
         if self.plot:
diff --git a/mdsuite/calculators/kirkwood_buff_integrals.py b/mdsuite/calculators/kirkwood_buff_integrals.py
index 2273c1e5..b0181ad6 100644
--- a/mdsuite/calculators/kirkwood_buff_integrals.py
+++ b/mdsuite/calculators/kirkwood_buff_integrals.py
@@ -11,9 +11,9 @@
 """
 import logging
 import numpy as np
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.utils.exceptions import NotApplicableToAnalysis
 from mdsuite.calculators.calculator import Calculator, call
+from mdsuite.database.scheme import Computation
 
 log = logging.getLogger(__name__)
 
@@ -70,17 +70,17 @@ def __init__(self, **kwargs):
         self.data_files = []
         self.rdf = None
         self.radii = None
-        self.species_tuple = None
         self.kb_integral = None
         self.database_group = "Kirkwood_Buff_Integral"
         self.x_label = r"$$ \text{r} / \AA$$"
         self.y_label = r"$$\text{G}(\mathbf{r})$$"
         self.analysis_name = "Kirkwood-Buff_Integral"
+        self.result_series_keys = ['r', 'kb_integral']
 
         self.post_generation = True
 
     @call
-    def __call__(self, plot=True, save=True, data_range=1, export: bool = False):
+    def __call__(self, plot=True, save=True, data_range=1, export: bool = False) -> Computation:
         """
         Doc string for this one.
         Parameters
@@ -99,6 +99,10 @@ def __call__(self, plot=True, save=True, data_range=1, export: bool = False):
             plot=plot, save=save, data_range=data_range, export=export
         )
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range
+        )
+
     def _autocorrelation_time(self):
         """
         Not needed in this analysis
@@ -127,31 +131,27 @@ def run_post_generation_analysis(self):
         """
         Calculate the potential of mean-force and perform error analysis
         """
-        calculations = self._get_rdf_data()
-        for data in calculations:  # Loop over all existing RDFs
-            self.species_tuple = "_".join(data.subjects)
-            self.data_range = data.data_range
-            self._load_rdf_from_file(data)  # load the tensor_values from it
+        calculations = self.experiment.run.RadialDistributionFunction(plot=False)
+        self.data_range = calculations.data_range
+        for selected_species, vals in calculations.data_dict.items():  # Loop over all existing RDFs
+            self.selected_species = selected_species.split("_")
+
+            self.radii = np.array(vals["x"]).astype(float)[1:]
+            self.rdf = np.array(vals["y"]).astype(float)[1:]
             self._calculate_kb_integral()  # Integrate the rdf and calculate the KB integral
 
-            data = [
-                {"r": x, "kb_integral": y} for x, y in
-                zip(self.radii[1:], self.kb_integral)
-            ]
-            log.debug(f"Writing {self.analysis_name} to database!")
-            properties = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=data,
-                Subject=[self.species_tuple]
+            data = {
+                self.result_series_keys[0]: self.radii[1:].tolist(),
+                self.result_series_keys[1]: self.kb_integral
+            }
+
+            self.queue_data(data=data, subjects=self.selected_species)
+
+    def plot_data(self, data):
+        """Plot the data"""
+        for selected_species, val in data.items():
+            self.run_visualization(
+                x_data=val[self.result_series_keys[0]],
+                y_data=val[self.result_series_keys[1]],
+                title=selected_species,
             )
-            self.update_database(properties)
-
-            # Plot if required
-            if self.plot:
-                self.run_visualization(
-                    x_data=self.radii[1:],
-                    y_data=self.kb_integral,
-                    title=f"{self.species_tuple}",
-                )
diff --git a/mdsuite/calculators/nernst_einstein_ionic_conductivity.py b/mdsuite/calculators/nernst_einstein_ionic_conductivity.py
index e0e8a989..cd3a668d 100644
--- a/mdsuite/calculators/nernst_einstein_ionic_conductivity.py
+++ b/mdsuite/calculators/nernst_einstein_ionic_conductivity.py
@@ -10,9 +10,7 @@
 Calculate the Nernst-Einstein Conductivity of a system
 """
 import logging
-import sys
 import operator
-from typing import Union
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.units import boltzmann_constant, elementary_charge
 
diff --git a/mdsuite/calculators/potential_of_mean_force.py b/mdsuite/calculators/potential_of_mean_force.py
index d280aa97..7a21269f 100644
--- a/mdsuite/calculators/potential_of_mean_force.py
+++ b/mdsuite/calculators/potential_of_mean_force.py
@@ -20,13 +20,13 @@
 import logging
 import numpy as np
 from scipy.signal import find_peaks
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.utils.exceptions import NotApplicableToAnalysis
 from mdsuite.calculators.calculator import Calculator, call
 from mdsuite.utils.meta_functions import golden_section_search
 from mdsuite.utils.meta_functions import apply_savgol_filter
 from mdsuite.utils.units import boltzmann_constant
 from bokeh.models import BoxAnnotation
+from mdsuite.database.scheme import Computation
 
 log = logging.getLogger(__name__)
 
@@ -62,7 +62,7 @@ class PotentialOfMeanForce(Calculator):
                         rdf tensor_values being studied.
     radii = None : list
                         radii tensor_values corresponding to the rdf.
-    species_tuple : list
+    selected_species : list
                         A list of species combinations being studied.
 
     See Also
@@ -91,18 +91,28 @@ def __init__(self, **kwargs):
         self.file_to_study = None
         self.rdf = None
         self.radii = None
-        self.species_tuple = None
         self.pomf = None
         self.indices = None
-        self.database_group = 'Potential_Of_Mean_Force'
-        self.x_label = r'$$\text{r| /  \AA$$'
-        self.y_label = r'$$w^{(2)}(r)$$'
-        self.analysis_name = 'Potential_of_Mean_Force'
+        self.database_group = "Potential_Of_Mean_Force"
+        self.x_label = r"$$\text{r| /  \AA$$"
+        self.y_label = r"$$w^{(2)}(r)$$"
+
+        self.result_keys = ["min_pomf", "uncertainty", "left", "right"]
+        self.result_series_keys = ["r", "pomf"]
+
+        self.analysis_name = "Potential_of_Mean_Force"
         self.post_generation = True
 
     @call
-    def __call__(self, plot=True, save=True, data_range=1, export: bool = False,
-                 savgol_order: int = 2, savgol_window_length: int = 17):
+    def __call__(
+            self,
+            plot=True,
+            save=True,
+            data_range=1,
+            export: bool = False,
+            savgol_order: int = 2,
+            savgol_window_length: int = 17,
+    ) -> Computation:
         """
         Python constructor for the class
 
@@ -131,6 +141,12 @@ def __call__(self, plot=True, save=True, data_range=1, export: bool = False,
         self.savgol_order = savgol_order
         self.savgol_window_length = savgol_window_length
 
+        return self.update_db_entry_with_kwargs(
+            data_range=data_range,
+            savgol_order=savgol_order,
+            savgol_window_length=savgol_window_length,
+        )
+
     def _autocorrelation_time(self):
         """
         Not needed in this analysis
@@ -142,15 +158,17 @@ def _calculate_potential_of_mean_force(self):
         Calculate the potential of mean force
         """
 
-        self.pomf = -1 * boltzmann_constant * self.experiment.temperature * np.log(self.rdf)
+        self.pomf = (
+                -1 * boltzmann_constant * self.experiment.temperature * np.log(self.rdf)
+        )
 
     def _get_max_values(self):
         """
         Calculate the maximums of the rdf
         """
         filtered_data = apply_savgol_filter(
-            self.pomf, order=self.savgol_order,
-            window_length=self.savgol_window_length)
+            self.pomf, order=self.savgol_order, window_length=self.savgol_window_length
+        )
 
         # Find the maximums in the filtered dataset
         peaks = find_peaks(filtered_data)[0]
@@ -170,13 +188,21 @@ def _find_minimum(self):
                 Location of the minimums of the pomf values.
         """
 
-        peaks = self._get_max_values()  # get the peaks of the tensor_values post-filtering
+        peaks = (
+            self._get_max_values()
+        )  # get the peaks of the tensor_values post-filtering
 
         # Calculate the radii of the minimum range
-        pomf_radii = golden_section_search([self.radii, self.pomf], self.radii[peaks[1]], self.radii[peaks[0]])
+        pomf_radii = golden_section_search(
+            [self.radii, self.pomf], self.radii[peaks[1]], self.radii[peaks[0]]
+        )
 
-        pomf_indices = list([np.where(self.radii == pomf_radii[0])[0][0],
-                             np.where(self.radii == pomf_radii[1])[0][0]])
+        pomf_indices = list(
+            [
+                np.where(self.radii == pomf_radii[0])[0][0],
+                np.where(self.radii == pomf_radii[1])[0][0],
+            ]
+        )
 
         return pomf_indices
 
@@ -185,11 +211,15 @@ def _get_pomf_value(self):
         Use a min-finding algorithm to calculate the potential of mean force value
         """
 
-        self.indices = self._find_minimum()  # update the class with the minimum value indices
+        self.indices = (
+            self._find_minimum()
+        )  # update the class with the minimum value indices
 
         # Calculate the value and error of the potential of mean-force
         pomf_value = np.mean([self.pomf[self.indices[0]], self.pomf[self.indices[1]]])
-        pomf_error = np.std([self.pomf[self.indices[0]], self.pomf[self.indices[1]]]) / np.sqrt(2)
+        pomf_error = np.std(
+            [self.pomf[self.indices[0]], self.pomf[self.indices[1]]]
+        ) / np.sqrt(2)
 
         return pomf_value, pomf_error
 
@@ -197,42 +227,48 @@ def run_post_generation_analysis(self):
         """
         Calculate the potential of mean-force and perform error analysis
         """
-
         # fill the tensor_values array with tensor_values
-        calculations = self._get_rdf_data()
-        for data in calculations:
-            self.file_to_study = data  # Set the correct tensor_values file in the class
-            self.species_tuple = "_".join(data.subjects)
-            self.data_range = data.data_range
-            self._load_rdf_from_file(data)  # load up the tensor_values
-            log.debug(f'rdf: {self.rdf} \t radii: {self.radii}')
+        calculations = self.experiment.run.RadialDistributionFunction(plot=False)
+        self.data_range = calculations.data_range
+        for selected_species, vals in calculations.data_dict.items():
+            self.selected_species = selected_species.split("_")
+
+            self.radii = np.array(vals["x"]).astype(float)[1:]
+            self.rdf = np.array(vals["y"]).astype(float)[1:]
+
+            log.debug(f"rdf: {self.rdf} \t radii: {self.radii}")
             self._calculate_potential_of_mean_force()  # calculate the potential of mean-force
-            _data = self._get_pomf_value()  # Determine the min values of the function and update experiment
-
-            properties = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=[{'min_pomf': _data[0],
-                       'uncertainty': _data[1]}],
-                Subject=[self.species_tuple]
+            (
+                pomf_value,
+                pomf_error,
+            ) = (
+                self._get_pomf_value()
+            )  # Determine the min values of the function and update experiment
+
+            data = {
+                self.result_keys[0]: pomf_value.tolist(),
+                self.result_keys[1]: pomf_error.tolist(),
+                self.result_keys[2]: self.radii[self.indices[0]],
+                self.result_keys[3]: self.radii[self.indices[1]],
+                self.result_series_keys[0]: self.radii.tolist(),
+                self.result_series_keys[1]: self.pomf.tolist()
+            }
+
+            self.queue_data(data=data, subjects=self.selected_species)
+
+    def plot_data(self, data):
+        """Plot the POMF"""
+        log.debug("Start plotting the POMF.")
+        for selected_species, val in data.items():
+            model = BoxAnnotation(
+                left=val[self.result_keys[2]],
+                right=val[self.result_keys[3]],
+                fill_alpha=0.1,
+                fill_color="red",
+            )
+            self.run_visualization(
+                x_data=val[self.result_series_keys[0]],
+                y_data=val[self.result_series_keys[1]],
+                title=fr"{selected_species}: {val[self.result_keys[0]]: 0.3E} +- {val[self.result_keys[1]]: 0.3E}",
+                layouts=[model],
             )
-            data = properties.data
-            data += [{'r': x, 'pomf': y} for x, y in
-                     zip(self.radii, self.pomf)]
-            properties.data = data
-            self.update_database(properties)
-
-            if self.plot:
-                model = BoxAnnotation(
-                    left=self.radii[self.indices[0]],
-                    right=self.radii[self.indices[1]],
-                    fill_alpha=0.1,
-                    fill_color='red')
-                self.run_visualization(
-                    x_data=self.radii,
-                    y_data=self.pomf,
-                    title=fr'{self.species_tuple}: {_data[0]: 0.3E} +- {_data[1]: 0.3E}',
-                    layouts=[model]
-                )
-
diff --git a/mdsuite/calculators/radial_distribution_function.py b/mdsuite/calculators/radial_distribution_function.py
index cf5751b3..83185ef2 100644
--- a/mdsuite/calculators/radial_distribution_function.py
+++ b/mdsuite/calculators/radial_distribution_function.py
@@ -32,7 +32,6 @@
 from mdsuite.utils.meta_functions import join_path
 
 from mdsuite.calculators.calculator import Calculator, call
-from mdsuite.database.calculator_database import Parameters
 from mdsuite.utils.meta_functions import split_array
 from mdsuite.utils.linalg import apply_minimum_image, get_partial_triu_indices, apply_system_cutoff
 
@@ -42,6 +41,7 @@
 
 if TYPE_CHECKING:
     from mdsuite import Experiment
+    from mdsuite.database.scheme import Computation
 
 # Set style preferences, turn off warning, and suppress the duplication of loading bars.
 tqdm.monitor_interval = 0
@@ -97,6 +97,7 @@ def __init__(self, **kwargs):
         self.x_label = r'$$r / \AA$$'
         self.y_label = r'$$g(r)$$'
         self.analysis_name = 'Radial_Distribution_Function'
+        self.result_series_keys = ["x", "y"]
         self.experimental = True
 
         self._dtype = tf.float32
@@ -134,7 +135,7 @@ def __call__(self,
                  species: list = None,
                  molecules: bool = False,
                  gpu: bool = False,
-                 **kwargs):
+                 **kwargs) -> Computation:
         """
         Compute the RDF with the given user parameters
 
@@ -200,8 +201,15 @@ def __call__(self,
         self._check_input()
         self._initialize_rdf_parameters()
 
-        # # Perform analysis and save.
-        # return self.run_analysis()
+        return self.update_db_entry_with_kwargs(
+            data_range=self.data_range,
+            number_of_bins=number_of_bins,
+            number_of_configurations=number_of_configurations,
+            start=start,
+            stop=stop,
+            molecules=molecules,
+            species=species
+        )
 
     def _initialize_rdf_parameters(self):
         """
@@ -335,31 +343,22 @@ def _calculate_radial_distribution_functions(self):
         Updates the class state
         """
         for names in self.key_list:
+            self.selected_species = names.split("_")
+            # TODO use selected_species instead of names, it is more clear!
             prefactor = self._calculate_prefactor(names)  # calculate the prefactor
 
             self.rdf.update({names: self.rdf.get(names) * prefactor})  # Apply the prefactor
             log.debug("Writing RDF to database!")
 
             self.data_range = self.number_of_configurations
-            data = [{"x": x, "y": y} for x, y in
-                    zip(np.linspace(0.0, self.cutoff, self.number_of_bins),
-                        self.rdf.get(names))]
-            params = Parameters(
-                Property=self.database_group,
-                Analysis=self.analysis_name,
-                data_range=self.data_range,
-                data=data,
-                Subject=names.split("_"))
-
-            self.update_database(params)
-
-            if self.plot:
-                self.run_visualization(
-                    x_data=np.linspace(0.0, self.cutoff, self.number_of_bins),
-                    y_data=self.rdf.get(names),
-                    title=f"{names}",
-                )
+            data = {
+                self.result_series_keys[0]: np.linspace(0.0, self.cutoff, self.number_of_bins).tolist(),
+                self.result_series_keys[1]: self.rdf.get(names).tolist()
+            }
+
+            self.queue_data(data=data, subjects=self.selected_species)
 
+        # TODO remove rdf_state
         self.experiment.radial_distribution_function_state = True  # update the state
 
     def _correct_batch_properties(self):
@@ -697,3 +696,13 @@ def _piecewise(data: np.array) -> np.array:
         bin_edges = np.linspace(0.0, self.cutoff, self.number_of_bins)
 
         return _piecewise(np.array(bin_edges)) * bin_width
+
+    def plot_data(self, data):
+        """Plot the RDF data"""
+        for selected_species, val in data.items():
+            # TODO fix units!
+            self.run_visualization(
+                x_data=np.array(val[self.result_series_keys[0]]),
+                y_data=np.array(val[self.result_series_keys[1]]),
+                title=selected_species,
+            )
diff --git a/mdsuite/calculators/structure_factor.py b/mdsuite/calculators/structure_factor.py
index bc58a6cb..43e93750 100644
--- a/mdsuite/calculators/structure_factor.py
+++ b/mdsuite/calculators/structure_factor.py
@@ -14,13 +14,13 @@
 import pandas as pd
 from scipy.integrate import simps
 from scipy.integrate import cumtrapz
-from mdsuite.database.calculator_database import Parameters
 from tqdm import tqdm
 from mdsuite.utils.exceptions import NotApplicableToAnalysis
 from mdsuite.calculators.calculator import Calculator
 from mdsuite import data as static_data
 from importlib.resources import open_text
 
+
 log = logging.getLogger(__name__)
 
 
@@ -288,15 +288,12 @@ def run_post_generation_analysis(self):
             total_structure_factor_li.append(self.total_structure_factor(scattering_scalar))
         total_structure_factor_li = np.array(total_structure_factor_li)
 
-        properties = Parameters(
-            Property=self.database_group,
-            Analysis=self.analysis_name,
-            data_range=self.data_range,
-            data=[{'q': x, 's(q)': y} for x, y in
-                 zip(self.Q_arr, total_structure_factor_li)],
-            Subject=["System"]
-        )
-        self.update_database(properties)
+        data = {
+            'q': self.Q_arr.tolist(),
+            's(q)': total_structure_factor_li.tolist()
+        }
+
+        self.queue_data(data=data, subjects=['System'])
 
         if self.plot:
             self.run_visualization(
diff --git a/mdsuite/database/calculator_database.py b/mdsuite/database/calculator_database.py
index c09e979d..ef1491d0 100644
--- a/mdsuite/database/calculator_database.py
+++ b/mdsuite/database/calculator_database.py
@@ -11,11 +11,11 @@
 from __future__ import annotations
 
 import mdsuite.database.scheme as db
-from mdsuite.utils.database import get_or_create
+from collections import Counter
 from dataclasses import dataclass, field
+from sqlalchemy import and_
 
 import logging
-import numpy as np
 from typing import TYPE_CHECKING, List, Union
 
 if TYPE_CHECKING:
@@ -25,19 +25,174 @@
 
 
 @dataclass
-class Parameters:
-    Property: str
-    Analysis: str
-    data_range: int
-    data: list[dict] = field(default_factory=list)
-    Subject: list[str] = field(default_factory=list)
+class ComputationResults:
+    data: dict = field(default_factory=dict)
+    subjects: dict = field(default_factory=list)
 
 
 class CalculatorDatabase:
+    """Database Interactions of the calculator class
+
+    This class handles the interaction of the calculator with the project database
+    """
+
     def __init__(self, experiment):
+        """Constructor for the calculator database"""
         self.experiment: Experiment = experiment
+        self.db_computation: db.Computation = None
+        self.database_group = None
+        self.analysis_name = None
+        self.load_data = None
+
+        self._queued_data = []
+
+        # List of computation attributes that will be added to the database when it is written
+        self.db_computation_attributes = []
+
+    def clean_cache(self):
+        """Clean the lists of computed data"""
+        self._queued_data = []
+        self.db_computation_attributes = []
+
+    def prepare_db_entry(self):
+        """Prepare a database entry based on the attributes defined in the init"""
+        with self.experiment.project.session as ses:
+            experiment = (
+                ses.query(db.Experiment)
+                    .filter(db.Experiment.name == self.experiment.name)
+                    .first()
+            )
+
+        self.db_computation = db.Computation(experiment=experiment)
+        self.db_computation.name = self.analysis_name
+
+    def update_db_entry_with_kwargs(self, **kwargs):
+        """Update the database entry with the given user args/kwargs
+
+        Parameters
+        ----------
+        kwargs: all arguments that are passed to the call method and should be stored in the database
+
+
+        Notes
+        -----
+        This does require kwargs, args do not work!
+
+        Returns
+        -------
+        db.Computation:
+            Either a db.Computation object if the calculation was already performed or None
 
-    def update_database(self, parameters: Union[dict, Parameters], delete_duplicate: bool = True):
+        """
+        with self.experiment.project.session as ses:
+            experiment = (
+                ses.query(db.Experiment)
+                    .filter(db.Experiment.name == self.experiment.name)
+                    .first()
+            )
+
+            #  filter the correct experiment
+            computations = ses.query(db.Computation).filter(
+                db.Computation.experiment == experiment,
+                db.Computation.name == self.analysis_name,
+            )
+
+            # filter the passed arguments and only run, if they changed
+            for key, val in kwargs.items():
+                computations = computations.filter(
+                    db.Computation.computation_attributes.any(
+                        and_(
+                            db.ComputationAttribute.name == key,
+                            db.ComputationAttribute.str_value == str(val),
+                        )
+                    )
+                )
+
+            # filter the version of the experiment, e.g. run new computation
+            # if the experiment version has changed
+            computations = computations.filter(
+                db.Computation.computation_attributes.any(
+                    and_(
+                        db.ComputationAttribute.name == "version",
+                        db.ComputationAttribute.value == self.experiment.version
+                    )
+                )
+            )
+
+            computations = computations.all()
+            if len(computations) > 0:
+                log.debug("Calculation already performed! Loading it up")
+            # loading data_dict to avoid DetachedInstance errors
+            # this can take some time, depending on the size of the data
+            for computation in computations:
+                _ = computation.data_dict
+                _ = computation.data_range
+
+        if len(computations) > 0:
+            if len(computations) > 1:
+                log.warning("Something went wrong! Found more than one computation with the given arguments!")
+            return computations[0]  # it should only be one value
+        else:
+            for key, val in kwargs.items():
+                computation_attribute = db.ComputationAttribute(
+                    name=key, str_value=str(val)
+                )
+
+                self.db_computation_attributes.append(computation_attribute)
+
+            # save the current experiment version in the ComputationAttributes
+            experiment_version = db.ComputationAttribute(name="version", value=self.experiment.version)
+            self.db_computation_attributes.append(experiment_version)
+
+    def save_db_data(self):
+        """Save all the collected computationattributes and computation data to the database
+
+        This will be run after the computation was successful.
+        """
+        with self.experiment.project.session as ses:
+            ses.add(self.db_computation)
+            for val in self.db_computation_attributes:
+                # I need to set the relation inside the session, otherwise it does not work.
+                val.computation = self.db_computation
+                ses.add(val)
+
+            for data_obj in self._queued_data:
+                # TODO consider renaming species to e.g., subjects, because species here can also be molecules
+                data_obj: ComputationResults
+                computation_result = db.ComputationResult(computation=self.db_computation, data=data_obj.data)
+                species_list = []
+                for species in data_obj.subjects:
+                    # this will collect duplicates that can be counted later, otherwise I would use .in_
+                    species_list.append(
+                        ses.query(db.ExperimentSpecies).filter(db.ExperimentSpecies.name == species).first()
+                    )
+                # in case of e.g. `System` species will be [None], which is then removed
+                species_list = [x for x in species_list if x is not None]
+                for species, count in Counter(species_list).items():
+                    associate = db.SpeciesAssociation(count=count)
+                    associate.species = species
+                    computation_result.species.append(associate)
+
+                ses.add(computation_result)
+
+            ses.commit()
+
+    def queue_data(self, data, subjects):
+        """Queue data to be stored in the database
+
+        Parameters:
+            data: dict
+                A  dictionary containing all the data that was computed by the computation
+            subjects: list
+                A list of strings / subject names that are associated with the data, e.g. the pairs of the RDF
+        """
+        self._queued_data.append(
+            ComputationResults(data=data, subjects=subjects)
+        )
+
+    def update_database(
+            self, parameters, delete_duplicate: bool = True
+    ):
         """
         Add data to the database
 
@@ -52,157 +207,36 @@ def update_database(self, parameters: Union[dict, Parameters], delete_duplicate:
         -------
         Updates the sql database
         """
-        if isinstance(parameters, Parameters):
-            with self.experiment.project.session as ses:
-                experiment = ses.query(db.Experiment).filter(db.Experiment.name == self.experiment.name).first()
-                computation = db.Computation(experiment=experiment, name=parameters.Analysis)
-                ses.add(computation)
-
-                computation_property = db.ComputationAttribute(computation=computation, name="Property",
-                                                               str_value=parameters.Property)
-                ses.add(computation_property)
-
-                computation_analysis = db.ComputationAttribute(computation=computation, name="Analysis",
-                                                               str_value=parameters.Analysis)
-                ses.add(computation_analysis)
-
-                computation_data_range = db.ComputationAttribute(computation=computation, name="data_range",
-                                                                 value=parameters.data_range)
-                ses.add(computation_data_range)
-
-                for subject in parameters.Subject:
-                    computation_subject = db.ComputationAttribute(computation=computation, name="subject",
-                                                                  str_value=subject)
-                    ses.add(computation_subject)
-
-                # data
-
-                for data in parameters.data:
-                    for key, val in data.items():
-                        computation_data = db.ComputationData(computation=computation, value=val, dimension=key)
-                        ses.add(computation_data)
-
-                ses.commit()
-        else:
-            log.warning("Using depreciated dictionary method - Please use dataclass instead.")
-            # allow subjects and Subject
-            if parameters.get("subjects") is None:
-                try:
-                    log.warning("Using depreciated `Subject` \t Please use `subjects` instead.")
-                    parameters['subjects'] = parameters['Subject']
-                except KeyError:
-                    raise KeyError('Please add the key "subjects" to your calculator')
-
-            with self.experiment.project.session as ses:
-                experiment = ses.query(db.Experiment).filter(db.Experiment.name == self.experiment.name).first()
-                computation = db.Computation(experiment=experiment, name=parameters["Analysis"])
-                ses.add(computation)
-
-                computation_property = db.ComputationAttribute(computation=computation, name="Property",
-                                                               str_value=parameters['Property'])
-                ses.add(computation_property)
-
-                computation_analysis = db.ComputationAttribute(computation=computation, name="Analysis",
-                                                               str_value=parameters['Analysis'])
-                ses.add(computation_analysis)
-
-                computation_data_range = db.ComputationAttribute(computation=computation, name="data_range",
-                                                                 value=parameters['data_range'])
-                ses.add(computation_data_range)
-
-                for subject in parameters['subjects']:
-                    computation_subject = db.ComputationAttribute(computation=computation, name="subject",
-                                                                  str_value=subject)
-                    ses.add(computation_subject)
-
-                # data
-
-                for data in parameters['data']:
-                    for key, val in data.items():
-                        computation_data = db.ComputationData(computation=computation, value=val, dimension=key)
-                        ses.add(computation_data)
-
-                ses.commit()
+        raise DeprecationWarning("This function has been replaced by `queue_data`")
 
+    ##### REMOVE ######
     # TODO rename and potentially move to a RDF based parent class
     def _get_rdf_data(self) -> List[db.Computation]:
         """Fill the data_files list with filenames of the rdf tensor_values"""
         # TODO replace with exp.load.RDF()
-        with self.experiment.project.session as ses:
-            computations = ses.query(db.Computation).filter(
-                db.Computation.computation_attributes.any(str_value="Radial_Distribution_Function", name="Property")
-            ).all()
-
-            for computation in computations:
-                _ = computation.data_dict
-                _ = computation.data_range
-
-        return computations
+        raise DeprecationWarning("Replaced by experiment.run.RadialDistributionFuncion(**kwargs)")
+        # with self.experiment.project.session as ses:
+        #     computations = (
+        #         ses.query(db.Computation)
+        #             .filter(
+        #             db.Computation.computation_attributes.any(
+        #                 str_value="Radial_Distribution_Function", name="Property"
+        #             )
+        #         )
+        #             .all()
+        #     )
+        #
+        #     for computation in computations:
+        #         _ = computation.data_dict
+        #         _ = computation.data_range
+        #
+        # return computations
 
     # TODO rename and potentially move to a RDF based parent class
     def _load_rdf_from_file(self, computation: db.Computation):
         """Load the raw rdf tensor_values from a directory"""
+        raise DeprecationWarning("Replaced by experiment.run.RadialDistributionFuncion(**kwargs)")
 
-        self.radii = np.array(computation.data_dict['x']).astype(float)[1:]
-        self.rdf = np.array(computation.data_dict['y']).astype(float)[1:]
-
-    # @property
-    # def data(self) -> np.ndarray:
-    #     """
-    #
-    #     Returns
-    #     -------
-    #     object that contains `
-    #
-    #     """
-    #     with self.experiment.project.session as ses:
-    #         ses.query(db.Computation)
-    #
-    #     return 0
-    #
-    # # if delete_duplicate:
-    # #     self._delete_duplicate_rows(parameters)
-    # # else:
-    # #     if self._check_row_existence(parameters):
-    # #         log.info("Note, an entry with these parameters already exists in the database.")
-    #
-    # log.debug(f'Adding {parameters.get("Property")} to database!')
-    #
-    # with self.experiment.project.session as ses:
-    #     # Create a Data instance to store the value
-    #     # TODO use **parameters instead with parameters.pop
-    #
-    #     try:  # check if it is a list
-    #         data = [Data(**param) for param in parameters['data']]  # param is a dict
-    #     except TypeError:
-    #         try:  # check if it is a dictionary with keys [x, y, z, uncertainty]
-    #             data = [Data(parameters['data'])]
-    #         except TypeError:
-    #             data = [Data(x=parameters['data'])]
-    #
-    #     try:
-    #         subjects = [Subject(subject=param) for param in parameters['subjects']]  # param is a string
-    #     except TypeError:
-    #         subjects = [Subject(subject=parameters['subjects'])]  # param is a string
-    #
-    #     log.debug(f"Subjects are: {subjects}")
-    #
-    #     # Create s SystemProperty instance to store the values
-    #     system_property = SystemProperty(
-    #         property=parameters['Property'],
-    #         analysis=parameters['Analysis'],
-    #         data_range=parameters['data_range'],
-    #         subjects=subjects,
-    #         data=data,
-    #         information=parameters.get("information")
-    #     )
-    #
-    #     log.debug(f"Created: {system_property}")
-    #
-    #     # add to the session
-    #     ses.add(system_property)
-    #
-    #     # commit to the database
-    #     ses.commit()
-    #
-    # log.debug("Values successfully written to database. Closed database session.")
+        # self.radii = np.array(computation.data_dict["x"]).astype(float)[1:]
+        # self.rdf = np.array(computation.data_dict["y"]).astype(float)[1:]
+#####################
diff --git a/mdsuite/database/database_scheme.py b/mdsuite/database/database_scheme.py
deleted file mode 100644
index 59828035..00000000
--- a/mdsuite/database/database_scheme.py
+++ /dev/null
@@ -1,143 +0,0 @@
-"""
-Definition of the Database objects
-"""
-from sqlalchemy.orm import declarative_base
-from sqlalchemy import Column, Integer, String, Float, ForeignKey
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm.exc import DetachedInstanceError
-
-import numpy as np
-
-Base = declarative_base()
-
-
-class SystemProperty(Base):
-    """Main class for System properties
-
-    Parameters
-    ----------
-    id: int, PK
-    property: str
-        Name of the Property
-    analysis: str
-        Name of the analysis
-    data_range: int
-        Data range
-    information: str
-        Additional information for the system property
-    data: Data
-        list of data associated with the system property (can be [x, y, z, uncertainty])
-    subjects: Subject
-        list of the subjects/species/molecules that are associated with the system property
-    """
-    __tablename__ = 'system_properties'
-    id = Column(Integer, primary_key=True)
-    property = Column(String)
-    analysis = Column(String)
-    data_range = Column(Integer)
-    information = Column(String, nullable=True)
-
-    data = relationship("Data", cascade="all, delete", back_populates="system_property")
-    subjects = relationship("Subject", cascade="all, delete", back_populates="system_property")
-
-    # TODO check that cascade is working properly!
-
-    def __repr__(self):
-        """System Property representation"""
-        try:
-            representation = f"{self.analysis} on {self.subjects}"
-        except DetachedInstanceError:
-            representation = f"{self.analysis}"
-
-        if self.information is not None:
-            representation += f" \t {self.information}"
-        return representation
-
-    def __init__(self, property, analysis, subjects, data_range, data, information=None):
-        """System Property constructor"""
-        self.property = property
-        self.analysis = analysis
-        self.subjects = subjects
-        self.data_range = data_range
-        self.data = data
-        self.information = information
-
-    def data_array(self) -> np.array:
-        """
-        Convert the one-to-many relationship data into a numpy array
-
-        Returns
-        -------
-        np.array: all data converted to a numpy array with the shape (length, property) where a property contains the
-        4 dimensions (x, y, z, uncertainty)
-
-        """
-        data = []
-        for obj in self.data:
-            data.append([obj.x, obj.y, obj.z, obj.uncertainty])
-        return np.array(data)
-
-
-class Data(Base):
-    """
-    Class for the data associated with SystemProperty
-
-    Parameters
-    ----------
-    id: int, PK
-    x: float, required
-        x-value of the data
-    y: float
-        y-value of the data
-    z: float
-        z-value of the data
-    uncertainty: float
-        uncertainty value of the data
-    """
-    __tablename__ = "data"
-
-    id = Column(Integer, primary_key=True)
-    x = Column(Float)
-    y = Column(Float, nullable=True)
-    z = Column(Float, nullable=True)
-
-    uncertainty = Column(Float, nullable=True)
-
-    system_property_id = Column(Integer, ForeignKey('system_properties.id', ondelete="CASCADE"))
-
-    system_property = relationship("SystemProperty", back_populates="data")
-
-    def __repr__(self):
-        """Representation of the data"""
-        return f"x:{self.x}"
-
-
-class Subject(Base):
-    """
-    Class for the subjects tables associated with SystemProperty
-
-    Parameters
-    ---------
-    id: int,
-        Primary key
-    subject: str,
-        name
-    system_property_id: int,
-        System property pk
-    system_property:
-        SQLAlchemy relation object
-
-
-    """
-    __tablename__ = "subjects"
-    id = Column(Integer, primary_key=True)
-    subject = Column(String)
-
-    system_property_id = Column(Integer, ForeignKey('system_properties.id', ondelete="CASCADE"))
-
-    system_property = relationship("SystemProperty", back_populates="subjects")
-
-    def __repr__(self):
-        """Representation of the subject"""
-        return f"{self.subject}"
-
diff --git a/mdsuite/database/experiment_database.py b/mdsuite/database/experiment_database.py
index d3125fdf..4e29798e 100644
--- a/mdsuite/database/experiment_database.py
+++ b/mdsuite/database/experiment_database.py
@@ -16,7 +16,7 @@
 import logging
 
 import mdsuite.database.scheme as db
-from mdsuite.database.scheme import Project, Experiment, ExperimentData, Species, SpeciesData
+from mdsuite.database.scheme import Project, Experiment, ExperimentAttribute
 from mdsuite.utils.database import get_or_create
 import pandas as pd
 
@@ -34,6 +34,10 @@ def __init__(self, project: Project, experiment_name):
         self.project = project
         self.name = experiment_name
 
+        # Property cache
+        self._species = None
+        self._molecules = None
+
     def export_property_data(self, parameters: dict) -> List[db.Computation]:
         """
         Export property data from the SQL database.
@@ -48,39 +52,7 @@ def export_property_data(self, parameters: dict) -> List[db.Computation]:
         output : list
                 A list of rows represented as dictionaries.
         """
-        with self.project.session as ses:
-            subjects = parameters.pop('subjects', None)
-            experiment = parameters.pop('experiment', None)
-
-            query = ses.query(db.Computation)
-            for key, val in parameters.items():
-                if isinstance(val, str):
-                    query = query.filter(
-                        db.Computation.computation_attributes.any(name=key),
-                        db.Computation.computation_attributes.any(str_value=val)
-                    )
-                else:
-                    query = query.filter(
-                        db.Computation.computation_attributes.any(name=key),
-                        db.Computation.computation_attributes.any(value=val)
-                    )
-            if experiment is not None:
-                query = query.filter(db.Computation.experiment.has(name=experiment))
-            computations_all_subjects = query.all()
-
-            # Filter out subjects, this is easier to do this way around than via SQL statements (feel free to rewrite!)
-            computations = []
-            if subjects is not None:
-                for x in computations_all_subjects:
-                    if set(x.subjects).issubset(subjects):
-                        computations.append(x)
-            else:
-                computations = computations_all_subjects
-
-            for computation in computations:
-                _ = computation.data_dict
-
-        return computations
+        raise DeprecationWarning("This function has been removed and replaced by queue_database")
 
     @property
     def active(self):
@@ -104,8 +76,12 @@ def temperature(self):
         """Get the temperature of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            temperature = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "temperature").first()
+            temperature = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "temperature")
+                .first()
+            )
         try:
             return temperature.value
         except AttributeError:
@@ -118,7 +94,9 @@ def temperature(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            temperature: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment, name="temperature")
+            temperature: ExperimentAttribute = get_or_create(
+                ses, ExperimentAttribute, experiment=experiment, name="temperature"
+            )
             temperature.value = value
             ses.commit()
 
@@ -127,8 +105,12 @@ def time_step(self):
         """Get the time_step of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            time_step = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "time_step").first()
+            time_step = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "time_step")
+                .first()
+            )
         try:
             return time_step.value
         except AttributeError:
@@ -141,7 +123,9 @@ def time_step(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            time_step: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment, name="time_step")
+            time_step: ExperimentAttribute = get_or_create(
+                ses, ExperimentAttribute, experiment=experiment, name="time_step"
+            )
             time_step.value = value
             ses.commit()
 
@@ -150,10 +134,12 @@ def units(self) -> Dict[str, float]:
         """Get the units of the experiment"""
         units = {}
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-            for experiment_data in experiment.experiment_data:
-                if experiment_data.name.startswith('unit_system_'):
-                    unit_name = experiment_data.name.split('unit_system_', 1)[1]
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            for experiment_data in experiment.experiment_attributes:
+                if experiment_data.name.startswith("unit_system_"):
+                    unit_name = experiment_data.name.split("unit_system_", 1)[1]
                     # everything after, max 1 split
                     units[unit_name] = experiment_data.value
 
@@ -167,7 +153,12 @@ def units(self, value: dict):
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
             for unit in value:
-                unit_entry = get_or_create(ses, ExperimentData, experiment=experiment, name=f"unit_system_{unit}")
+                unit_entry = get_or_create(
+                    ses,
+                    ExperimentAttribute,
+                    experiment=experiment,
+                    name=f"unit_system_{unit}",
+                )
                 unit_entry.value = value[unit]
 
             ses.commit()
@@ -177,8 +168,12 @@ def number_of_configurations(self) -> int:
         """Get the time_step of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            number_of_configurations = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "number_of_configurations").first()
+            number_of_configurations = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "number_of_configurations")
+                .first()
+            )
         try:
             return int(number_of_configurations.value)
         except AttributeError:
@@ -191,8 +186,12 @@ def number_of_configurations(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            number_of_configurations: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment,
-                                                                     name="number_of_configurations")
+            number_of_configurations: ExperimentAttribute = get_or_create(
+                ses,
+                ExperimentAttribute,
+                experiment=experiment,
+                name="number_of_configurations",
+            )
             number_of_configurations.value = value
             ses.commit()
 
@@ -201,8 +200,12 @@ def number_of_atoms(self) -> int:
         """Get the time_step of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            number_of_atoms = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "number_of_atoms").first()
+            number_of_atoms = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "number_of_atoms")
+                .first()
+            )
         try:
             return int(number_of_atoms.value)
         except AttributeError:
@@ -215,8 +218,9 @@ def number_of_atoms(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            number_of_atoms: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment,
-                                                            name="number_of_atoms")
+            number_of_atoms: ExperimentAttribute = get_or_create(
+                ses, ExperimentAttribute, experiment=experiment, name="number_of_atoms"
+            )
             number_of_atoms.value = value
             ses.commit()
 
@@ -225,8 +229,12 @@ def sample_rate(self):
         """Get the sample_rate of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            sample_rate = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "sample_rate").first()
+            sample_rate = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "sample_rate")
+                .first()
+            )
         try:
             return sample_rate.value
         except AttributeError:
@@ -239,7 +247,9 @@ def sample_rate(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            sample_rate: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment, name="sample_rate")
+            sample_rate: ExperimentAttribute = get_or_create(
+                ses, ExperimentAttribute, experiment=experiment, name="sample_rate"
+            )
             sample_rate.value = value
             ses.commit()
 
@@ -248,8 +258,12 @@ def volume(self):
         """Get the volume of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            volume = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "volume").first()
+            volume = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "volume")
+                .first()
+            )
         try:
             return volume.value
         except AttributeError:
@@ -262,7 +276,9 @@ def volume(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            volume: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment, name="volume")
+            volume: ExperimentAttribute = get_or_create(
+                ses, ExperimentAttribute, experiment=experiment, name="volume"
+            )
             volume.value = value
             ses.commit()
 
@@ -271,8 +287,12 @@ def box_array(self):
         """Get the sample_rate of the experiment"""
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            box_arrays = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name.startswith("box_array")).all()
+            box_arrays = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name.startswith("box_array"))
+                .all()
+            )
 
             box_array = [box_side.value for box_side in box_arrays]
 
@@ -286,31 +306,36 @@ def box_array(self, value):
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
             for idx, box_length in enumerate(value):
-                sample_rate: ExperimentData = get_or_create(
-                    ses, ExperimentData, experiment=experiment, name=f"box_array_{idx}")
+                sample_rate: ExperimentAttribute = get_or_create(
+                    ses,
+                    ExperimentAttribute,
+                    experiment=experiment,
+                    name=f"box_array_{idx}",
+                )
                 sample_rate.value = box_length
             ses.commit()
 
     @property
     def species(self):
-        species_dict = {}
-        with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-            for species in experiment.species:
-                species_dict.update({
-                    species.name: {
-                        "indices": species.indices,
-                        "mass": species.mass,
-                        "charge": species.charge,
-                        "particle_density": species.particle_density,
-                        "molar_fraction": species.molar_fraction
-                    }
-                })
-
-        return species_dict
+        """Get species
+
+        Returns
+        -------
+
+        dict:
+            A dictionary of species such as {Li: {indices: [1, 2, 3], mass: [12.0], charge: [0]}}
+        """
+        if self._species is None:
+            with self.project.session as ses:
+                experiment = (
+                    ses.query(Experiment).filter(Experiment.name == self.name).first()
+                )
+                self._species = experiment.get_species()
+
+        return self._species
 
     @species.setter
-    def species(self, value):
+    def species(self, value: dict):
         """
 
         Parameters
@@ -325,26 +350,48 @@ def species(self, value):
         """
         if value is None:
             return
+        self._species = None
         with self.project.session as ses:
-            for species_name in value:
-                experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-                species = get_or_create(ses, Species, name=species_name, experiment=experiment)
-                for species_attr, species_values in value[species_name].items():
-                    try:
-                        for idx, species_value in enumerate(species_values):
-                            _ = get_or_create(
-                                ses, SpeciesData,
-                                name=species_attr, species=species, value=species_value
-                            )
-                    except TypeError:
-                        # e.g., float or int values that are not iterable
-                        if species_values is not None:
-                            log.warning(f"Updating {species_attr} with {species_values}")
-                            _ = get_or_create(
-                                ses, SpeciesData,
-                                name=species_attr, species=species, value=species_values
-                            )
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            for species_name, species_data in value.items():
+                species = get_or_create(
+                    ses, db.ExperimentSpecies, name=species_name, experiment=experiment
+                )
+                species.data = species_data
+            ses.commit()
 
+    @property
+    def molecules(self):
+        """Get the molecules dict"""
+        if self._molecules is None:
+            with self.project.session as ses:
+                experiment = (
+                    ses.query(Experiment).filter(Experiment.name == self.name).first()
+                )
+                self._molecules = experiment.get_molecules()
+        return self._molecules
+
+    @molecules.setter
+    def molecules(self, value):
+        """Save the molecules dict to the database"""
+        if value is None:
+            return
+        self._molecules = None
+        with self.project.session as ses:
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            for molecule_name, molecule_data in value.items():
+                molecule = get_or_create(
+                    ses,
+                    db.ExperimentSpecies,
+                    name=molecule_name,
+                    experiment=experiment,
+                    molecule=True
+                )
+                molecule.data = molecule_data
             ses.commit()
 
     @property
@@ -358,10 +405,12 @@ def property_groups(self):
         """
         property_groups = {}
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-            for experiment_data in experiment.experiment_data:
-                if experiment_data.name.startswith('property_group_'):
-                    property_group = experiment_data.name.split('property_group_', 1)[1]
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            for experiment_data in experiment.experiment_attributes:
+                if experiment_data.name.startswith("property_group_"):
+                    property_group = experiment_data.name.split("property_group_", 1)[1]
                     # everything after, max 1 split
                     group_values = property_groups.get(property_group, [])
                     group_values.append(int(experiment_data.value))
@@ -375,11 +424,18 @@ def property_groups(self, value):
         if value is None:
             return
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
             for group in value:
                 for group_value in value[group]:
-                    get_or_create(ses, ExperimentData, name=f"property_group_{group}", value=group_value,
-                                  experiment=experiment)
+                    get_or_create(
+                        ses,
+                        ExperimentAttribute,
+                        name=f"property_group_{group}",
+                        value=group_value,
+                        experiment=experiment,
+                    )
 
             ses.commit()
 
@@ -394,9 +450,15 @@ def read_files(self):
 
         """
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-            read_files = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "read_file").all()
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            read_files = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(ExperimentAttribute.name == "read_file")
+                .all()
+            )
             read_files = [Path(file.str_value) for file in read_files]
         return read_files
 
@@ -417,17 +479,38 @@ def read_files(self, value):
         if isinstance(value, Path):
             value = value.as_posix()
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-            get_or_create(ses, ExperimentData, name="read_file", str_value=value, experiment=experiment)
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            get_or_create(
+                ses,
+                ExperimentAttribute,
+                name="read_file",
+                str_value=value,
+                experiment=experiment,
+            )
             ses.commit()
 
     @property
     def radial_distribution_function_state(self) -> bool:
-        """Get the radial_distribution_function_state of the experiment"""
+        """Get the radial_distribution_function_state of the experiment
+
+        Returns
+        -------
+        bool:
+            the state of the RDF
+        # TODO this method could potentially be replaced by a quick query of the database
+        """
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            rdf_state = ses.query(ExperimentData).filter(ExperimentData.experiment == experiment).filter(
-                ExperimentData.name == "radial_distribution_function_state").first()
+            rdf_state = (
+                ses.query(ExperimentAttribute)
+                .filter(ExperimentAttribute.experiment == experiment)
+                .filter(
+                    ExperimentAttribute.name == "radial_distribution_function_state"
+                )
+                .first()
+            )
         try:
             return rdf_state.value
         except AttributeError:
@@ -440,8 +523,12 @@ def radial_distribution_function_state(self, value):
             return
         with self.project.session as ses:
             experiment = get_or_create(ses, Experiment, name=self.name)
-            rdf_state: ExperimentData = get_or_create(ses, ExperimentData, experiment=experiment,
-                                                      name="radial_distribution_function_state")
+            rdf_state: ExperimentAttribute = get_or_create(
+                ses,
+                ExperimentAttribute,
+                experiment=experiment,
+                name="radial_distribution_function_state",
+            )
             rdf_state.value = value
             ses.commit()
 
@@ -458,13 +545,19 @@ def simulation_data(self) -> dict:
         """
         simulation_data = {}
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
-            for experiment_data in experiment.experiment_data:
-                if experiment_data.name.startswith('simulation_data_'):
-                    simulation_data_name = experiment_data.name.split('simulation_data_', 1)[1]
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            for experiment_data in experiment.experiment_attributes:
+                if experiment_data.name.startswith("simulation_data_"):
+                    simulation_data_name = experiment_data.name.split(
+                        "simulation_data_", 1
+                    )[1]
                     simulation_data_name = simulation_data_name.split("_")
                     no_list = False
-                    if simulation_data_name[-1] == "nolist":  # check if list or single str/float
+                    if (
+                        simulation_data_name[-1] == "nolist"
+                    ):  # check if list or single str/float
                         no_list = True
                     simulation_data_name = "_".join(simulation_data_name[:-1])
                     # everything after, max 1 split
@@ -505,31 +598,83 @@ def simulation_data(self, value: dict):
         is_nested = False
         for entry in value.values():
             if isinstance(entry, dict):
-                log.warning("Converting nested dict of simulation_data into json_normalized version!")
+                log.warning(
+                    "Converting nested dict of simulation_data into json_normalized version!"
+                )
                 is_nested = True
         if is_nested:
-            value = pd.json_normalize(value).to_dict(orient='records')[0]
+            value = pd.json_normalize(value).to_dict(orient="records")[0]
             log.debug(value)
 
         with self.project.session as ses:
-            experiment = ses.query(Experiment).filter(Experiment.name == self.name).first()
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
             log.warning("Converting all values to float or str")
             for group in value:
                 if isinstance(value[group], list):
                     for idx, group_value in enumerate(value[group]):
-                        entry = get_or_create(ses, ExperimentData, name=f"simulation_data_{group}_{idx}",
-                                              experiment=experiment)
+                        entry = get_or_create(
+                            ses,
+                            ExperimentAttribute,
+                            name=f"simulation_data_{group}_{idx}",
+                            experiment=experiment,
+                        )
                         # TODO consider using a dedicated relationship database instead of two keys in the name?!
                         if isinstance(group_value, str):
                             entry.str_value = group_value
                         else:
                             entry.value = group_value
                 else:
-                    entry = get_or_create(ses, ExperimentData, name=f"simulation_data_{group}_nolist",
-                                          experiment=experiment)
+                    entry = get_or_create(
+                        ses,
+                        ExperimentAttribute,
+                        name=f"simulation_data_{group}_nolist",
+                        experiment=experiment,
+                    )
                     if isinstance(value[group], str):
                         entry.str_value = value[group]
                     else:
                         entry.value = value[group]
 
             ses.commit()
+
+    @property
+    def version(self) -> int:
+        """Get the version of the experiment
+
+        Versioning starts at 0 and can be increased by +1 for every added file
+        """
+        with self.project.session as ses:
+            experiment = (
+                ses.query(Experiment).filter(Experiment.name == self.name).first()
+            )
+            version = (
+                ses.query(db.ExperimentAttribute)
+                .filter(db.ExperimentAttribute.experiment == experiment)
+                .filter(db.ExperimentAttribute.name == "version")
+                .first()
+            )
+
+        if version is None:
+            return 0
+        return int(version.value)
+
+    @version.setter
+    def version(self, value: int):
+        """Update the version of the experiment
+
+        Can be used to differentiate between different experiment versions in calculations
+        """
+        if value is None:
+            return
+        with self.project.session as ses:
+            experiment = get_or_create(ses, Experiment, name=self.name)
+            version = get_or_create(
+                ses,
+                ExperimentAttribute,
+                experiment=experiment,
+                name="version",
+            )
+            version.value = value
+            ses.commit()
diff --git a/mdsuite/database/project_database.py b/mdsuite/database/project_database.py
index 21fab03c..b2231c76 100644
--- a/mdsuite/database/project_database.py
+++ b/mdsuite/database/project_database.py
@@ -71,28 +71,3 @@ def description(self, value: str):
             project = get_or_create(ses, Project, id=self.project_id)
             project.description = value
             ses.commit()
-
-        # self.name = name
-        #
-        # self._experiment_id = None
-    #
-    # @property
-    # def experiment(self) -> Experiment:
-    #     """Write an entry for the Experiment the database
-    #
-    #     Returns
-    #     -------
-    #
-    #     Experiment instance queried from the database
-    #
-    #     """
-    #     if self._experiment_id is None:
-    #         experiment = Experiment(name=self.name)
-    #         with self.session as ses:
-    #             ses.add(experiment)
-    #             ses.commit()
-    #             self._experiment_id = experiment.id
-    #
-    #     with self.session as ses:
-    #         experiment = ses.query(Experiment).get(self._experiment_id)
-    #     return experiment
diff --git a/mdsuite/database/property_database.py b/mdsuite/database/property_database.py
deleted file mode 100644
index 30d6fdf8..00000000
--- a/mdsuite/database/property_database.py
+++ /dev/null
@@ -1,265 +0,0 @@
-"""
-This program and the accompanying materials are made available under the terms of the
-Eclipse Public License v2.0 which accompanies this distribution, and is available at
-https://www.eclipse.org/legal/epl-v20.html
-
-SPDX-License-Identifier: EPL-2.0
-
-Copyright Contributors to the MDSuite Project.
-
-Python module for the properties database.
-"""
-import logging
-import sqlalchemy as sa
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm.session import Session
-from .database_scheme import Base, SystemProperty, Data, Subject
-
-log = logging.getLogger(__name__)
-
-
-class PropertiesDatabase:
-    """
-    A class to control the properties database.
-    """
-
-    def __init__(self, name: str):
-        """
-        Constructor for the PropertiesDatabase class.
-
-        Parameters
-        ----------
-        name : str
-                Name of the database. Should be the full path to the name.
-        """
-        self.name = name
-        self.engine = sa.create_engine(f"sqlite+pysqlite:///{self.name}", echo=False, future=True)
-
-        # self.engine = sa.create_engine(f"sqlite:///:memory:", echo=True)
-        self.Session: sessionmaker
-
-        self.Base = Base
-
-        self.get_session()
-        self.build_database()
-
-    def get_session(self):
-        """Create a session"""
-        log.debug('Creating sessionmaker')
-        self.Session = sessionmaker(bind=self.engine, future=True)
-
-    def build_database(self):
-        """Create the database scheme"""
-        log.debug('Creating Database if not existing')
-        self.Base.metadata.create_all(self.engine)
-
-    @staticmethod
-    def _build_subject_query(subjects, query, ses):
-        """Query the one->many subjects relationship
-
-        Parameters
-        ----------
-        subjects: list
-            List of the subjects to query
-        query:
-            The query objects
-        ses:
-            session object
-        """
-
-        for subject in subjects:
-            query = query.filter(SystemProperty.subjects.any(subject=subject))
-        # select the samples where the subject conditions are full filled and connect them with "and" (multiple filters)
-
-        subject_objs = ses.query(Subject.id).filter(Subject.subject.in_(subjects)).distinct()
-        # get all subjects where the subject is in the given list
-
-        query = query.filter(~SystemProperty.subjects.any(
-            Subject.id.notin_(subject_objs)  # remove all, that have additional subjects in their query
-        ))
-
-        return query
-
-    def _check_row_existence(self, parameters: dict):
-        """
-        Check if a row exists.
-
-        Parameters
-        ----------
-        parameters : dict
-                Parameters to be used in the addition, i.e.
-                {"Analysis": "Green_Kubo_Self_Diffusion", "Subject": "Na", "data_range": 500, "data": 1.8e-9}
-        Returns
-        -------
-        result : bool
-                True or False depending on existence.
-        """
-        log.debug(f'Check if row for {parameters.keys()} exists')
-        with self.Session() as ses:
-            ses: Session
-
-            query = ses.query(SystemProperty)
-
-            if parameters.get("data_range") is not None:
-                query = query.filter_by(data_range=parameters['data_range'])
-            if parameters.get("Analysis") is not None:
-                query = query.filter_by(analysis=parameters['Analysis'])
-            if parameters.get("information") is not None:
-                query = query.filter_by(information=parameters['information'])
-
-            log.debug(f"check, without subjects: {query.all()}")
-
-            if parameters.get('subjects') is not None:
-                query = self._build_subject_query(parameters['subjects'], query, ses)
-            query = query.all()
-
-        log.debug(f'Check yielded {query}')
-        return len(query) > 0
-
-    def _delete_duplicate_rows(self, parameters: dict):
-        """
-        Delete duplicate rows
-
-        Parameters
-        ----------
-        parameters : dict
-                Parameters to be used in the addition, i.e.
-                {"Analysis": "Green_Kubo_Self_Diffusion", "Subject": "Na", "data_range": 500, "data": 1.8e-9}
-        Returns
-        -------
-        result : bool
-                True or False depending on existence.
-        """
-
-        log.debug(f"Parameters: {parameters.keys()}")
-
-        with self.Session() as ses:
-            ses: Session
-
-            query = ses.query(SystemProperty).filter_by(
-                data_range=parameters['data_range'],
-                analysis=parameters['Analysis'],
-            )
-            if parameters.get("information") is not None:
-                query = query.filter_by(information=parameters.get("information"))
-
-            if parameters.get('subjects') is not None:
-                query = self._build_subject_query(parameters['subjects'], query, ses)
-
-            system_properties = query.all()
-
-            log.debug(f'Removing {system_properties} from database')
-            for system_property in system_properties:
-                ses.delete(system_property)
-
-            ses.commit()
-
-    def add_data(self, parameters: dict, delete_duplicate: bool = True):
-        """
-        Add data to the database
-
-        Parameters
-        ----------
-        parameters : dict
-                Parameters to be used in the addition, i.e.
-                {"Analysis": "Green_Kubo_Self_Diffusion", "Subject": "Na", "data_range": 500, "data": 1.8e-9}
-        delete_duplicate : bool
-                If true, duplicate entries will be deleted.
-        Returns
-        -------
-        Updates the sql database
-        """
-        # allow subjects and Subject
-        if parameters.get("subjects") is None:
-            try:
-                parameters['subjects'] = parameters['Subject']
-            except KeyError:
-                raise KeyError('Please add the key "subjects" to your calculator')
-
-        if delete_duplicate:
-            self._delete_duplicate_rows(parameters)
-        else:
-            if self._check_row_existence(parameters):
-                log.info("Note, an entry with these parameters already exists in the database.")
-
-        log.debug(f'Adding {parameters.get("Property")} to database!')
-
-        with self.Session() as ses:
-            ses: Session
-
-            # Create a Data instance to store the value
-            # TODO use **parameters instead with parameters.pop
-
-            try:  # check if it is a list
-                data = [Data(**param) for param in parameters['data']]  # param is a dict
-            except TypeError:
-                try:  # check if it is a dictionary with keys [x, y, z, uncertainty]
-                    data = [Data(parameters['data'])]
-                except TypeError:
-                    data = [Data(x=parameters['data'])]
-
-            try:
-                subjects = [Subject(subject=param) for param in parameters['subjects']]  # param is a string
-            except TypeError:
-                subjects = [Subject(subject=parameters['subjects'])]  # param is a string
-
-            log.debug(f"Subjects are: {subjects}")
-
-            # Create s SystemProperty instance to store the values
-            system_property = SystemProperty(
-                property=parameters['Property'],
-                analysis=parameters['Analysis'],
-                data_range=parameters['data_range'],
-                subjects=subjects,
-                data=data,
-                information=parameters.get("information")
-            )
-
-            log.debug(f"Created: {system_property}")
-
-            # add to the session
-            ses.add(system_property)
-
-            # commit to the database
-            ses.commit()
-
-        log.debug("Values successfully written to database. Closed database session.")
-
-    def load_data(self, parameters: dict) -> list:
-        """
-        Load some data from the database.
-        Parameters
-        ----------
-        parameters : dict
-                Parameters to be used in the addition, e.g.
-
-                .. code-block::
-
-                   {"Analysis": "Green_Kubo_Self_Diffusion", "Subject": "Na", "data_range": 500}
-
-        Returns
-        -------
-        output : list
-                All rows matching the parameters represented as a dictionary.
-        """
-
-        log.debug(f'querying {parameters} from database')
-
-        with self.Session() as ses:
-            ses: Session
-
-            subjects = parameters.pop('subjects', None)
-            query = ses.query(SystemProperty).filter_by(**parameters)
-
-            if subjects is not None:
-                query = self._build_subject_query(subjects, query, ses)
-
-            system_properties = query.all()
-
-            # Iterate over data so that the information gets pulled from the database
-            # Note: If you keep the session open, this would not be necessary
-            for system_property in system_properties:
-                _ = system_property.data
-                _ = system_property.subjects
-
-        return system_properties
diff --git a/mdsuite/database/scheme.py b/mdsuite/database/scheme.py
index 3522e558..970c4b21 100644
--- a/mdsuite/database/scheme.py
+++ b/mdsuite/database/scheme.py
@@ -8,16 +8,41 @@
 
 Description: Collection of all used SQLAlchemy Database schemes
 """
+import logging
+
 from sqlalchemy.orm import declarative_base
-from sqlalchemy import Column, Integer, String, Float, ForeignKey, Boolean
+from sqlalchemy import Column, Integer, String, Float, ForeignKey, Boolean, Table
 from sqlalchemy.orm import relationship
-from sqlalchemy.orm.exc import DetachedInstanceError
 
-import numpy as np
+from .types import MutableDict, JSONEncodedDict
+
+
+log = logging.getLogger(__name__)
 
 Base = declarative_base()
 
 
+class SpeciesAssociation(Base):
+    """Connection between Computations and Experiment Species
+
+    This table is required to be defined specifically, because we need add the count, e.g. Na - Na would
+    otherwise only appear as Na.
+    """
+    __tablename__ = "species_association"
+    computation_results_id = Column(ForeignKey('computation_results.id'), primary_key=True)
+    experiment_species_id = Column(ForeignKey('experiment_species.id'), primary_key=True)
+
+    count = Column(Integer, default=1)  # how often a species occurs, e.g. Na - Na - Cl ADF would be 2, 1
+
+    computation_result = relationship("ComputationResult", back_populates="species")
+    species = relationship("ExperimentSpecies", back_populates="computation_result")
+
+    @property
+    def name(self):
+        """Get the name of the species"""
+        return self.species.name
+
+
 class Project(Base):
     __tablename__ = "projects"
     id = Column(Integer, primary_key=True)
@@ -30,7 +55,8 @@ class Experiment(Base):
     """
     Class for the experiment table associated with the Project table.
     """
-    __tablename__ = 'experiments'
+
+    __tablename__ = "experiments"
 
     id = Column(Integer, primary_key=True)
     name = Column(String)
@@ -38,15 +64,16 @@ class Experiment(Base):
     active = Column(Boolean, default=False)
     # Whether this experiment is currently loaded in the project class
 
-    project_id = Column(Integer, ForeignKey('projects.id', ondelete="CASCADE"))
+    project_id = Column(Integer, ForeignKey("projects.id", ondelete="CASCADE"))
     project = relationship("Project")
 
-    experiment_data = relationship("ExperimentData",
-                                   cascade='all, delete',
-                                   back_populates='experiment')
+    experiment_attributes = relationship(
+        "ExperimentAttribute", cascade="all, delete", back_populates="experiment"
+    )
 
     computations = relationship("Computation")
-    species = relationship("Species")
+
+    species = relationship("ExperimentSpecies")
 
     def __repr__(self):
         """
@@ -59,9 +86,30 @@ def __repr__(self):
         """
         return f"{self.id}: {self.name}"
 
+    def get_species(self) -> dict:
+        """Get the species information for the experiment"""
+        species_dict = {}
+        for species in self.species:
+            if species.molecule:
+                continue
+            species: ExperimentSpecies
+            species_dict[species.name] = species.data
+
+        return species_dict
+
+    def get_molecules(self) -> dict:
+        """Get the molecules information for the experiment"""
+        molecule_dict = {}
+        for molecule in self.species:
+            if molecule.molecule:
+                molecule: ExperimentSpecies
+                molecule_name = molecule.name
+                molecule_dict[molecule_name] = molecule.data
 
-# TODO consider renaming ExperimentAttributes in accordance to ComputationAttributes
-class ExperimentData(Base):
+        return molecule_dict
+
+
+class ExperimentAttribute(Base):
     """
     Class for the experiment data table.
 
@@ -78,15 +126,16 @@ class ExperimentData(Base):
     str_value : str
             String value of the property.
     """
-    __tablename__ = 'experiment_data'
+
+    __tablename__ = "experiment_attributes"
 
     id = Column(Integer, primary_key=True)
     name = Column(String)
     value = Column(Float, nullable=True)
     str_value = Column(String, nullable=True)
 
-    experiment_id = Column(Integer, ForeignKey('experiments.id', ondelete="CASCADE"))
-    experiment = relationship("Experiment", back_populates='experiment_data')
+    experiment_id = Column(Integer, ForeignKey("experiments.id", ondelete="CASCADE"))
+    experiment = relationship("Experiment", back_populates="experiment_attributes")
 
     def __repr__(self):
         if self.value is not None:
@@ -94,90 +143,49 @@ def __repr__(self):
         elif self.str_value is not None:
             return self.str_value
         else:
-            return None
-
-
-class Species(Base):
-    __tablename__ = 'species'
-    id = Column(Integer, primary_key=True)
-    name = Column(String)
-
-    experiment_id = Column(Integer, ForeignKey('experiments.id', ondelete="CASCADE"))
-    experiment = relationship("Experiment", back_populates='species')
+            return f"{self.name}"
 
-    species_data = relationship("SpeciesData")
 
-    @property
-    def indices(self) -> list:
-        indices = []
-        for species_data in self.species_data:
-            if species_data.name == "indices":
-                indices.append(int(species_data.value))
-        return indices
-
-    @property
-    def mass(self) -> list:
-        mass = []
-        for species_data in self.species_data:
-            if species_data.name == "mass":
-                mass.append(species_data.value)
-        return mass
+class ExperimentSpecies(Base):
+    """Table for storing species information
 
-    @property
-    def charge(self) -> list:
-        charge = []
-        for species_data in self.species_data:
-            if species_data.name == "charge":
-                charge.append(species_data.value)
-        return charge
+    This table is used to store species and molecule information that can be related to a specific experiment
 
-    @property
-    def particle_density(self) -> float:
-        for species_data in self.species_data:
-            if species_data.name == "particle_density":
-                return species_data.value
-        return None
+    """
 
-    @property
-    def molar_fraction(self) -> float:
-        for species_data in self.species_data:
-            if species_data.name == "molar_fraction":
-                return species_data.value
-        return None
+    __tablename__ = "experiment_species"
+    id = Column(Integer, primary_key=True)
 
+    name = Column(String)
+    data = Column(MutableDict.as_mutable(JSONEncodedDict))
+    molecule = Column(Boolean, default=False)
 
-#      TODO consider adding species data as a function here!
+    experiment_id = Column(Integer, ForeignKey("experiments.id", ondelete="CASCADE"))
+    experiment = relationship("Experiment", back_populates="species")
 
+    computation_result = relationship("SpeciesAssociation", back_populates="species")
 
-class SpeciesData(Base):
-    __tablename__ = 'species_data'
-    id = Column(Integer, primary_key=True)
-    name = Column(String)
-    value = Column(Float, nullable=True)
-    str_value = Column(String, nullable=True)
-
-    species_id = Column(Integer, ForeignKey('species.id', ondelete="CASCADE"))
-    species = relationship("Species", back_populates='species_data')
+    def __repr__(self):
+        return f"{self.name}_obj"
 
 
 class Computation(Base):
-    """
-    Class for the computation table.
-    """
-    __tablename__ = 'computations'
+    """Class for the computation table."""
+
+    __tablename__ = "computations"
 
     id = Column(Integer, primary_key=True)
     name = Column(String, default="Computation")
 
-    experiment_id = Column(Integer, ForeignKey('experiments.id', ondelete="CASCADE"))
+    experiment_id = Column(Integer, ForeignKey("experiments.id", ondelete="CASCADE"))
     experiment = relationship("Experiment")
 
-    computation_attributes = relationship('ComputationAttribute',
-                                          cascade='all, delete',
-                                          back_populates='computation')
-    computation_data = relationship('ComputationData',
-                                    cascade='all, delete',
-                                    back_populates='computation')
+    computation_attributes = relationship(
+        "ComputationAttribute", cascade="all, delete", back_populates="computation"
+    )
+    computation_results = relationship(
+        "ComputationResult", cascade="all, delete", back_populates="computation", lazy=True
+    )
 
     def __repr__(self):
         """
@@ -196,33 +204,57 @@ def data_dict(self) -> dict:
 
         Returns
         -------
-        data_dict: dict
-            A dictionary of the type {x: [1, 2, 3], y: [5, 6, 7], ...}
-            where the keys are defined by computation_data.dimension
+        species_dict: dict
+            A dictionary of the type
+            {
+                Li:
+                    {
+                        a: 1.2,
+                        uncert: 0.1,
+                        time: [1, 2, 3, ],
+                        msd: [0.1, 0.3, 0.7]
+                    },
+                Cl:
+                    {
+                        a: 1.2,
+                        uncert: 0.1,
+                        time: [1, 2, 3, ],
+                        msd: [0.1, 0.3, 0.7]
+                    },
+            }
+            where the keys are defined by species (multiple species are joined by "_") and the dimension argument
+            of the computation_data
 
         """
-        data_dict = {}
-        for data in self.computation_data:
-            data_list = data_dict.get(data.dimension, [])
-            data_list.append(data.value)
-            data_dict[data.dimension] = data_list
-
-        return data_dict
+        species_dict = {}
+        for result in self.computation_results:
+            result: ComputationResult
+            species_keys_list = []
+            for species_associate in result.species:
+                species_associate: SpeciesAssociation
+                species_keys_list += species_associate.count * [species_associate.species.name]
+            species_keys = "_".join(species_keys_list)
+            if species_keys == "":
+                species_keys = "System"
+            # iterating over associates
+            species_dict[species_keys] = result.data
+
+        return species_dict
 
     @property
     def data_range(self) -> int:
         """Get the data_range stored in computation_attributes"""
         for comp_attr in self.computation_attributes:
             if comp_attr.name == "data_range":
-                return int(comp_attr.value)
+                return int(comp_attr.str_value)
 
     @property
     def subjects(self) -> list:
         """Get the subjects stored in computation_attributes"""
+        log.warning("This function will be depreciated!")
         subjects = []
-        for comp_attr in self.computation_attributes:
-            if comp_attr.name == "subject":
-                subjects.append(comp_attr.str_value)
+        for x in self.computation_results:
+            subjects.append(x.species.species.name)
         return subjects
 
 
@@ -230,7 +262,8 @@ class ComputationAttribute(Base):
     """
     Class for the meta data of a computation.
     """
-    __tablename__ = 'computation_attributes'
+
+    __tablename__ = "computation_attributes"
 
     # Table data
     id = Column(Integer, primary_key=True)
@@ -239,28 +272,27 @@ class ComputationAttribute(Base):
     str_value = Column(String, nullable=True)
 
     # Relation data
-    computation_id = Column(Integer, ForeignKey('computations.id', ondelete="CASCADE"))
-    computation = relationship("Computation", back_populates='computation_attributes')
+    computation_id = Column(Integer, ForeignKey("computations.id", ondelete="CASCADE"))
+    computation = relationship("Computation", back_populates="computation_attributes")
 
     def __repr__(self):
         return f"{self.name}: {self.value} - {self.str_value}"
 
 
-class ComputationData(Base):
+class ComputationResult(Base):
     """
     raw computation data of a calculation.
     """
-    __tablename__ = 'computation_data'
+
+    __tablename__ = "computation_results"
 
     id = Column(Integer, primary_key=True)
 
-    value = Column(Float)
-    uncertainty = Column(Float, nullable=True)
-    dimension = Column(String)
+    data = Column(MutableDict.as_mutable(JSONEncodedDict))
 
     # Relation data
-    computation_id = Column(Integer, ForeignKey('computations.id', ondelete="CASCADE"))
-    computation = relationship("Computation", back_populates='computation_data')
+    computation_id = Column(Integer, ForeignKey("computations.id", ondelete="CASCADE"))
+    computation = relationship("Computation", back_populates="computation_results")
 
-    def __repr__(self):
-        return f"{self.id}: {self.value} ({self.uncertainty}) - {self.dimension}"
+    # Many <-> Many
+    species = relationship("SpeciesAssociation", back_populates="computation_result")
diff --git a/mdsuite/database/types.py b/mdsuite/database/types.py
new file mode 100644
index 00000000..f1f3a025
--- /dev/null
+++ b/mdsuite/database/types.py
@@ -0,0 +1,72 @@
+"""
+This program and the accompanying materials are made available under the terms of the
+Eclipse Public License v2.0 which accompanies this distribution, and is available at
+https://www.eclipse.org/legal/epl-v20.html
+SPDX-License-Identifier: EPL-2.0
+
+Copyright Contributors to the Zincware Project.
+
+Description: Collection of custom database types
+
+References
+----------
+
+https://docs.sqlalchemy.org/en/14/orm/extensions/mutable.html
+"""
+
+from sqlalchemy.types import TypeDecorator, VARCHAR
+from sqlalchemy.ext.mutable import Mutable
+
+import json
+
+
+class JSONEncodedDict(TypeDecorator):
+    """Represents an immutable structure as a json-encoded string."""
+
+    impl = VARCHAR
+
+    def process_bind_param(self, value: dict, dialect):
+        """Provide a bound value processing function
+
+        Convert a dictionary to a json string and store the string in the database
+        """
+        if value is not None:
+            value = json.dumps(value)
+        return value
+
+    def process_result_value(self, value, dialect) -> dict:
+        """Receive a result-row column value to be converted
+
+        Convert a loaded string from the database into a dict object
+        """
+        if value is not None:
+            value = json.loads(value)
+        return value
+
+
+class MutableDict(Mutable, dict):
+    """Subclassed version of a dictionary used in the database"""
+    @classmethod
+    def coerce(cls, key, value):
+        """Convert plain dictionaries to MutableDict."""
+
+        if not isinstance(value, MutableDict):
+            if isinstance(value, dict):
+                return MutableDict(value)
+
+            # this call will raise ValueError
+            return Mutable.coerce(key, value)
+        else:
+            return value
+
+    def __setitem__(self, key, value):
+        """Detect dictionary set events and emit change events."""
+
+        dict.__setitem__(self, key, value)
+        self.changed()
+
+    def __delitem__(self, key):
+        """Detect dictionary del events and emit change events."""
+
+        dict.__delitem__(self, key)
+        self.changed()
diff --git a/mdsuite/experiment/add_files.py b/mdsuite/experiment/add_files.py
index f915cb0d..0cbb80a8 100644
--- a/mdsuite/experiment/add_files.py
+++ b/mdsuite/experiment/add_files.py
@@ -14,10 +14,8 @@
 from mdsuite.file_io.file_io_dict import dict_file_io
 from mdsuite.file_io.file_read import FileProcessor
 from mdsuite.utils.exceptions import ElementMassAssignedZero
-from mdsuite.database.property_database import PropertiesDatabase
 from mdsuite.utils.meta_functions import join_path
 
-from io import UnsupportedOperation
 from tqdm import tqdm
 from pathlib import Path
 import pubchempy as pcp
@@ -32,6 +30,7 @@ class ExperimentAddingFiles:
     """Parent class that handles the file additions"""
 
     def __init__(self):
+        """Constructor of the ExperimentAddingFiles class"""
         self.database_path: str = ""
         self.memory_requirements = None
         self.number_of_configurations = None
@@ -40,6 +39,7 @@ def __init__(self):
         self.species = None
         self.property_groups = None
         self.read_files = None
+        self.version = None
 
     def add_data(self,
                  trajectory_file: str = None,
@@ -106,9 +106,14 @@ def add_data(self,
                                      flux=flux,
                                      sort=sort)
 
+        self.version += 1
+
         self.build_species_dictionary()
         self.memory_requirements = database.get_memory_information()
 
+        # set at the end, because if something fails, the file was not properly read.
+        self.read_files = trajectory_file
+
     def _check_read_files(self, file_path: str):
         """
         Check if a file has been read before and add it to the hidden file.
@@ -126,7 +131,6 @@ def _check_read_files(self, file_path: str):
         if file_path in self.read_files:
             return True
         else:
-            self.read_files = file_path
             return False
 
     def _load_trajectory_reader(self, file_format, trajectory_file, sort: bool = False):
@@ -208,13 +212,6 @@ def _build_new_database(self, trajectory_reader: FileProcessor, trajectory_file:
                                   n_atoms=self.number_of_atoms,
                                   sort=sort)
 
-        # analysis_database = AnalysisDatabase(name=os.path.join(self.database_path, "analysis_database"))
-        # analysis_database.build_database()
-        property_database = PropertiesDatabase(name=Path(self.database_path, "property_database").as_posix())
-        property_database.build_database()
-
-        # self.save_class()  # Update the class state
-
     def build_species_dictionary(self):
         """
         Add information to the species dictionary
diff --git a/mdsuite/experiment/experiment.py b/mdsuite/experiment/experiment.py
index 93661d16..de60b9db 100644
--- a/mdsuite/experiment/experiment.py
+++ b/mdsuite/experiment/experiment.py
@@ -101,8 +101,8 @@ def __init__(self, project, experiment_name, storage_path='./', time_step=None,
 
         # Added from trajectory file
         self.units = self.units_to_si(units)  # Units used during the simulation.
-        self.species = None  # Species dictionary.
-        self.molecules = {}  # molecules
+        # self.species = None  # Species dictionary.
+        # self.molecules = {}  # molecules
         self.box_array = None  # Box vectors.
         self.dimensions = None  # Dimensionality of the experiment.
 
@@ -142,17 +142,17 @@ class that has all available calculators as properties
         """
         return RunComputation(experiment=self)
 
-    @property
-    def load(self) -> RunComputation:
-        """Method to access the available calculators results
-
-        Returns
-        -------
-        RunComputation:
-            if called, return List[db.Computation]
-
-        """
-        return RunComputation(experiment=self, load_data=True)
+    # @property
+    # def load(self) -> RunComputation:
+    #     """Method to access the available calculators results
+    #
+    #     Returns
+    #     -------
+    #     RunComputation:
+    #         if called, return List[db.Computation]
+    #
+    #     """
+    #     return RunComputation(experiment=self, load_data=True)
 
     def __repr__(self):
         return f"exp_{self.name}"
@@ -366,19 +366,21 @@ def run_visualization(
     #         self.species[new_name] = self.species[old_name]  # update dict
     #         del self.species[old_name]  # remove old entry
     #
-    # def set_charge(self, element: str, charge: float):
-    #     """
-    #     Set the charge/s of an element
-    #
-    #     Parameters
-    #     ----------
-    #     element : str
-    #             Name of the element whose charge you want to change
-    #     charge : list
-    #             New charge/s of the element
-    #     """
-    #     self.species[element]['charge'] = [charge]  # update entry
-    #     self.save_class()
+    def set_charge(self, element: str, charge: float):
+        """
+        Set the charge/s of an element
+
+        Parameters
+        ----------
+        element : str
+                Name of the element whose charge you want to change
+        charge : list
+                New charge/s of the element
+        """
+
+        species = self.species
+        species[element]['charge'] = [charge]  # update entry
+        self.species = species
     #
     # def set_mass(self, element: str, mass: float):
     #     """
diff --git a/mdsuite/file_io/extxyz_trajectory_reader.py b/mdsuite/file_io/extxyz_trajectory_reader.py
index 43581fa4..e4725738 100644
--- a/mdsuite/file_io/extxyz_trajectory_reader.py
+++ b/mdsuite/file_io/extxyz_trajectory_reader.py
@@ -12,6 +12,7 @@
 Summary
 -------
 """
+import logging
 from typing import Union, List, Dict, Tuple
 import numpy as np
 from mdsuite.file_io.trajectory_files import TrajectoryFile
@@ -19,6 +20,8 @@
 from mdsuite.utils.meta_functions import line_counter
 from mdsuite.utils.meta_functions import optimize_batch_size
 
+log = logging.getLogger(__name__)
+
 var_names = {
     "Positions": 'pos',
     "Velocities": 'vel',
@@ -290,6 +293,7 @@ def process_trajectory_file(self, update_class: bool = True, rename_cols: dict =
         species_summary, box, property_groups, line_length = self._get_species_information(number_of_atoms)
 
         if update_class:
+            log.debug("Updating class")
             self.experiment.batch_size = batch_size
             self.experiment.dimensions = get_dimensionality(box)
             self.experiment.box_array = box
diff --git a/mdsuite/file_io/trajectory_files.py b/mdsuite/file_io/trajectory_files.py
index eb266342..281139cf 100644
--- a/mdsuite/file_io/trajectory_files.py
+++ b/mdsuite/file_io/trajectory_files.py
@@ -117,14 +117,16 @@ def build_file_structure(self, batch_size: int = None):
         if batch_size is None:
             batch_size = self.experiment.batch_size
 
-        for item in self.experiment.species:
+        species = self.experiment.species
+
+        for item in species:
             if self.sort:
-                positions = np.array(self.experiment.species[item]['indices'])
+                positions = np.array(species[item]['indices'])
             else:
-                positions = np.array([np.array(self.experiment.species[item]['indices']) + i *
+                positions = np.array([np.array(species[item]['indices']) + i *
                                       self.experiment.number_of_atoms -
                                       self.header_lines for i in range(batch_size)]).flatten()
-            length = len(self.experiment.species[item]['indices'])
+            length = len(species[item]['indices'])
             for observable in self.experiment.property_groups:
                 path = join_path(item, observable)
                 columns = self.experiment.property_groups[observable]
diff --git a/mdsuite/memory_management/memory_manager.py b/mdsuite/memory_management/memory_manager.py
index 8c20d8fe..d05eaf9c 100644
--- a/mdsuite/memory_management/memory_manager.py
+++ b/mdsuite/memory_management/memory_manager.py
@@ -149,8 +149,7 @@ def get_batch_size(self, system: bool = False) -> tuple:
                 loaded to collect unused tensor_values.
         """
         if self.data_path is []:
-            log.warning("No tensor_values have been requested.")
-            sys.exit(1)
+            raise ValueError("No tensor_values have been requested.")
 
         per_configuration_memory: float = 0
         for item in self.data_path:
diff --git a/mdsuite/project/project.py b/mdsuite/project/project.py
index 5251943f..393b9f8c 100644
--- a/mdsuite/project/project.py
+++ b/mdsuite/project/project.py
@@ -272,19 +272,19 @@ class that has all available calculators as properties
         """
         return RunComputation(experiments=[x for x in self.experiments.values()])
 
-    @property
-    def load(self) -> RunComputation:
-        """Method to access the available calculators results
-
-        Returns
-        -------
-        RunComputation:
-            if called, return List[db.Computation]
-
-        """
-        return RunComputation(
-            experiments=[x for x in self.experiments.values()], load_data=True
-        )
+    # @property
+    # def load(self) -> RunComputation:
+    #     """Method to access the available calculators results
+    #
+    #     Returns
+    #     -------
+    #     RunComputation:
+    #         if called, return List[db.Computation]
+    #
+    #     """
+    #     return RunComputation(
+    #         experiments=[x for x in self.experiments.values()], load_data=True
+    #     )
 
     # def get_results(self, key_to_find):
     #     """
diff --git a/mdsuite/transformations/map_molecules.py b/mdsuite/transformations/map_molecules.py
index 91feed6e..c33028d6 100644
--- a/mdsuite/transformations/map_molecules.py
+++ b/mdsuite/transformations/map_molecules.py
@@ -208,6 +208,7 @@ def _map_molecules(self):
             data_structure = self._prepare_database_entry(item, len(self.adjacency_graphs[item]['molecules']))
             path_list = [join_path(s, 'Unwrapped_Positions') for s in species]
             self._prepare_monitors(data_path=path_list)
+            # TODO for #338
             scaling_factor = self.reference_molecules[item]['mass']
             self.experiment.molecules[item] = {}
             self.experiment.molecules[item]['indices'] = [i for i in range(len(self.adjacency_graphs[item]['molecules']))]
diff --git a/mdsuite/utils/config.py b/mdsuite/utils/config.py
index bec03070..62e35b32 100644
--- a/mdsuite/utils/config.py
+++ b/mdsuite/utils/config.py
@@ -15,6 +15,7 @@
 class Config:
     """Collection of MDSuite configurations"""
     jupyter: bool = False
+    GPU: bool = False
 
 
 config = Config()


Test patch:
diff --git a/CI/integration_tests/calculators/_test_einstein_distinct_diffusion_coefficients.py b/CI/integration_tests/calculators/__test_einstein_distinct_diffusion_coefficients.py
similarity index 100%
rename from CI/integration_tests/calculators/_test_einstein_distinct_diffusion_coefficients.py
rename to CI/integration_tests/calculators/__test_einstein_distinct_diffusion_coefficients.py
diff --git a/CI/integration_tests/calculators/_test_green_kubo_distinct_diffusion_coefficients.py b/CI/integration_tests/calculators/__test_green_kubo_distinct_diffusion_coefficients.py
similarity index 100%
rename from CI/integration_tests/calculators/_test_green_kubo_distinct_diffusion_coefficients.py
rename to CI/integration_tests/calculators/__test_green_kubo_distinct_diffusion_coefficients.py
diff --git a/CI/integration_tests/calculators/_test_structure_factor.py b/CI/integration_tests/calculators/__test_structure_factor.py
similarity index 100%
rename from CI/integration_tests/calculators/_test_structure_factor.py
rename to CI/integration_tests/calculators/__test_structure_factor.py
diff --git a/CI/integration_tests/calculators/test_coordination_numbers.py b/CI/integration_tests/calculators/_test_coordination_numbers.py
similarity index 61%
rename from CI/integration_tests/calculators/test_coordination_numbers.py
rename to CI/integration_tests/calculators/_test_coordination_numbers.py
index 295a7db5..5beaceb8 100644
--- a/CI/integration_tests/calculators/test_coordination_numbers.py
+++ b/CI/integration_tests/calculators/_test_coordination_numbers.py
@@ -62,32 +62,19 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_cn_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the CN called from the project class"""
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    # project.run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
-    project.run.CoordinationNumbers(plot=False)
+    computation = project.run.CoordinationNumbers()
 
-    data_dict = project.load.CoordinationNumbers(plot=False)["NaCl"][0].data_dict
+    keys = project.run.CoordinationNumbers.result_keys
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
-
-
-def test_cn_experiment(traj_files, true_values, tmp_path):
-    """Test the CN called from the experiment class"""
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.CoordinationNumbers(plot=False)
-
-    data_dict = project.experiments['NaCl'].load.CoordinationNumbers(plot=False)[0].data_dict
-
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
+    data_dict = computation["NaCl"].data_dict["Na_Na"]
+    # TODO issue that  the CN outputs will only contain a single cn, not both
+    #  fix later
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'])
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['y'])
+    np.testing.assert_array_almost_equal(data_dict[keys[2]], true_values['uncertainty'])
diff --git a/CI/integration_tests/calculators/data/green_kubo_ionic_conductivity.json b/CI/integration_tests/calculators/data/green_kubo_ionic_conductivity.json
index 5870fa43..19261166 100644
--- a/CI/integration_tests/calculators/data/green_kubo_ionic_conductivity.json
+++ b/CI/integration_tests/calculators/data/green_kubo_ionic_conductivity.json
@@ -1,1 +1,1 @@
-{"x": [2147684.0253798524], "uncertainty": [0.0]}
\ No newline at end of file
+{"x": [3619613.919685], "uncertainty": [0.0]}
\ No newline at end of file
diff --git a/CI/integration_tests/calculators/test_angular_distribution_function.py b/CI/integration_tests/calculators/test_angular_distribution_function.py
index cbe78261..48c2e15c 100644
--- a/CI/integration_tests/calculators/test_angular_distribution_function.py
+++ b/CI/integration_tests/calculators/test_angular_distribution_function.py
@@ -62,29 +62,17 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_adf_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the ADF called from the project class"""
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.AngularDistributionFunction(plot=False)
+    computation = project.run.AngularDistributionFunction()
+    keys = project.run.AngularDistributionFunction.result_series_keys
 
-    data_dict = project.load.AngularDistributionFunction()["NaCl"][0].data_dict
+    data_dict = computation["NaCl"].data_dict['Na_Na_Na']
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=2)
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'], decimal=2)
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'], decimal=2)
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['y'], decimal=2)
 
-
-def test_adf_experiment(traj_files, true_values, tmp_path):
-    """Test the ADF called from the experiment class"""
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.AngularDistributionFunction(plot=False)
-
-    data_dict = project.experiments['NaCl'].load.AngularDistributionFunction()[0].data_dict
-
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=2)
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'], decimal=2)
diff --git a/CI/integration_tests/calculators/test_einstein_diffusion_coefficients.py b/CI/integration_tests/calculators/test_einstein_diffusion_coefficients.py
index ae9db57b..92476700 100644
--- a/CI/integration_tests/calculators/test_einstein_diffusion_coefficients.py
+++ b/CI/integration_tests/calculators/test_einstein_diffusion_coefficients.py
@@ -69,24 +69,13 @@ def test_project(traj_files, true_values, tmp_path):
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.EinsteinDiffusionCoefficients(plot=False, data_range=300, correlation_time=1)
+    computation = project.run.EinsteinDiffusionCoefficients(data_range=300, correlation_time=1)
 
-    data_dict = project.load.EinsteinDiffusionCoefficients()["NaCl"][0].data_dict
+    keys = project.run.EinsteinDiffusionCoefficients.result_keys
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
+    data_dict = computation["NaCl"].data_dict["Na"]
 
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'])
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['uncertainty'])
 
-def test_rdf_experiment(traj_files, true_values, tmp_path):
-    """Test the EinsteinDiffusionCoefficients called from the experiment class"""
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.EinsteinDiffusionCoefficients(plot=False, data_range=300,
-                                                                              correlation_time=1)
-
-    data_dict = project.experiments['NaCl'].load.EinsteinDiffusionCoefficients()[0].data_dict
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
diff --git a/CI/integration_tests/calculators/test_einstein_helfand_ionic_conductivity.py b/CI/integration_tests/calculators/test_einstein_helfand_ionic_conductivity.py
index cbb793a6..331d002b 100644
--- a/CI/integration_tests/calculators/test_einstein_helfand_ionic_conductivity.py
+++ b/CI/integration_tests/calculators/test_einstein_helfand_ionic_conductivity.py
@@ -63,7 +63,7 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_ehic_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the Einstein_Helfand_Ionic_Conductivity called from the project class
 
     Notes
@@ -74,28 +74,12 @@ def test_ehic_project(traj_files, true_values, tmp_path):
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.EinsteinHelfandIonicConductivity(plot=False)
+    computation = project.run.EinsteinHelfandIonicConductivity()
+    keys = project.run.EinsteinHelfandIonicConductivity.result_keys
 
-    data_dict = project.load.EinsteinHelfandIonicConductivity()["NaCl"][0].data_dict
+    data_dict = computation["NaCl"].data_dict['System']
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=-5)
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'], decimal=-5)
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'], decimal=-5)
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['uncertainty'], decimal=-5)
 
 
-def test_ehic_experiment(traj_files, true_values, tmp_path):
-    """Test the Einstein_Helfand_Ionic_Conductivity called from the experiment class
-
-    Notes
-    ------
-    Test uncertainty is very high!
-    """
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.EinsteinHelfandIonicConductivity(plot=False)
-
-    data_dict = project.experiments['NaCl'].load.EinsteinHelfandIonicConductivity()[0].data_dict
-
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=-5)
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'], decimal=-5)
diff --git a/CI/integration_tests/calculators/test_green_kubo_ionic_conductivity.py b/CI/integration_tests/calculators/test_green_kubo_ionic_conductivity.py
index d0bd8f23..cbe8e393 100644
--- a/CI/integration_tests/calculators/test_green_kubo_ionic_conductivity.py
+++ b/CI/integration_tests/calculators/test_green_kubo_ionic_conductivity.py
@@ -63,31 +63,19 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_gkic_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the green_kubo_ionic_conductivity called from the project class
     """
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.GreenKuboIonicConductivity(plot=False)
+    key = project.run.GreenKuboIonicConductivity.result_keys
+    computation = project.run.GreenKuboIonicConductivity()
 
-    data_dict = project.load.GreenKuboIonicConductivity()["NaCl"][0].data_dict
+    data_dict = computation["NaCl"].data_dict["System"]
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
+    np.testing.assert_array_almost_equal(data_dict[key[0]], true_values['x'])
+    np.testing.assert_array_almost_equal(data_dict[key[1]], true_values['uncertainty'])
 
 
-def test_gkic_experiment(traj_files, true_values, tmp_path):
-    """Test the green_kubo_ionic_conductivity called from the experiment class
-    """
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.GreenKuboIonicConductivity(plot=False)
-
-    data_dict = project.experiments['NaCl'].load.GreenKuboIonicConductivity()[0].data_dict
-
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
diff --git a/CI/integration_tests/calculators/test_green_kubo_self_diffusion_coefficients.py b/CI/integration_tests/calculators/test_green_kubo_self_diffusion_coefficients.py
index 3e53b786..e9a6df46 100644
--- a/CI/integration_tests/calculators/test_green_kubo_self_diffusion_coefficients.py
+++ b/CI/integration_tests/calculators/test_green_kubo_self_diffusion_coefficients.py
@@ -62,7 +62,7 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_gksd_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the green_kubo_self_diffusion called from the project class"""
     os.chdir(tmp_path)
     project = mds.Project()
@@ -70,22 +70,12 @@ def test_gksd_project(traj_files, true_values, tmp_path):
         "NaCl", data=traj_files[0], timestep=0.002, temperature=1400
     )
 
-    project.run.GreenKuboSelfDiffusionCoefficients(plot=True)
-    data_dict = project.load.GreenKuboSelfDiffusionCoefficients()["NaCl"][0].data_dict
+    computation = project.run.GreenKuboDiffusionCoefficients(plot=True)
+    keys = project.run.GreenKuboDiffusionCoefficients.result_keys
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
+    data_dict = computation["NaCl"].data_dict['Na']
 
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'])
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['uncertainty'])
 
-def test_gksd_experiment(traj_files, true_values, tmp_path):
-    """Test the green_kubo_self_diffusion called from the experiment class"""
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.GreenKuboSelfDiffusionCoefficients(plot=False)
-
-    data_dict = project.experiments['NaCl'].load.GreenKuboSelfDiffusionCoefficients()[0].data_dict
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
diff --git a/CI/integration_tests/calculators/test_kirkwood_buff_integrals.py b/CI/integration_tests/calculators/test_kirkwood_buff_integrals.py
index 07961e12..4149dc6a 100644
--- a/CI/integration_tests/calculators/test_kirkwood_buff_integrals.py
+++ b/CI/integration_tests/calculators/test_kirkwood_buff_integrals.py
@@ -68,25 +68,11 @@ def test_project(traj_files, true_values, tmp_path):
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
-    project.run.KirkwoodBuffIntegral(plot=False)
+    keys = project.run.KirkwoodBuffIntegral.result_series_keys
+    computation = project.run.KirkwoodBuffIntegral()
 
-    data_dict = project.load.KirkwoodBuffIntegral()["NaCl"][0].data_dict
+    data_dict = computation["NaCl"].data_dict["Na_Na"]
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=2)
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'], decimal=2)
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'], decimal=2)
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['y'], decimal=2)
 
-
-def test_experiment(traj_files, true_values, tmp_path):
-    """Test the rdf called from the experiment class"""
-    os.chdir(tmp_path)
-    project = mds.Project()
-    project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
-
-    project.experiments['NaCl'].run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
-    project.experiments['NaCl'].run.KirkwoodBuffIntegral()
-
-    data_dict = project.experiments['NaCl'].load.KirkwoodBuffIntegral()[0].data_dict
-
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=2)
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'], decimal=2)
diff --git a/CI/integration_tests/calculators/test_potential_of_mean_force.py b/CI/integration_tests/calculators/test_potential_of_mean_force.py
index 24fa36a6..605164c9 100644
--- a/CI/integration_tests/calculators/test_potential_of_mean_force.py
+++ b/CI/integration_tests/calculators/test_potential_of_mean_force.py
@@ -62,31 +62,31 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_pomf_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the pomf called from the project class"""
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
-    project.run.PotentialOfMeanForce(plot=False)
+    computation = project.run.PotentialOfMeanForce()
+    keys = project.run.PotentialOfMeanForce.result_keys
 
-    data_dict = project.load.PotentialOfMeanForce()["NaCl"][0].data_dict
+    data_dict = computation["NaCl"].data_dict["Na_Na"]
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'])
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['uncertainty'])
 
 
-def test_pomf_experiment(traj_files, true_values, tmp_path):
+def test_experiment(traj_files, true_values, tmp_path):
     """Test the pomf called from the experiment class"""
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
-    project.experiments['NaCl'].run.PotentialOfMeanForce(plot=False)
+    computation = project.experiments["NaCl"].run.PotentialOfMeanForce(plot=False)
+    keys = project.run.PotentialOfMeanForce.result_keys
 
-    data_dict = project.experiments['NaCl'].load.PotentialOfMeanForce()[0].data_dict
+    data_dict = computation.data_dict["Na_Na"]
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'])
-    np.testing.assert_array_almost_equal(data_dict['uncertainty'], true_values['uncertainty'])
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'])
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['uncertainty'])
diff --git a/CI/integration_tests/calculators/test_radial_distribution_function.py b/CI/integration_tests/calculators/test_radial_distribution_function.py
index f14ac75b..e2150be3 100644
--- a/CI/integration_tests/calculators/test_radial_distribution_function.py
+++ b/CI/integration_tests/calculators/test_radial_distribution_function.py
@@ -62,41 +62,45 @@ def true_values() -> dict:
     return json.loads(data.read_bytes())
 
 
-def test_rdf_project(traj_files, true_values, tmp_path):
+def test_project(traj_files, true_values, tmp_path):
     """Test the rdf called from the project class"""
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
+    computation = project.run.RadialDistributionFunction(number_of_configurations=-1)
 
-    data_dict = project.load.RadialDistributionFunction()["NaCl"][0].data_dict
+    data_dict = computation["NaCl"].data_dict["Na_Na"]
 
-    data_dict['x'][data_dict['x'] is None] = 0
-    data_dict['y'][data_dict['y'] is None] = 0
+    keys = project.run.RadialDistributionFunction.result_series_keys
+
+    data_dict[keys[0]][data_dict[keys[0]] is None] = 0
+    data_dict[keys[1]][data_dict[keys[1]] is None] = 0
 
     true_values['x'][true_values['x'] is None] = 0
     true_values['y'][true_values['y'] is None] = 0
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=3)
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'], decimal=3)
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'], decimal=3)
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['y'], decimal=3)
 
 
-def test_rdf_experiment(traj_files, true_values, tmp_path):
+def test_experiment(traj_files, true_values, tmp_path):
     """Test the rdf called from the experiment class"""
     os.chdir(tmp_path)
     project = mds.Project()
     project.add_experiment("NaCl", data=traj_files[0], timestep=0.002, temperature=1400)
 
-    project.experiments['NaCl'].run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
+    computation = project.experiments['NaCl'].run.RadialDistributionFunction(number_of_configurations=-1, plot=False)
+
+    data_dict = computation.data_dict["Na_Na"]
 
-    data_dict = project.experiments['NaCl'].load.RadialDistributionFunction()[0].data_dict
+    keys = project.run.RadialDistributionFunction.result_series_keys
 
-    data_dict['x'][data_dict['x'] is None] = 0
-    data_dict['y'][data_dict['y'] is None] = 0
+    data_dict[keys[0]][data_dict[keys[0]] is None] = 0
+    data_dict[keys[1]][data_dict[keys[1]] is None] = 0
 
     true_values['x'][true_values['x'] is None] = 0
     true_values['y'][true_values['y'] is None] = 0
 
-    np.testing.assert_array_almost_equal(data_dict['x'], true_values['x'], decimal=3)
-    np.testing.assert_array_almost_equal(data_dict['y'], true_values['y'], decimal=3)
+    np.testing.assert_array_almost_equal(data_dict[keys[0]], true_values['x'], decimal=3)
+    np.testing.assert_array_almost_equal(data_dict[keys[1]], true_values['y'], decimal=3)
diff --git a/CI/integration_tests/project/test_run_load_multiple_experiments.py b/CI/integration_tests/project/test_run_load_multiple_experiments.py
index a7759abc..e08ea444 100644
--- a/CI/integration_tests/project/test_run_load_multiple_experiments.py
+++ b/CI/integration_tests/project/test_run_load_multiple_experiments.py
@@ -76,9 +76,7 @@ def test_add_run_load_data(project, traj_files):
     # Check that 4 experiments have been created
     assert len(project.experiments) == 4
 
-    project.run.RadialDistributionFunction(plot=False)
-
-    loaded_data = project.load.RadialDistributionFunction()
+    loaded_data = project.run.RadialDistributionFunction(plot=False)
 
     # Check that data for 4 experiments has been loaded
     assert len(loaded_data) == 4
@@ -87,4 +85,4 @@ def test_add_run_load_data(project, traj_files):
 
     # Each loaded data should contain 3 entries, Na-Na, Na-Cl, Cl-Cl
     for data in loaded_data.values():
-        assert len(data) == 3
+        assert len(data.data_dict) == 3
diff --git a/CI/unit_tests/database/test_experiment_database.py b/CI/unit_tests/database/test_experiment_database.py
index ca78f3f7..a4f0902f 100644
--- a/CI/unit_tests/database/test_experiment_database.py
+++ b/CI/unit_tests/database/test_experiment_database.py
@@ -78,6 +78,32 @@ def test_project_number_of_atoms():
     assert project_2.experiments['Exp01'].number_of_atoms == 100
 
 
+def test_species():
+    """Test that the species are stored correctly in the database"""
+
+    species = {"H": {'indices': [1, 2, 3], 'mass': 1}, 'Cl': {'indices': [4, 5, 6], 'mass': 35.45}}
+
+    project_1 = mds.Project()
+    project_1.add_experiment(experiment="Exp01")
+    project_1.experiments['Exp01'].species = species
+
+    project_2 = mds.Project()
+    project_2.load_experiments('Exp01')
+    assert project_2.experiments['Exp01'].species == species
+
+def test_molecules():
+    """Test that the molecules are stored correctly in the database"""
+
+    molecule = {"Proton": {'indices': [1, 2, 3], 'mass': 1}, 'Chloride': {'indices': [4, 5, 6], 'mass': 35.45}}
+
+    project_1 = mds.Project()
+    project_1.add_experiment(experiment="Exp01")
+    project_1.experiments['Exp01'].molecules = molecule
+
+    project_2 = mds.Project()
+    project_2.load_experiments('Exp01')
+    assert project_2.experiments['Exp01'].molecules == molecule
+
 def test_project_box_array():
     """Test that the project description is stored correctly in the database"""