Source code for festim.temperature.temperature_solver

import festim
import fenics as f
import sympy as sp


[docs] class HeatTransferProblem(festim.Temperature): """ Args: transient (bool, optional): If True, a transient simulation will be run. Defaults to True. initial_condition (int, float, sp.Expr, festim.InitialCondition, optional): The initial condition. Only needed if transient is True. absolute_tolerance (float, optional): the absolute tolerance of the newton solver. Defaults to 1e-03 relative_tolerance (float, optional): the relative tolerance of the newton solver. Defaults to 1e-10 maximum_iterations (int, optional): maximum iterations allowed for the solver to converge. Defaults to 30. linear_solver (str, optional): linear solver method for the newton solver, options can be viewed with print(list_linear_solver_methods()). If None, the default fenics linear solver will be used ("umfpack"). More information can be found at: https://fenicsproject.org/pub/tutorial/html/._ftut1017.html. Defaults to None. preconditioner (str, optional): preconditioning method for the newton solver, options can be veiwed by print(list_krylov_solver_preconditioners()). Defaults to "default". Attributes: F (fenics.Form): the variational form of the heat transfer problem v_T (fenics.TestFunction): the test function newton_solver (fenics.NewtonSolver): Newton solver for solving the nonlinear problem initial_condition (festim.InitialCondition): the initial condition sub_expressions (list): contains time dependent fenics.Expression to be updated sources (list): contains festim.Source objects for volumetric heat sources boundary_conditions (list): contains festim.BoundaryConditions """ def __init__( self, transient=True, initial_condition=None, absolute_tolerance=1e-3, relative_tolerance=1e-10, maximum_iterations=30, linear_solver=None, preconditioner="default", ) -> None: super().__init__() self.transient = transient self.initial_condition = initial_condition self.absolute_tolerance = absolute_tolerance self.relative_tolerance = relative_tolerance self.maximum_iterations = maximum_iterations self.linear_solver = linear_solver self.preconditioner = preconditioner self.F = 0 self.v_T = None self.sources = [] self.boundary_conditions = [] self.sub_expressions = [] self.newton_solver = None @property def newton_solver(self): return self._newton_solver @newton_solver.setter def newton_solver(self, value): if value is None: self._newton_solver = value elif isinstance(value, f.NewtonSolver): if self._newton_solver: festim.festim_print( "Settings for the Newton solver will be overwritten" ) self._newton_solver = value else: raise TypeError("accepted type for newton_solver is fenics.NewtonSolver") @property def initial_condition(self): return self._initial_condition @initial_condition.setter def initial_condition(self, value): if isinstance(value, (int, float, sp.Expr)): self._initial_condition = festim.InitialCondition(field="T", value=value) else: self._initial_condition = value # TODO rename initialise?
[docs] def create_functions(self, materials, mesh, dt=None): """Creates functions self.T, self.T_n and test function self.v_T. Solves the steady-state heat transfer problem if self.transient is False. Args: materials (festim.Materials): the materials. mesh (festim.Mesh): the mesh dt (festim.Stepsize, optional): the stepsize. Only needed if self.transient is True. Defaults to None. """ # Define variational problem for heat transfers V = f.FunctionSpace(mesh.mesh, "CG", 1) self.T = f.Function(V, name="T") self.T_n = f.Function(V, name="T_n") self.v_T = f.TestFunction(V) if self.transient and self.initial_condition is None: raise AttributeError( "Initial condition is required for transient heat transfer simulations" ) if self.transient and self.initial_condition: if isinstance(self.initial_condition.value, str): if self.initial_condition.value.endswith(".xdmf"): with f.XDMFFile(self.initial_condition.value) as file: file.read_checkpoint( self.T_n, self.initial_condition.label, self.initial_condition.time_step, ) else: ccode_T_ini = sp.printing.ccode(self.initial_condition.value) self.initial_condition.value = f.Expression(ccode_T_ini, degree=2, t=0) self.T_n.assign(f.interpolate(self.initial_condition.value, V)) self.define_variational_problem(materials, mesh, dt) self.create_dirichlet_bcs(mesh.surface_markers) if not self.newton_solver: self.define_newton_solver() if not self.transient: festim.festim_print("Solving stationary heat equation") dT = f.TrialFunction(self.T.function_space()) JT = f.derivative(self.F, self.T, dT) problem = festim.Problem(JT, self.F, self.dirichlet_bcs) f.begin( "Solving nonlinear variational problem." ) # Add message to fenics logs self.newton_solver.solve(problem, self.T.vector()) f.end() self.T_n.assign(self.T)
[docs] def define_variational_problem(self, materials, mesh, dt=None): """Create a variational form for heat transfer problem Args: materials (festim.Materials): the materials. mesh (festim.Mesh): the mesh. dt (festim.Stepsize, optional): the stepsize. Only needed if self.transient is True. Defaults to None. """ festim.festim_print("Defining variational problem heat transfers") T, T_n = self.T, self.T_n v_T = self.v_T self.F = 0 for mat in materials: thermal_cond = mat.thermal_cond if callable(thermal_cond): # if thermal_cond is a function thermal_cond = thermal_cond(T) subdomains = mat.id # list of subdomains with this material if type(subdomains) is not list: subdomains = [subdomains] # make sure subdomains is a list if self.transient: cp = mat.heat_capacity rho = mat.rho if callable(cp): # if cp or rho are functions, apply T cp = cp(T) if callable(rho): rho = rho(T) # Transien term for vol in subdomains: self.F += rho * cp * (T - T_n) / dt.value * v_T * mesh.dx(vol) # Diffusion term for vol in subdomains: if mesh.type == "cartesian": self.F += f.dot(thermal_cond * f.grad(T), f.grad(v_T)) * mesh.dx( vol ) elif mesh.type == "cylindrical": r = f.SpatialCoordinate(mesh.mesh)[0] self.F += ( r * f.dot(thermal_cond * f.grad(T), f.grad(v_T / r)) * mesh.dx(vol) ) elif mesh.type == "spherical": r = f.SpatialCoordinate(mesh.mesh)[0] self.F += ( thermal_cond * r * r * f.dot(f.grad(T), f.grad(v_T / r / r)) * mesh.dx(vol) ) # source term for source in self.sources: self.sub_expressions.append(source.value) if type(source.volume) is list: volumes = source.volume else: volumes = [source.volume] for volume in volumes: self.F += -source.value * v_T * mesh.dx(volume) # Boundary conditions for bc in self.boundary_conditions: if isinstance(bc, festim.FluxBC): bc.create_form(self.T, solute=None) # TODO: maybe that's not necessary self.sub_expressions += bc.sub_expressions for surf in bc.surfaces: self.F += -bc.form * self.v_T * mesh.ds(surf)
[docs] def define_newton_solver(self): """Creates the Newton solver and sets its parameters""" self.newton_solver = f.NewtonSolver(f.MPI.comm_world) self.newton_solver.parameters["error_on_nonconvergence"] = True self.newton_solver.parameters["absolute_tolerance"] = self.absolute_tolerance self.newton_solver.parameters["relative_tolerance"] = self.relative_tolerance self.newton_solver.parameters["maximum_iterations"] = self.maximum_iterations self.newton_solver.parameters["linear_solver"] = self.linear_solver self.newton_solver.parameters["preconditioner"] = self.preconditioner
[docs] def create_dirichlet_bcs(self, surface_markers): """Creates a list of fenics.DirichletBC and add time dependent expressions to .sub_expressions Args: surface_markers (fenics.MeshFunction): contains the mesh facet markers """ V = self.T.function_space() self.dirichlet_bcs = [] for bc in self.boundary_conditions: if isinstance(bc, festim.DirichletBC) and bc.field == "T": bc.create_expression(self.T) for surf in bc.surfaces: bci = f.DirichletBC(V, bc.expression, surface_markers, surf) self.dirichlet_bcs.append(bci) self.sub_expressions += bc.sub_expressions self.sub_expressions.append(bc.expression)
[docs] def update(self, t): """Updates T_n, and T with respect to time by solving the heat transfer problem Args: t (float): the time """ if self.transient: festim.update_expressions(self.sub_expressions, t) # Solve heat transfers dT = f.TrialFunction(self.T.function_space()) JT = f.derivative(self.F, self.T, dT) # Define the Jacobian problem = festim.Problem(JT, self.F, self.dirichlet_bcs) f.begin( "Solving nonlinear variational problem." ) # Add message to fenics logs self.newton_solver.solve(problem, self.T.vector()) f.end() self.T_n.assign(self.T)
def is_steady_state(self): return not self.transient