Computing fluxes accurately

Hi,
this works exactly the same, independently of mechanics. If you take the action of the thermal bilinear form on a field with unit value on a given boundary, you will get the total flux on this boundary in a consistent manner.

1 Like

Thanks a lot Jeremy, I am very excited, I will post my (failed) attempt when I have the time. So far my whole personal project solely depends on evaluating these fluxes as accurately as possible, I was thinking about ways to bypass the problem but I did not find any remedy. I now have big hopes!

My current attempt looks like so:


import numpy as np
from dolfinx import log, fem
from dolfinx.fem import (Constant, Function, functionspace, FunctionSpace, assemble_scalar,
                         dirichletbc, form, locate_dofs_geometrical, VectorFunctionSpace,
                         locate_dofs_topological, Expression, create_matrix, assemble_matrix)
from dolfinx.fem.petsc import NonlinearProblem
from dolfinx.nls.petsc import NewtonSolver
from mpi4py import MPI
from petsc4py import PETSc
from petsc4py.PETSc import ScalarType
from ufl import (FiniteElement, Measure, MixedElement, SpatialCoordinate,
                 TestFunction, TrialFunction, as_tensor, dot, dx, grad, inner,
                 inv, split, derivative, FacetNormal, div, curl, as_vector, action)
import gmsh
from dolfinx.io import gmshio, VTXWriter


mesh, cell_markers, facet_markers = gmshio.read_from_msh("meshes/mesh2.msh", MPI.COMM_WORLD, gdim=2)

def thermal_problem(ΔT=0):
    current_curves=(1,2)
    voltages_curves=(1,2)  
    inj_current_curve, out_current_curve = current_curves
    reading_voltage_curve_0, reading_voltage_curve_1 = voltages_curves

    # Define FE function space
    deg = 1
    el = FiniteElement("CG", mesh.ufl_cell(), deg)
    V = FunctionSpace(mesh, el)    

    u = TestFunction(V)
    temp = Function(V)
  
    κ = 1.8
    T_cold = 300
    # Define the boundary conditions
    left_facets = facet_markers.find(inj_current_curve)
    right_facets = facet_markers.find(out_current_curve)
  
    left_dofs_temp = locate_dofs_topological(V, mesh.topology.dim-1, left_facets)
    right_dofs_temp = locate_dofs_topological(V, mesh.topology.dim-1, right_facets)
    
    bc_temp_left = dirichletbc(ScalarType(T_cold), left_dofs_temp, V)
    bc_temp_right = dirichletbc(ScalarType(T_cold + ΔT), right_dofs_temp, V)
    bcs = [bc_temp_left, bc_temp_right]
    
    x = SpatialCoordinate(mesh)
    dx = Measure("dx", domain=mesh, subdomain_data=cell_markers)
    ds = Measure("ds", domain=mesh, subdomain_data=facet_markers)


    # Weak form.
    weak_form = dot(-κ * grad(temp), grad(u)) * dx

    print(f''' ------- Pre-processing --------
    Length of the side where heat enters: {assemble_scalar(form(1 * ds(inj_current_curve, domain=mesh)))}
    Length of the side where heat leaves the material: {assemble_scalar(form(1 * ds(out_current_curve, domain=mesh)))}
    ''')

    # Solve the PDE.
    problem = NonlinearProblem(weak_form, temp, bcs=bcs)
    solver = NewtonSolver(MPI.COMM_WORLD, problem)

    ksp = solver.krylov_solver
    opts = PETSc.Options()
    option_prefix = ksp.getOptionsPrefix()
    opts[f"{option_prefix}pc_type"] = "lu"
    opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"

    ksp.setFromOptions()

    log.set_log_level(log.LogLevel.WARNING)
    n, converged = solver.solve(temp)
    assert (converged)
    print(f'''------- Processing --------
    Number of interations: {n:d}''')

    avg_temp = assemble_scalar(form(temp * dx(domain=mesh))) / assemble_scalar(form(1 * dx(domain=mesh)))
    print(f'avg_temp: {avg_temp}')

    # Compute fluxes on boundaries
    n = FacetNormal(mesh)
    down_heat_flux = form(dot(-κ * grad(temp), n)*ds(inj_current_curve))
    Q_out = assemble_scalar(down_heat_flux)

    top_heat_flux = form(dot(-κ * grad(temp), n)*ds(out_current_curve))
    Q_in = assemble_scalar(top_heat_flux)

    print(f'''------- Post processing --------
    Q_in: {Q_in}
    Q_out: {Q_out}''')    


    Q = functionspace(mesh, ("DG", deg-1, (mesh.geometry.dim,)))
    q = Function(Q)
    flux_calculator = Expression(-κ * grad(temp), Q.element.interpolation_points())
    q.interpolate(flux_calculator)
    
    # Try an alternate way to compute the fluxes.
    virtual_temp = Function(V)
    virtual_heat_flux_form = form(action(weak_form, virtual_temp))
    virtual_heat_flux = assemble_scalar(virtual_heat_flux_form)
    print(f'Virtual heat flux computation: {virtual_heat_flux}')
    return Q_in, Q_out
    
t_b = thermal_problem(ΔT=10)
print(t_b)

Producing the output:

 ------- Pre-processing --------
    Length of the side where heat enters: 0.10000000000000002
    Length of the side where heat leaves the material: 0.09999999999999998
    
------- Processing --------
    Number of interations: 1
avg_temp: 305.4445644560497
------- Post processing --------
    Q_in: -7.434551004310087
    Q_out: 7.494950253817867
Virtual heat flux computation: 0.0
(-7.434551004310087, 7.494950253817867)

In other words, it computes as 0 heat flux instead of something like 7.49 or so.
My mesh is similar to the file.msh I posted in post #10 just above.
Any help is appreciated.