Repository: StemVibrations/STEM (language: python)

Issue:
Let user define parameters to visualise in postprocessing



Discussion / hints:

Fix patch:
diff --git a/stem/IO/kratos_output_io.py b/stem/IO/kratos_output_io.py
new file mode 100644
index 00000000..4d36f2be
--- /dev/null
+++ b/stem/IO/kratos_output_io.py
@@ -0,0 +1,287 @@
+import json
+from copy import deepcopy
+from pathlib import Path
+from typing import List, Dict, Tuple, Union, Any
+
+import numpy as np
+
+from stem.load import Load, PointLoad, MovingLoad
+from stem.output import (
+    Output,
+    GiDOutputParameters,
+    VtkOutputParameters,
+    JsonOutputParameters,
+    OutputParametersABC,
+)
+from stem.soil_material import *
+from stem.structural_material import *
+
+
+class KratosOutputsIO:
+    """
+    Class containing methods to write outputs to Kratos
+
+    Attributes:
+        domain (str): name of the Kratos domain
+
+    """
+
+    def __init__(self, domain: str):
+        """
+        Constructor of KratosOutputsIO class
+
+        Args:
+            domain (str): name of the Kratos domain
+
+        """
+        self.domain = domain
+
+    def __create_gid_output_dict(
+        self,
+        part_name: str,
+        output_dir: Path,
+        output_name: str,
+        output_parameters: GiDOutputParameters,
+    ) -> Dict[str, Any]:
+        """
+        Creates a dictionary containing the output parameters to produce outputs in GiD
+        format. To visualize the outputs, the software GiD is required.
+
+        Args:
+            part_name (str): name of the model part
+            output_dir (Path): output path for the GiD output
+            output_name (str): Name for the output file.
+            output_parameters (GiDOutputParameters): class containing GiD output
+                parameters
+
+        Returns:
+            Dict[str, Any]: dictionary containing the output parameters in Kratos format
+        """
+
+        if output_name is None or output_name == "":
+            output_name = f"{part_name}"
+
+        _output_path = output_dir.joinpath(output_name)
+        __output_path_gid = str(_output_path).replace("\\", "/")
+
+        if output_parameters.file_format == "binary":
+            gid_post_mode = "GiD_PostBinary"
+        elif output_parameters.file_format == "ascii":
+            gid_post_mode = "GiD_PostAscii"
+
+        elif output_parameters.file_format == "hdf5":
+            gid_post_mode = "GiD_PostHDF5"
+        else:
+            raise ValueError(
+                "Incorrect selected output for GiD `file_format` "
+                f"variable:{output_parameters.file_format}.\n"
+                f"Accepted inputs are `binary`, `ascii` or `hdf5`."
+            )
+
+        parameters_dict = {
+            "model_part_name": f"{self.domain}.{part_name}",
+            "output_name": __output_path_gid,
+            "postprocess_parameters": {
+                "result_file_configuration": {
+                    "gidpost_flags": {
+                        "WriteDeformedMeshFlag": "WriteUndeformed",
+                        "WriteConditionsFlag": "WriteElementsOnly",
+                        "GiDPostMode": gid_post_mode,
+                        "MultiFileFlag": "SingleFile",
+                    },
+                    "file_label": output_parameters.file_label,
+                    "output_control_type": output_parameters.output_control_type,
+                    "output_interval": output_parameters.output_interval,
+                    "body_output": output_parameters.body_output,
+                    "node_output": output_parameters.node_output,
+                    "skin_output": output_parameters.skin_output,
+                    "plane_output": output_parameters.plane_output,
+                    "nodal_results": [
+                        op.name for op in output_parameters.nodal_results
+                    ],
+                    "gauss_point_results": [
+                        op.name for op in output_parameters.gauss_point_results
+                    ],
+                },
+                "point_data_configuration": output_parameters.point_data_configuration,
+            },
+        }
+        # initialize output dictionary
+        output_dict: Dict[str, Any] = {
+            "python_module": "gid_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "GiDOutputProcess",
+            "Parameters": parameters_dict,
+        }
+
+        return output_dict
+
+    def __create_vtk_output_dict(
+        self,
+        part_name: str,
+        output_dir: Path,
+        output_name: str,
+        output_parameters: VtkOutputParameters,
+    ) -> Dict[str, Any]:
+        """
+        Creates a dictionary containing the output parameters to produce outputs in vtk
+        format. The format can be visualized e.g., using Paraview.
+
+        Args:
+            part_name (str): name of the model part
+            output_dir (Path): output path for the VTK output
+            output_name (str): Name for the output file. This parameter is ignored by
+                VTK output process.
+            output_parameters (VtkOutputParameters): class containing VTK output
+                parameters
+
+        Returns:
+            Dict[str, Any]: dictionary containing the output parameters in Kratos format
+        """
+
+        _output_path = output_dir
+        __output_path_vtk = str(_output_path).replace("\\", "/")
+
+        parameters_dict = {
+            "model_part_name": f"{self.domain}.{part_name}",
+            "output_path": __output_path_vtk,
+            "file_format": output_parameters.file_format,
+            "output_precision": output_parameters.output_precision,
+            "output_control_type": output_parameters.output_control_type,
+            "output_interval": output_parameters.output_interval,
+            "nodal_solution_step_data_variables": [
+                op.name for op in output_parameters.nodal_results
+            ],
+            "gauss_point_variables_in_elements": [
+                op.name for op in output_parameters.gauss_point_results
+            ],
+        }
+
+        # initialize load dictionary
+        output_dict: Dict[str, Any] = {
+            "python_module": "vtk_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "VtkOutputProcess",
+            "Parameters": parameters_dict,
+        }
+
+        return output_dict
+
+    def __create_json_output_dict(
+        self,
+        part_name,
+        output_dir: Path,
+        output_name: str,
+        output_parameters: JsonOutputParameters,
+    ) -> Dict[str, Any]:
+        """
+        Creates a dictionary containing the output parameters to produce outputs in
+        JSON format.
+
+        Args:
+            part_name (str): name of the model part
+            output_dir (Path): output path for the VTK output
+            output_name (str): Name for the output file. If not needed, the name is
+                taken as the part_name.
+            output_parameters (JsonOutputParameters): class containing JSON output
+                parameters
+
+        Returns:
+            Dict[str, Any]: dictionary containing the output parameters in Kratos format
+        """
+
+        if output_name is None or output_name == "":
+            output_name = f"{part_name}" + ".json"
+
+        # create the target folder for json or simulation will not run.
+        output_dir.mkdir(parents=True, exist_ok=True)
+
+        _output_path = output_dir.joinpath(output_name)
+        if _output_path.suffix == "":
+            _output_path = _output_path.with_suffix(".json")
+
+        __output_path_json = str(_output_path).replace("\\", "/")
+
+        # initialize output dictionary
+        output_dict: Dict[str, Any] = {
+            "python_module": "json_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "JsonOutputProcess",
+            "Parameters": {
+                "model_part_name": f"{self.domain}.{part_name}",
+                "output_file_name": __output_path_json,
+                "output_variables": [op.name for op in output_parameters.nodal_results],
+                "gauss_points_output_variables": [
+                    op.name for op in output_parameters.gauss_point_results
+                ],
+            },
+        }
+        return output_dict
+
+    def __create_output_dict(self, output: Output) -> Tuple[str, Dict[str, Any]]:
+        """
+        Creates a dictionary containing the output parameters for the desired format.
+        Allowed format are GiD, VTK and JSON.
+
+        Args:
+            output (Output): output process object
+
+        Returns:
+            str: string specifying the format of the output
+            Dict[str, Any]: dictionary containing the output parameters
+        """
+        # add output keys and parameters to dictionary based on output process type.
+        if isinstance(output.output_parameters, GiDOutputParameters):
+            return "gid_output", self.__create_gid_output_dict(**output.__dict__)
+        elif isinstance(output.output_parameters, VtkOutputParameters):
+            return "vtk_output", self.__create_vtk_output_dict(**output.__dict__)
+        elif isinstance(output.output_parameters, JsonOutputParameters):
+            return "json_output", self.__create_json_output_dict(**output.__dict__)
+        else:
+            raise NotImplementedError
+
+    @staticmethod
+    def __get_process_type_for_output(output_parameters: OutputParametersABC) -> str:
+        """
+        Checks if considered output parameters are `output_process` as for VTK and
+        GiD JSON output types or `process` as for JSON output type.
+
+        Args:
+            output_parameters (Output): class containing output parameters
+
+        Returns:
+            str
+        """
+
+        if isinstance(output_parameters, (VtkOutputParameters, GiDOutputParameters)):
+            return "output_processes"
+        return "processes"
+
+    def create_output_process_dictionary(self, outputs: List[Output]) -> Dict[str, Any]:
+        """
+        Creates a dictionary containing the output_processes, that specifies which
+        output to request Kratos and the type of output ('GiD', 'VTK', 'JSON')
+
+        Args:
+            outputs (List[Output]): list of output process objects
+
+        Returns:
+            output_dict (Dict[str, Any]): dictionary containing two other dictionary
+                for output properties:
+                - the first containing the "output_process" dictionary.
+                - the second containing the "processes" dictionary, which includes JSON
+                  outputs.
+        """
+        output_dict: Dict[str, Any] = {"output_processes": {}, "processes": {}}
+
+        for output in outputs:
+            output.output_parameters.validate()
+            key_output, _parameters_output = self.__create_output_dict(output)
+            key_process = KratosOutputsIO.__get_process_type_for_output(
+                output.output_parameters
+            )
+            if key_output in output_dict[key_process].keys():
+                output_dict[key_process][key_output].append(_parameters_output)
+            else:
+                output_dict[key_process][key_output] = [_parameters_output]
+        return output_dict
diff --git a/stem/output.py b/stem/output.py
new file mode 100644
index 00000000..85512ca6
--- /dev/null
+++ b/stem/output.py
@@ -0,0 +1,281 @@
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from enum import Enum
+from pathlib import Path
+from typing import List, Optional
+
+
+class NodalOutput(Enum):
+    DISPLACEMENT = 1
+    DISPLACEMENT_X = 11
+    DISPLACEMENT_Y = 12
+    DISPLACEMENT_Z = 13
+    VELOCITY = 2
+    VELOCITY_X = 21
+    VELOCITY_Y = 22
+    VELOCITY_Z = 23
+    ACCELERATION = 3
+    ACCELERATION_X = 31
+    ACCELERATION_Y = 32
+    ACCELERATION_Z = 33
+    TOTAL_DISPLACEMENT = 4
+    TOTAL_DISPLACEMENT_X = 41
+    TOTAL_DISPLACEMENT_Y = 42
+    TOTAL_DISPLACEMENT_Z = 43
+    WATER_PRESSURE = 5
+    VOLUME_ACCELERATION = 6
+    VOLUME_ACCELERATION_X = 61
+    VOLUME_ACCELERATION_Y = 62
+    VOLUME_ACCELERATION_Z = 63
+
+
+class GaussPointOutput(Enum):
+    VON_MISES_STRESS = 1
+    FLUID_FLUX_VECTOR = 2
+    HYDRAULIC_HEAD = 3
+    GREEN_LAGRANGE_STRAIN_VECTOR = 41
+    GREEN_LAGRANGE_STRAIN_TENSOR = 42
+    ENGINEERING_STRAIN_VECTOR = 51
+    ENGINEERING_STRAIN_TENSOR = 52
+    CAUCHY_STRESS_VECTOR = 61
+    CAUCHY_STRESS_TENSOR = 62
+    TOTAL_STRESS_VECTOR = 71
+    TOTAL_STRESS_TENSOR = 72
+
+
+TENSOR_OUTPUTS = [
+    "GREEN_LAGRANGE_STRAIN",
+    "ENGINEERING_STRAIN",
+    "CAUCHY_STRESS",
+    "TOTAL_STRESS",
+]
+
+
+def detect_vector_in_tensor_outputs(requested_outputs: List[GaussPointOutput]):
+    """
+    Detects whether gauss point outputs are requested and warns the user
+
+    Args:
+        requested_outputs (List[st]): list of requested outputs (gauss point)
+    """
+    if len(requested_outputs) > 0:
+        detected_tensor_outputs = []
+        for requested_output in requested_outputs:
+            for tensor_output in TENSOR_OUTPUTS:
+                if (
+                    tensor_output in requested_output.name
+                    and "_VECTOR" in requested_output.name
+                ):
+                    detected_tensor_outputs.append(tensor_output)
+                    break
+        if len(detected_tensor_outputs):
+            _fmt_list = "".join([f" - {outpt} \n" for outpt in detected_tensor_outputs])
+            _msg = (
+                f"\n[WARNING] Vector specified for tensor output:\n{_fmt_list}"
+                f"In GiD, Such outputs are incorrectly rendered."
+            )
+            print(_msg)
+
+
+# define out
+
+
+def detect_tensor_outputs(requested_outputs: List[GaussPointOutput]):
+    """
+    Detects whether gauss point outputs are requested and warns the user
+
+    Args:
+        requested_outputs (List[st]): list of requested outputs (gauss point)
+    """
+    if len(requested_outputs) > 0:
+        detected_tensor_outputs = []
+        for requested_output in requested_outputs:
+            for tensor_output in TENSOR_OUTPUTS:
+                if tensor_output in requested_output.name:
+                    detected_tensor_outputs.append(tensor_output)
+                    break
+        if len(detected_tensor_outputs):
+            _fmt_list = "".join([f" - {outpt} \n" for outpt in detected_tensor_outputs])
+            _msg = (
+                f"\n[WARNING] tensor output detected:\n{_fmt_list}"
+                f"The outputs are incorrectly rendered in VTK and JSON output types:\n"
+                f"For VECTOR the output is incorrectly produced ans misses "
+                f"components.\n"
+                f"For TENSOR the output is ignored.\n"
+            )
+            print(_msg)
+
+
+@dataclass
+class OutputParametersABC(ABC):
+    """
+    Abstract class for the definition of user output parameters (GiD, VTK, json).
+
+    Attributes:
+        -
+    """
+
+    pass
+
+    @abstractmethod
+    def validate(self):
+        """
+        Abstract method for validating user inputs
+        """
+        pass
+
+
+@dataclass
+class GiDOutputParameters(OutputParametersABC):
+    """
+    Class containing the output parameters for GiD output
+
+    Attributes:
+        output_interval (float): frequency of the output, either step interval if
+            `output_control_type` is `step` or time interval in seconds if
+            `output_control_type` is `time`.
+        output_control_type (str): type of output control, either `step` or `time`.
+        file_format (str): format of output (`binary`,`ascii` or `hdf5`) for the
+            gid_post_mode flag
+        nodal_results (List[NodalOutput]): list of nodal outputs as defined in
+            NodalOutput.
+        gauss_point_results (List[GaussPointOutput]): list of gauss point outputs as
+            defined in GaussPointOutput.
+        file_label (str): labelling format for the files (`step` or `time`)
+
+        body_output (bool):
+        node_output (bool):
+        skin_output (bool):
+        plane_output (List[str]):
+        point_data_configuration (List[str]):
+    """
+
+    # general inputs
+    output_interval: float
+    output_control_type: str = "step"
+    file_format: str = "binary"
+    nodal_results: List[NodalOutput] = field(default_factory=lambda: [])
+    gauss_point_results: List[GaussPointOutput] = field(default_factory=lambda: [])
+    # GiD specific inputs
+    file_label: str = "step"
+    # optional
+    body_output: bool = True
+    node_output: bool = False
+    skin_output: bool = False
+    plane_output: List[str] = field(default_factory=lambda: [])
+    point_data_configuration: List[str] = field(default_factory=lambda: [])
+
+    def validate(self):
+        """
+        Validates the gauss point results requested for GiD output.
+        Prints warnings if vector format is requested for tensor output.
+        """
+        detect_vector_in_tensor_outputs(requested_outputs=self.gauss_point_results)
+
+
+@dataclass
+class VtkOutputParameters(OutputParametersABC):
+    """
+    Class containing the output parameters for GiD output
+
+    Attributes:
+        output_interval (float): frequency of the output, either step interval if
+            `output_control_type` is `step` or time interval in seconds if
+            `output_control_type` is `time`.
+        output_control_type (str): type of output control, either `step` or `time`.
+        file_format (str): file format for VTK, either `binary` or `ascii` are allowed.
+        nodal_results (List[NodalOutput]): list of nodal outputs as defined in
+            NodalOutput.
+        gauss_point_results (List[GaussPointOutput]): list of gauss point outputs as
+            defined in GaussPointOutput.
+        output_precision (int): precision of the output for ascii. Default is 7.
+    """
+
+    # general inputs
+    output_interval: float
+    output_control_type: str = "step"
+    file_format: str = "binary"
+    nodal_results: List[NodalOutput] = field(default_factory=lambda: [])
+    gauss_point_results: List[GaussPointOutput] = field(default_factory=lambda: [])
+    # VTK specif inputs
+    output_precision: int = 7
+
+    def validate(self):
+        """
+        Validates the gauss point results requested for VTK output.
+        Prints warnings if tensor are asked in output.
+        """
+        detect_tensor_outputs(requested_outputs=self.gauss_point_results)
+
+
+@dataclass
+class JsonOutputParameters(OutputParametersABC):
+    """
+    Class containing the output parameters for JSON output
+
+    Attributes:
+        time_frequency (float): time frequency of the output [s].
+        nodal_results (List[NodalOutput]): list of nodal outputs as defined in
+            NodalOutput.
+        gauss_point_results (List[GaussPointOutput]): list of gauss point outputs as
+            defined in GaussPointOutput.
+    """
+
+    # JSON specif inputs
+    time_frequency: float
+    # general inputs
+    nodal_results: List[NodalOutput] = field(default_factory=lambda: [])
+    gauss_point_results: List[GaussPointOutput] = field(default_factory=lambda: [])
+
+    def validate(self):
+        """
+        Validates the gauss point results requested for JSON output.
+        Prints warnings if tensor are asked in output.
+        """
+        detect_tensor_outputs(requested_outputs=self.gauss_point_results)
+
+
+class Output:
+    """
+    Class containing output information for postprocessing
+
+    Attributes:
+        part_name (str): name of the model part
+        output_dir (str): Optional input. output directory for the relative or
+            absolute path to the output file. The path will be created if it does
+            not exist yet. If not specified, the files it corrensponds to the workiing
+            directory.
+            example1=`test1` results in the test1 output folder relative to
+                current folder as ".test1"
+            example2=`path1/path2/test2` saves the outputs in
+                current_folder/path1/path2/test2
+            example3=`C:/Documents/yourproject/test3` saves the outputs in
+                `C:/Documents/yourproject/test3`.
+        output_name (str): Optional input. Name for the output file. This parameter is
+            used by GiD and JSON outputs while is ignored in VTK. If the name is not
+            given, the part_name is used instead.
+
+        output_parameters (OutputParametersABC): class containing the output parameters
+    """
+
+    def __init__(
+        self,
+        part_name: str,
+        output_parameters: OutputParametersABC,
+        output_dir: str = "",
+        output_name: str = "",
+    ):
+        """
+        Constructor of the output process class
+
+        Args:
+            part_name (str): name of the model part
+            output_name (str): name for the output file
+            output_dir (str): path to the output files
+            output_parameters (OutputParametersABC): class containing output parameters
+        """
+
+        self.output_name: str = output_name
+        self.part_name: str = part_name
+        self.output_dir: Path = Path(output_dir)
+        self.output_parameters: OutputParametersABC = output_parameters


