#!/usr/bin/env python # coding: utf-8 # # Stationnary non-linear heat transfer # # ## Description of the non-linear constitutive heat transfer law # # ## FEniCS implementation # # We consider a rectanglar domain with imposed temperatures `Tl` (resp. `Tr`) on the left (resp. right boundaries). We want to solve for the temperature field `T` inside the domain using a $P^1$-interpolation. We initialize the temperature at value `Tl` throughout the domain. We finally load the material library with a `plane_strain` hypothesis. # In[1]: import matplotlib.pyplot as plt from dolfin import * import mfront_wrapper as mf length = 30e-3 width = 5.4e-3 mesh = RectangleMesh(Point(0., 0.), Point(length, width), 100, 10) V = FunctionSpace(mesh, "CG", 1) T = Function(V, name="Temperature") def left(x, on_boundary): return near(x[0], 0) and on_boundary def right(x, on_boundary): return near(x[0], length) and on_boundary Tl = 300 Tr = 800 T.interpolate(Constant(Tl)) bc = [DirichletBC(V, Constant(Tl), left), DirichletBC(V, Constant(Tr), right)] material = mf.MFrontNonlinearMaterial("../materials/src/libBehaviour.so", "StationaryHeatTransfer", hypothesis="plane_strain") # The MFront behaviour implicitly declares the temperature as an external state variable called `"Temperature"`. We must therefore associate this external state variable to a known mechanical field. This can be achieved explicitly using the `register_external_state_variable` method. In the present case, this can be donc automatically since the name of the unknown temperature field matches the [TFEL Glossary](http://tfel.sourceforge.net/glossary.html) name `"Temperature"`. In this case, the following message is printed: # ``` # Automatic registration of 'Temperature' as an external state variable. # ``` # For problems in which the temperature only acts as a parameter (no jacobian blocks with respect to the temperature), the temperature can be automatically registered as a constant value ($293.15 \text{ K}$ by default) or to any other (`dolfin.Constant` or `float`) value using the `register_external_state_variable` method. # # In the FEniCS interface, we instantiate the main mechanical unknown, here the temperature field `T` which has to be named `"Temperature"` in order to match MFront's predefined name. Using another name than this will later result in an error saying: # ``` # ValueError: 'Temperature' could not be associated with a registered gradient or a known state variable. # ``` # # The MFront behaviour declares the field `"TemperatureGradient"` as a Gradient variable, with its associated Flux called `"HeatFlux"`. We can check that the `material` object retrieves MFront's gradient and flux names, as well as the different tangent operator blocks which have been defined, namely `dj_ddgT` and `dj_ddT` in the present case: # In[2]: print(material.get_gradient_names()) print(material.get_flux_names()) print(["d{}_d{}".format(*t) for t in material.get_tangent_block_names()]) # When defining the non-linear problem, we will specify the boundary conditions and the requested quadrature degree which will control the number of quadrature points used in each cell to compute the non-linear constitutive law. Here, we specify a quadrature of degree 2 (i.e. 3 Gauss points for a triangular element). Finally, we need to associate to MFront gradient object the corresponding UFL expression as a function of the unknown field `T`. To do so, we use the `register_gradient` method linking MFront `"TemperatureGradient"` object to the UFL expression `grad(T)`. Doing so, the corresponding non-linear variational problem will be automatically be built: # # \begin{equation} # F(\widehat{T}) = \int_\Omega \boldsymbol{j}\cdot \nabla \widehat{T} \text{dx} = 0 \quad \forall \widehat{T} # \end{equation} # In[3]: problem = mf.MFrontNonlinearProblem(T, material, quadrature_degree=2, bcs=bc) problem.register_gradient("TemperatureGradient", grad(T)) # From the two tangent operator blocks `dj_ddgT` and `dj_ddT`, it will automatically be deduced that the heat flux $\boldsymbol{j}$ is a function of both the temperature gradient $\boldsymbol{g}=\nabla T$ and the temperature itself i.e. $\boldsymbol{j}=\boldsymbol{j}(\boldsymbol{g}, T)$. The following tangent bilinear form will therefore be used when solving the above non-linear problem: # # \begin{equation} # J(\widehat{T},T^*) = \int_{\Omega} \nabla \widehat{T}\cdot\left(\dfrac{\partial \boldsymbol{j}}{\partial \boldsymbol{g}}\cdot \nabla T^*+\dfrac{\partial \boldsymbol{j}}{\partial T}\cdot T^*\right) \text{dx} # \end{equation} # # Similarly to the case of external state variables, common gradient expressions for some [TFEL Glossary](http://tfel.sourceforge.net/glossary.html) names have been already predefined which avoid calling explicitly the `register_gradient` method. Predefined expressions can be obtained from: # In[4]: mf.list_predefined_gradients() # We can see that the name `"Temperature Gradient"` is in fact a predefined gradient. Omitting calling the `register_gradient` method will in this case print the following message upon calling `solve`: # ``` # Automatic registration of 'TemperatureGradient' as grad(Temperature). # ``` # meaning that a predefined gradient name has been found and registered as the UFL expression $\nabla T$. # # We finally solve the non-linear problem using a default Newton non-linear solver. The `solve` method returns the number of Newton iterations (4 in the present case) and converged status . # In[5]: problem.solve(T.vector()) # We finally check that the thermal conductivity coefficient $k$, computed from the ratio between the horizontal heat flux and temperature gradient matches the temperature-dependent expressions implemented in the MFront behaviour. # In[7]: j = problem.fluxes["HeatFlux"].function g = problem.gradients["TemperatureGradient"].function k_gauss = j.vector().get_local()[::2]/g.vector().get_local()[::2] T_gauss = problem.state_variables["external"]["Temperature"].function.vector().get_local() A = 0.0375; B = 2.165e-4; k_ref = 1/(A + B*T_gauss) plt.plot(T_gauss, k_gauss, 'o', label="FE") plt.plot(T_gauss, k_ref, '.', label="ref") plt.xlabel(r"Temperature $T\: (K)$") plt.ylabel(r"Thermal conductivity $k\: (W.m^{-1}.K^{-1})$") plt.legend() plt.show() # In[ ]: