text: from __future__ import print_function, absolute_import, division  # makes KratosMultiphysics backward compatible with python 2.6 and 2.7
  import sys
  
  # Importing the Kratos Library
  import KratosMultiphysics
  
  # Import applications
  import KratosMultiphysics.FluidDynamicsApplication as KratosCFD
  import KratosMultiphysics.ConvectionDiffusionApplication as ConvDiff
  
  # Importing the base class
  from KratosMultiphysics.python_solver import PythonSolver
  
  def CreateSolver(main_model_part, custom_settings):
      return CoupledFluidThermalSolver(main_model_part, custom_settings)
  
  class CoupledFluidThermalSolver(PythonSolver):
  
      def __init__(self, model, custom_settings):
  
          super(CoupledFluidThermalSolver, self).__init__(model, custom_settings)
  
          default_settings = KratosMultiphysics.Parameters("""
          {
              "solver_type" : "ThermallyCoupled",
              "domain_size" : -1,
              "echo_level": 0,
              "fluid_solver_settings": {
                  "solver_type": "navier_stokes_solver_vmsmonolithic",
                  "model_import_settings": {
                      "input_type": "mdpa",
                      "input_filename": "unknown_name"
                  }
              },
              "thermal_solver_settings": {
                  "solver_type": "Transient",
                  "analysis_type": "linear",
                  "model_import_settings": {
                      "input_type": "use_input_model_part"
                  },
                  "material_import_settings": {
                          "materials_filename": "ThermalMaterials.json"
                  }
              }
          }
          """)
  
          ## Overwrite the default settings with user-provided parameters
          self.settings.ValidateAndAssignDefaults(default_settings)
  
          ## Get domain size
          self.domain_size = self.settings["domain_size"].GetInt()
  
          from KratosMultiphysics.FluidDynamicsApplication import python_solvers_wrapper_fluid
          self.fluid_solver = python_solvers_wrapper_fluid.CreateSolverByParameters(self.model, self.settings["fluid_solver_settings"],"OpenMP")
  
          from KratosMultiphysics.ConvectionDiffusionApplication import python_solvers_wrapper_convection_diffusion
          self.thermal_solver = python_solvers_wrapper_convection_diffusion.CreateSolverByParameters(self.model,self.settings["thermal_solver_settings"],"OpenMP")
  
      def AddVariables(self):
          # Import the fluid and thermal solver variables. Then merge them to have them in both fluid and thermal solvers.
          self.fluid_solver.AddVariables()
          self.thermal_solver.AddVariables()
          KratosMultiphysics.MergeVariableListsUtility().Merge(self.fluid_solver.main_model_part, self.thermal_solver.main_model_part)
  
      def ImportModelPart(self):
          # Call the fluid solver to import the model part from the mdpa
          self.fluid_solver.ImportModelPart()
  
          # Save the convection diffusion settings
          convection_diffusion_settings = self.thermal_solver.main_model_part.ProcessInfo.GetValue(KratosMultiphysics.CONVECTION_DIFFUSION_SETTINGS)
  
          # Here the fluid model part is cloned to be thermal model part so that the nodes are shared
          modeler = KratosMultiphysics.ConnectivityPreserveModeler()
          if self.domain_size == 2:
              modeler.GenerateModelPart(self.fluid_solver.main_model_part,
                                        self.thermal_solver.main_model_part,
                                        "EulerianConvDiff2D",
                                        "ThermalFace2D2N")
          else:
              modeler.GenerateModelPart(self.fluid_solver.main_model_part,
                                        self.thermal_solver.main_model_part,
                                        "EulerianConvDiff3D",
                                        "ThermalFace3D3N")
  
          # Set the saved convection diffusion settings to the new thermal model part
          self.thermal_solver.main_model_part.ProcessInfo.SetValue(KratosMultiphysics.CONVECTION_DIFFUSION_SETTINGS, convection_diffusion_settings)
  
      def PrepareModelPart(self):
          self.fluid_solver.PrepareModelPart()
          self.thermal_solver.PrepareModelPart()
  
      def AddDofs(self):
          self.fluid_solver.AddDofs()
          self.thermal_solver.AddDofs()
  
      def AdaptMesh(self):
          pass
  
      def GetComputingModelPart(self):
          return self.fluid_solver.GetComputingModelPart()
  
      def GetOutputVariables(self):
          pass
  
      def ComputeDeltaTime(self):
          return self.fluid_solver._ComputeDeltaTime()
  
      def GetMinimumBufferSize(self):
          buffer_size_fluid = self.fluid_solver.GetMinimumBufferSize()
          buffer_size_thermal = self.thermal_solver.GetMinimumBufferSize()
          return max(buffer_size_fluid, buffer_size_thermal)
  
      def Initialize(self):
          self.fluid_solver.Initialize()
          self.thermal_solver.Initialize()
  
      def Clear(self):
          (self.fluid_solver).Clear()
          (self.thermal_solver).Clear()
  
      def Check(self):
          (self.fluid_solver).Check()
          (self.thermal_solver).Check()
  
      def SetEchoLevel(self, level):
          (self.fluid_solver).SetEchoLevel(level)
          (self.thermal_solver).SetEchoLevel(level)
  
      def AdvanceInTime(self, current_time):
          #NOTE: the cloning is done ONLY ONCE since the nodes are shared
          new_time = self.fluid_solver.AdvanceInTime(current_time)
          return new_time
  
      def InitializeSolutionStep(self):
          self.fluid_solver.InitializeSolutionStep()
          self.thermal_solver.InitializeSolutionStep()
  
      def Predict(self):
          self.fluid_solver.Predict()
          self.thermal_solver.Predict()
  
      def SolveSolutionStep(self):
          self.fluid_solver.SolveSolutionStep()
          self.thermal_solver.SolveSolutionStep()
  
      def FinalizeSolutionStep(self):
          self.fluid_solver.FinalizeSolutionStep()
          self.thermal_solver.FinalizeSolutionStep()
  
      def Solve(self):
          self.InitializeSolutionStep()
          self.Predict()
          self.SolveSolutionStep()
          self.FinalizeSolutionStep()
  