Test patch:
diff --git a/tests/test_data/expected_output_parameters.json b/tests/test_data/expected_output_parameters.json
new file mode 100644
index 00000000..d01b5c59
--- /dev/null
+++ b/tests/test_data/expected_output_parameters.json
@@ -0,0 +1,132 @@
+{    "output_processes": {
+        "gid_output": [{
+            "python_module": "gid_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "GiDOutputProcess",
+            "Parameters":    {
+                "model_part_name": "PorousDomain.test_gid_output",
+                "output_name": "dir_test/test_gid1",
+                "postprocess_parameters": {
+                    "result_file_configuration": {
+                        "gidpost_flags":       {
+                            "WriteDeformedMeshFlag": "WriteUndeformed",
+                            "WriteConditionsFlag":   "WriteElementsOnly",
+                            "GiDPostMode":           "GiD_PostBinary",
+                            "MultiFileFlag":         "SingleFile"
+                        },
+                        "file_label":          "step",
+                        "output_control_type": "step",
+                        "output_interval":     100,
+                        "body_output":         true,
+                        "node_output":         false,
+                        "skin_output":         false,
+                        "plane_output":        [],
+                        "nodal_results":       ["DISPLACEMENT","TOTAL_DISPLACEMENT"],
+                        "gauss_point_results": ["VON_MISES_STRESS","FLUID_FLUX_VECTOR","HYDRAULIC_HEAD"]
+                    },
+                    "point_data_configuration":  []
+                }
+            }
+        }, {
+            "python_module": "gid_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "GiDOutputProcess",
+            "Parameters":    {
+                "model_part_name": "PorousDomain.test_gid_output",
+                "output_name": "dir_test/test_gid2",
+                "postprocess_parameters": {
+                    "result_file_configuration": {
+                        "gidpost_flags":       {
+                            "WriteDeformedMeshFlag": "WriteUndeformed",
+                            "WriteConditionsFlag":   "WriteElementsOnly",
+                            "GiDPostMode":           "GiD_PostAscii",
+                            "MultiFileFlag":         "SingleFile"
+                        },
+                        "file_label":          "step",
+                        "output_control_type": "step",
+                        "output_interval":     100,
+                        "body_output":         true,
+                        "node_output":         false,
+                        "skin_output":         false,
+                        "plane_output":        [],
+                        "nodal_results":       ["WATER_PRESSURE","VOLUME_ACCELERATION"],
+                        "gauss_point_results": ["GREEN_LAGRANGE_STRAIN_TENSOR","ENGINEERING_STRAIN_TENSOR","CAUCHY_STRESS_TENSOR","TOTAL_STRESS_TENSOR"]
+                    },
+                    "point_data_configuration":  []
+                }
+            }
+        }],
+        "vtk_output": [{
+            "python_module": "vtk_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "VtkOutputProcess",
+            "Parameters":    {
+                "model_part_name": "PorousDomain.test_vtk_output",
+                "output_path": "dir_test/test_vtk1",
+                "file_format" :  "binary",
+                "output_precision" : 8,
+                "output_control_type" :  "step",
+                "output_interval" : 100.0,
+                "nodal_solution_step_data_variables" :  ["DISPLACEMENT","TOTAL_DISPLACEMENT"],
+
+                "gauss_point_variables_in_elements": ["VON_MISES_STRESS","FLUID_FLUX_VECTOR","HYDRAULIC_HEAD"]
+            }
+        }, {
+            "python_module": "vtk_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "VtkOutputProcess",
+            "Parameters":    {
+                "model_part_name": "PorousDomain.test_vtk_output",
+                "output_path": "dir_test/test_vtk2",
+                "file_format" :  "ascii",
+                "output_precision" : 8,
+                "output_control_type" :  "step",
+                "output_interval" : 100.0,
+                "nodal_solution_step_data_variables" :  ["WATER_PRESSURE","VOLUME_ACCELERATION"],
+
+                "gauss_point_variables_in_elements": ["GREEN_LAGRANGE_STRAIN_TENSOR","ENGINEERING_STRAIN_TENSOR","CAUCHY_STRESS_TENSOR","TOTAL_STRESS_TENSOR"]
+            }
+        }]
+    },
+    "processes": {
+        "json_output": [{
+            "python_module": "json_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "JsonOutputProcess",
+            "Parameters": {
+                "model_part_name": "PorousDomain.test_json_output1",
+                "output_file_name": "dir_test/test_json_output1.json",
+                "output_variables": [
+                    "DISPLACEMENT",
+                    "TOTAL_DISPLACEMENT"
+                ],
+                "gauss_points_output_variables": [
+                    "VON_MISES_STRESS",
+                    "FLUID_FLUX_VECTOR",
+                    "HYDRAULIC_HEAD"
+                ],
+                "time_frequency": 0.002
+            }
+        },
+        {
+            "python_module": "json_output_process",
+            "kratos_module": "KratosMultiphysics",
+            "process_name": "JsonOutputProcess",
+            "Parameters": {
+                "model_part_name": "PorousDomain.test_json_output2",
+                "output_file_name": "dir_test/test_json_output2.json",
+                "output_variables": [
+                    "WATER_PRESSURE",
+                    "VOLUME_ACCELERATION"
+                ],
+                "gauss_points_output_variables": [
+                    "GREEN_LAGRANGE_STRAIN_TENSOR",
+                    "ENGINEERING_STRAIN_TENSOR",
+                    "CAUCHY_STRESS_TENSOR",
+                    "TOTAL_STRESS_TENSOR"
+                ],
+                "time_frequency": 0.002
+            }
+        }]
+    }
+}
diff --git a/tests/test_kratos_outputs_io.py b/tests/test_kratos_outputs_io.py
new file mode 100644
index 00000000..bf238651
--- /dev/null
+++ b/tests/test_kratos_outputs_io.py
@@ -0,0 +1,135 @@
+import json
+
+import shutil
+
+import pytest
+
+from stem.IO.kratos_output_io import KratosOutputsIO
+from stem.output import *
+from tests.utils import TestUtils
+
+
+class TestKratosOutputsIO:
+    def test_create_output_process_dictionary(self):
+        """
+        Test the creation of the output process dictionary for the
+        ProjectParameters.json file
+        """
+        # Nodal results
+        nodal_results1 = [NodalOutput.DISPLACEMENT, NodalOutput.TOTAL_DISPLACEMENT]
+        nodal_results2 = [NodalOutput.WATER_PRESSURE, NodalOutput.VOLUME_ACCELERATION]
+        # Gauss point results
+        gauss_point_results1 = [
+            GaussPointOutput.VON_MISES_STRESS,
+            GaussPointOutput.FLUID_FLUX_VECTOR,
+            GaussPointOutput.HYDRAULIC_HEAD,
+        ]
+        gauss_point_results2 = [
+            GaussPointOutput.GREEN_LAGRANGE_STRAIN_TENSOR,
+            GaussPointOutput.ENGINEERING_STRAIN_TENSOR,
+            GaussPointOutput.CAUCHY_STRESS_TENSOR,
+            GaussPointOutput.TOTAL_STRESS_TENSOR,
+        ]
+        # define output parameters
+        # 1. GiD
+        gid_output_parameters1 = GiDOutputParameters(
+            file_format="binary",
+            output_interval=100,
+            nodal_results=nodal_results1,
+            gauss_point_results=gauss_point_results1,
+        )
+
+        gid_output_parameters2 = GiDOutputParameters(
+            file_format="ascii",
+            output_interval=100,
+            nodal_results=nodal_results2,
+            gauss_point_results=gauss_point_results2,
+        )
+
+        # 2. Vtk (Paraview)
+        vtk_output_parameters1 = VtkOutputParameters(
+            file_format="binary",
+            output_precision=8,
+            output_interval=100.0,
+            nodal_results=nodal_results1,
+            gauss_point_results=gauss_point_results1,
+        )
+        vtk_output_parameters2 = VtkOutputParameters(
+            file_format="ascii",
+            output_precision=8,
+            output_control_type="step",
+            output_interval=100.0,
+            nodal_results=nodal_results2,
+            gauss_point_results=gauss_point_results2,
+        )
+        # 3. Json
+        json_output_parameters1 = JsonOutputParameters(
+            time_frequency=0.002,
+            nodal_results=nodal_results1,
+            gauss_point_results=gauss_point_results1,
+        )
+        json_output_parameters2 = JsonOutputParameters(
+            time_frequency=0.002,
+            nodal_results=nodal_results2,
+            gauss_point_results=gauss_point_results2,
+        )
+
+        # create Load objects and store in the list
+        gid_output_process1 = Output(
+            part_name="test_gid_output",
+            output_dir=r"dir_test",
+            output_name=r"test_gid1",
+            output_parameters=gid_output_parameters1,
+        )
+        gid_output_process2 = Output(
+            part_name="test_gid_output",
+            output_dir=r"dir_test",
+            output_name=r"test_gid2",
+            output_parameters=gid_output_parameters2,
+        )
+        vtk_output_process1 = Output(
+            part_name="test_vtk_output",
+            output_dir=r"dir_test\test_vtk1",
+            output_parameters=vtk_output_parameters1,
+        )
+        vtk_output_process2 = Output(
+            part_name="test_vtk_output",
+            output_dir=r"dir_test\test_vtk2",
+            output_parameters=vtk_output_parameters2,
+        )
+
+        json_output_process1 = Output(
+            part_name="test_json_output1",
+            output_name="test_json_output1",
+            output_dir="dir_test",
+            output_parameters=json_output_parameters1,
+        )
+
+        json_output_process2 = Output(
+            part_name="test_json_output2",
+            output_name="test_json_output2",
+            output_dir="dir_test",
+            output_parameters=json_output_parameters2,
+        )
+        all_outputs = [
+            gid_output_process1,
+            vtk_output_process1,
+            json_output_process1,
+            gid_output_process2,
+            vtk_output_process2,
+            json_output_process2,
+        ]
+
+        # write dictionary for the output(s)
+        kratos_outputs_io = KratosOutputsIO(domain="PorousDomain")
+        test_output = kratos_outputs_io.create_output_process_dictionary(all_outputs)
+
+        # load expected dictionary from the json
+        expected_load_parameters_json = json.load(
+            open("tests/test_data/expected_output_parameters.json")
+        )
+
+        # assert the objects to be equal
+        TestUtils.assert_dictionary_almost_equal(
+            test_output, expected_load_parameters_json
+        )
diff --git a/tests/utils.py b/tests/utils.py
index c040a3f7..99044283 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,3 +1,4 @@
+import pprint
 from typing import Dict, Any
 
 import numpy.testing as npt
@@ -25,6 +26,7 @@ def assert_dictionary_almost_equal(expected: Dict[Any, Any], actual: Dict[Any, A
             elif isinstance(v, str):
                 assert v == actual[k]
             elif isinstance(v, list):
+                assert len(v) == len(actual[k])
                 for v_i, actual_i in zip(v, actual[k]):
                     if isinstance(v_i, dict):
                         TestUtils.assert_dictionary_almost_equal(v_i, actual_i)