Translating a working FEniCS code to FEniCSx, stuck with boundary conditions

I am in the process of translating a working code from FEniCS to FEniCSx, but I am stuck at the boundary conditions part.

First, the mesh comes from a file.geo created with GMSH GUI:

SetFactory("OpenCASCADE");
Box(1) = {0, 0, 0, 1e-2, 1e-2, 1};
Physical Volume("the_wire", 13) = {1};
Physical Surface("left_end", 14) = {5};
Physical Surface("right_end", 15) = {6};

It’s just a box that represents a wire. The idea is to solve Ohm’s law: we apply a known current to the wire, and “measure” its voltage, this should yield the resistance of the wire. More precisely, the current is injected via the left end of the wire (surface 5), and leaves the wire at the right end (surface 6). The voltage is read between these two surfaces, too.

I now show you my code in 2 parts, the first part works, I believe, and is just there as to make a MWE.

1st part:

from mpi4py import MPI
import gmsh
from dolfinx.io import gmshio
mesh, cell_markers, facet_markers = gmshio.read_from_msh("file.msh", MPI.COMM_WORLD, gdim=3)

# Now I want to convert the mesh file to XDMF using meshio.
proc = MPI.COMM_WORLD.rank 
import meshio
def create_mesh(mesh, cell_type, prune_z=False):
    cells = mesh.get_cells_type(cell_type)
    cell_data = mesh.get_cell_data("gmsh:physical", cell_type)
    points = mesh.points[:,:2] if prune_z else mesh.points
    out_mesh = meshio.Mesh(points=points, cells={cell_type: cells}, cell_data={"name_to_read":[cell_data]})
    return out_mesh

if proc == 0:
    # Read in mesh
    msh = meshio.read("file.msh")
   
    # Create and save one file for the mesh, and one file for the facets 
    triangle_mesh = create_mesh(msh, "triangle", prune_z=False)
    tetra_mesh = create_mesh(msh, "tetra", prune_z=False)
    meshio.write("mesh.xdmf", tetra_mesh)
    meshio.write("mt.xdmf", triangle_mesh)

# Now read the mesh files produced above.
from dolfinx.io import XDMFFile
with XDMFFile(MPI.COMM_WORLD, "mesh.xdmf", "r") as xdmf:
    mesh = xdmf.read_mesh(name="Grid")
    ct = xdmf.read_meshtags(mesh, name="Grid")
mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim-1)
with XDMFFile(MPI.COMM_WORLD, "mt.xdmf", "r") as xdmf:
    ft = xdmf.read_meshtags(mesh, name="Grid")

from dolfinx.fem import (Constant, dirichletbc, Function, FunctionSpace, assemble_scalar,
                         form, locate_dofs_geometrical, locate_dofs_topological)
from ufl import (SpatialCoordinate, TestFunction, TrialFunction,
                 dx, grad, inner, as_tensor, inv)
import numpy as np

# Define function space
V = FunctionSpace(mesh, ("CG", 1))
volt = Function(V)
u = TestFunction(V)

rho_tensor = as_tensor([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
sigma_tensor = inv(rho_tensor)

# Reminder of the mesh:
# Physical Volume("the_wire", 13) = {1};
# Physical Surface("left_end", 14) = {5};
# Physical Surface("right_end", 15) = {6};
inj_current_surface = 5
vanish_voltage_surface = 6 # This corresponds to the surface the current leaves the material.
V_current_contact_out = 0.0 # Voltage value of the surface where the current leaves the material.
reading_voltage_surface_0 = 5
reading_voltage_surface_1 = 6

Now the 2nd part, where I am stuck.

# We define the boundary conditions
u_bc = Function(V)
left_facets = ft.find(inj_current_surface)
left_dofs = locate_dofs_topological(V, mesh.topology.dim-1, left_facets)
bcs = [dirichletbc(ScalarType(1), left_dofs, V)]

Here I have the first doubt, I want to apply the Dirichlet boundary condition of voltage = 0 on the left end of the wire. In FEniCS, I have the code:

bc0 = DirichletBC(V, V_current_contact_out, boundary_parts, vanish_voltage_surface)
bcs = [bc0]
J = 2e-3 / assemble(1 * ds(inj_current_surface, domain=mesh))
F = dot(grad(u), sigma_tensor * grad(volt))*dx + u * J * ds(inj_current_surface)
solve(F == 0, volt, bcs)

That’s the part I am stuck at translating. Any help is greatly appreciated.

To me it is unclear what you are stuck with.
You say you want to apply a zero boundary condition to the voltage, but what is stopping you from doing something similar to

with ScalarType(0.)?

Oops, right, I should have written 0 instead of 1, indeed. But I wasn’t sure this part of my code was correct. For example, I tried to change

left_facets = ft.find(inj_current_surface)

where inj_current_surface was set to 50 instead of 5. I was expecting an error, but no, the code executed without any error. Hence I am not sure I am applying the condition of 0 voltage on the left end of the wire with that command.

And then I proceeded anyway, and got stuck wit the “ds” part. I was unable to define it as I used to do it with FEniCS. I’ll update this post with my attempt.

Update:

from petsc4py.PETSc import ScalarType
from dolfinx.fem import (assemble)
from ufl import Measure
dx = Measure("dx", domain=mesh,subdomain_data=ct)
ds = Measure("ds", domain=mesh, subdomain_data=ft)
J = 2e-3 / assemble(1 * ds(inj_current_surface, domain=mesh))

yields:


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [10], line 5
      3 dx = Measure("dx", domain=mesh,subdomain_data=ct)
      4 ds = Measure("ds", domain=mesh, subdomain_data=ft)
----> 5 J = 2e-3 / assemble(1 * ds(inj_current_surface, domain=mesh))
      6 print(J)

TypeError: 'module' object is not callable

I realize it’s a Python error rather than fenicsx… Hmm. Not sure why I get this error though.

I would suggest you print left_facets which is a numpy array with the facet indices (index local to process) of those tagget with inj_current_surface. In the case were you use 50, this list is probably empty.

For your other error

I would suggest you read the tutorial: A known analytical solution — FEniCSx tutorial as you have to call dolfinx.fem.form and assemble_scalar to assemble scalar values

1 Like

Thank you very much, that was very useful.
I have debugged with

for i in range(100):
    print(ft.find(i))

this showed me that only entries 14 and 15 were not empty. Looking back at the file.geo from Gmsh (posted here in the 1st post), I see that I may have mixed up the numbers/variables. So, overall, I should use 14 instead of 5.

I already told you this in the last post,
you need to call it as

assemble_scalar(dolfinx.fem.form(1 * ds(inj_current_surface)))

I am sorry, I had missed that part but seen it too late (you replied too quickly!), but indeed, that works.

I could complete the translation of my code. However the result doesn’t look right, despite that there’s no error while executing the code.

Here’s the last part of my code:


from dolfinx.fem import assemble_scalar, form
from ufl import Measure
dx = Measure("dx", domain=mesh,subdomain_data=ct)
ds = Measure("ds", domain=mesh, subdomain_data=ft)
J = 2.0 / assemble_scalar(form(1 * ds(inj_current_surface, domain=mesh)))
print(J)
print('Area of surface where current is injected', assemble_scalar(form(1 * ds(inj_current_surface, domain=mesh))))
print('Area of surface where current leaves the wire', assemble_scalar(form(1 * ds(vanish_voltage_surface, domain=mesh))))

which outputs

20000.0
Area of surface where current is injected 0.0001
Area of surface where current leaves the wire 0.0001

which looks alright.

Then:

from ufl import dot
# Weak form.
F = dot(grad(u), sigma_tensor * grad(volt))*dx + u * J * ds(inj_current_surface)

# Solve the PDE.
from dolfinx.fem.petsc import NonlinearProblem
from dolfinx.nls.petsc import NewtonSolver
#solve(F == 0, volt, bcs, solver_parameters={"newton_solver":{"relative_tolerance": 1e-11, "absolute_tolerance": 2e-9, "maximum_iterations":1000}})
problem = NonlinearProblem(F, volt, bcs=bcs)
solver = NewtonSolver(MPI.COMM_WORLD, problem)
solver.convergence_criterion = "incremental"
solver.rtol = 1e-11
solver.report = True

from petsc4py import PETSc
ksp = solver.krylov_solver
opts = PETSc.Options()
option_prefix = ksp.getOptionsPrefix()
opts[f"{option_prefix}ksp_type"] = "cg"
opts[f"{option_prefix}pc_type"] = "gamg"
opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"
ksp.setFromOptions()

from dolfinx import log
#log.set_log_level(log.LogLevel.INFO)
n, converged = solver.solve(volt)
assert(converged)
print(f"Number of interations: {n:d}")
avg_voltage_surface_integral_0 = assemble_scalar(form(volt * ds(reading_voltage_surface_0, domain=mesh))) / assemble_scalar(form(1 * ds(reading_voltage_surface_0, domain=mesh)))
avg_voltage_surface_integral_1 = assemble_scalar(form(volt * ds(reading_voltage_surface_1, domain=mesh))) / assemble_scalar(form(1 * ds(reading_voltage_surface_1, domain=mesh)))
voltage_diff = avg_voltage_surface_integral_1 - avg_voltage_surface_integral_0
print(voltage_diff,'voltage result')

which yields a vanishing voltage (not possible, i.e. not the solution to Ohm’s law). I see that both avg_voltage_surface_integral_0 and avg_voltage_surface_integral_1 are worth 0.0. Only one of them should vanish (because of the Dirichlet boundary condition), not the other one.

Then I also see that the solver converged after 2 iterations, but that the log reveals:


2022-11-19 22:37:06.349 ( 396.989s) [main            ]              petsc.cpp:677   INFO| PETSc Krylov solver starting to solve system.
2022-11-19 22:37:06.350 ( 396.990s) [main            ]              petsc.cpp:677   INFO| PETSc Krylov solver starting to solve system.
2022-11-19 22:37:06.351 ( 396.991s) [main            ]       NewtonSolver.cpp:36    INFO| Newton iteration 2: r (abs) = 0 (tol = 1e-10) r (rel) = -nan(tol = 1e-11)
2022-11-19 22:37:06.351 ( 396.991s) [main            ]       NewtonSolver.cpp:255   INFO| Newton solver finished in 2 iterations and 0 linear solver iterations.

-nan? Uh… I don’t really understand this, and why this didn’t throw an error instead of showing a convergence… But something seems wrong. I don’t know how I could debug my code efficiently.

Your code is not complete, ie. there is no way for anyone to reproduce the results you are seeing.
This is because one is lacking key definitions in your code.

I am sorry for this, I thought I had posted the full code (except the file.msh itself). I am unable to upload my files here, so I will paste the full code in the 2 files (my Python code is ran with Jupyter notebook using the latest fenicsx version in your tutorial, so 0.5.1.

The file.msh:

$MeshFormat
4.1 0 8
$EndMeshFormat
$PhysicalNames
3
2 14 "left_end"
2 15 "right_end"
3 13 "the_wire"
$EndPhysicalNames
$Entities
8 12 6 1
1 0 0 1 0 
2 0 0 0 0 
3 0 0.01 1 0 
4 0 0.01 0 0 
5 0.01 0 1 0 
6 0.01 0 0 0 
7 0.01 0.01 1 0 
8 0.01 0.01 0 0 
1 -1e-07 -1e-07 -9.999999994736442e-08 1e-07 1e-07 1.0000001 0 2 2 -1 
2 -1e-07 -1.000000000002735e-07 0.9999999000000001 1e-07 0.0100001 1.0000001 0 2 1 -3 
3 -1e-07 0.009999900000000001 -9.999999994736442e-08 1e-07 0.0100001 1.0000001 0 2 4 -3 
4 -1e-07 -1.000000000002735e-07 -1e-07 1e-07 0.0100001 1e-07 0 2 2 -4 
5 0.009999900000000001 -1e-07 -9.999999994736442e-08 0.0100001 1e-07 1.0000001 0 2 6 -5 
6 0.009999900000000001 -1.000000000002735e-07 0.9999999000000001 0.0100001 0.0100001 1.0000001 0 2 5 -7 
7 0.009999900000000001 0.009999900000000001 -9.999999994736442e-08 0.0100001 0.0100001 1.0000001 0 2 8 -7 
8 0.009999900000000001 -1.000000000002735e-07 -1e-07 0.0100001 0.0100001 1e-07 0 2 6 -8 
9 -1.000000000002735e-07 -1e-07 -1e-07 0.0100001 1e-07 1e-07 0 2 2 -6 
10 -1.000000000002735e-07 -1e-07 0.9999999000000001 0.0100001 1e-07 1.0000001 0 2 1 -5 
11 -1.000000000002735e-07 0.009999900000000001 -1e-07 0.0100001 0.0100001 1e-07 0 2 4 -8 
12 -1.000000000002735e-07 0.009999900000000001 0.9999999000000001 0.0100001 0.0100001 1.0000001 0 2 3 -7 
1 -1e-07 -1.000000000002735e-07 -9.999999994736442e-08 1e-07 0.0100001 1.0000001 0 4 -1 4 3 -2 
2 0.009999900000000001 -1.000000000002735e-07 -9.999999994736442e-08 0.0100001 0.0100001 1.0000001 0 4 -5 8 7 -6 
3 -1.000000000002735e-07 -1e-07 -9.999999994736442e-08 0.0100001 1e-07 1.0000001 0 4 -9 1 10 -5 
4 -1.000000000002735e-07 0.009999900000000001 -9.999999994736442e-08 0.0100001 0.0100001 1.0000001 0 4 -11 3 12 -7 
5 -1.000000000002735e-07 -1.000000000002735e-07 -1e-07 0.0100001 0.0100001 1e-07 1 14 4 -4 9 8 -11 
6 -1.000000000002735e-07 -1.000000000002735e-07 0.9999999000000001 0.0100001 0.0100001 1.0000001 1 15 4 -2 10 6 -12 
1 -1.000000000002735e-07 -1.000000000002735e-07 -9.999999994736442e-08 0.0100001 0.0100001 1.0000001 1 13 6 -1 2 -3 4 -5 6 
$EndEntities
$Nodes
15 47 1 47
0 1 0 1
1
0 0 1
0 2 0 1
2
0 0 0
0 3 0 1
3
0 0.01 1
0 4 0 1
4
0 0.01 0
0 5 0 1
5
0.01 0 1
0 6 0 1
6
0.01 0 0
0 7 0 1
7
0.01 0.01 1
0 8 0 1
8
0.01 0.01 0
1 1 0 9
9
10
11
12
13
14
15
16
17
0 0 0.09999999999999987
0 0 0.1999999999999997
0 0 0.2999999999999997
0 0 0.3999999999999997
0 0 0.4999999999999998
0 0 0.5999999999999998
0 0 0.6999999999999998
0 0 0.7999999999999999
0 0 0.8999999999999999
1 3 0 9
18
19
20
21
22
23
24
25
26
0 0.01 0.09999999999999987
0 0.01 0.1999999999999997
0 0.01 0.2999999999999997
0 0.01 0.3999999999999997
0 0.01 0.4999999999999998
0 0.01 0.5999999999999998
0 0.01 0.6999999999999998
0 0.01 0.7999999999999999
0 0.01 0.8999999999999999
1 5 0 9
27
28
29
30
31
32
33
34
35
0.01 0 0.09999999999999987
0.01 0 0.1999999999999997
0.01 0 0.2999999999999997
0.01 0 0.3999999999999997
0.01 0 0.4999999999999998
0.01 0 0.5999999999999998
0.01 0 0.6999999999999998
0.01 0 0.7999999999999999
0.01 0 0.8999999999999999
1 7 0 9
36
37
38
39
40
41
42
43
44
0.01 0.01 0.09999999999999987
0.01 0.01 0.1999999999999997
0.01 0.01 0.2999999999999997
0.01 0.01 0.3999999999999997
0.01 0.01 0.4999999999999998
0.01 0.01 0.5999999999999998
0.01 0.01 0.6999999999999998
0.01 0.01 0.7999999999999999
0.01 0.01 0.8999999999999999
2 5 0 1
45
0.005 0.005 0
2 6 0 1
46
0.005 0.005 1
3 1 0 1
47
0.005136782292051895 0.005031105134917242 0.603292007721658
$EndNodes
$Elements
3 79 1 79
2 5 2 4
1 2 4 45 
2 6 2 45 
3 4 8 45 
4 8 6 45 
2 6 2 4
5 1 46 3 
6 5 46 1 
7 3 46 7 
8 7 46 5 
3 1 4 71
9 42 41 32 47 
10 42 23 41 47 
11 24 23 42 47 
12 23 24 14 47 
13 44 3 46 7 
14 5 44 46 7 
15 18 8 45 4 
16 8 27 45 6 
17 2 27 6 45 
18 18 2 4 45 
19 26 46 35 44 
20 1 35 46 5 
21 1 26 3 46 
22 3 44 46 26 
23 5 44 35 46 
24 35 26 16 17 
25 19 38 28 37 
26 25 33 34 16 
27 25 33 43 34 
28 38 10 20 19 
29 40 21 12 22 
30 33 25 15 16 
31 12 39 31 30 
32 38 21 11 39 
33 39 12 11 30 
34 33 25 43 24 
35 21 12 11 39 
36 21 38 11 20 
37 25 33 15 24 
38 40 12 39 31 
39 21 40 12 39 
40 38 39 11 29 
41 33 43 42 24 
42 38 10 19 28 
43 30 11 39 29 
44 38 11 10 29 
45 11 38 10 20 
46 29 10 38 28 
47 28 37 18 19 
48 28 18 9 19 
49 36 28 37 18 
50 19 9 28 10 
51 41 31 14 40 
52 41 22 40 14 
53 41 22 14 23 
54 32 31 14 41 
55 34 25 44 43 
56 34 44 16 35 
57 25 34 44 16 
58 16 26 44 25 
59 26 35 16 44 
60 14 32 41 47 
61 14 41 23 47 
62 47 14 15 24 
63 47 15 14 32 
64 33 47 15 24 
65 33 15 47 32 
66 42 47 33 24 
67 42 33 47 32 
68 26 35 1 17 
69 1 35 26 46 
70 28 27 18 36 
71 28 18 27 9 
72 8 18 27 36 
73 8 27 18 45 
74 2 18 27 45 
75 2 27 18 9 
76 14 13 40 31 
77 14 40 13 22 
78 12 40 13 31 
79 12 13 40 22 
$EndElements
# The mesh file has been created, in this case, using Gmsh GUI priorly.
# Now I am trying to read it with fenicsx.
from mpi4py import MPI
import gmsh
from dolfinx.io import gmshio
mesh, cell_markers, facet_markers = gmshio.read_from_msh("file.msh", MPI.COMM_WORLD, gdim=3)
# Now I want to convert the mesh file to XDMF using meshio.
proc = MPI.COMM_WORLD.rank 
import meshio
def create_mesh(mesh, cell_type, prune_z=False):
    cells = mesh.get_cells_type(cell_type)
    cell_data = mesh.get_cell_data("gmsh:physical", cell_type)
    points = mesh.points[:,:2] if prune_z else mesh.points
    out_mesh = meshio.Mesh(points=points, cells={cell_type: cells}, cell_data={"name_to_read":[cell_data]})
    return out_mesh

if proc == 0:
    # Read in mesh
    msh = meshio.read("file.msh")
   
    # Create and save one file for the mesh, and one file for the facets 
    triangle_mesh = create_mesh(msh, "triangle", prune_z=False)
    tetra_mesh = create_mesh(msh, "tetra", prune_z=False)
    meshio.write("mesh.xdmf", tetra_mesh)
    meshio.write("mt.xdmf", triangle_mesh)
# Now read the mesh files produced above.
from dolfinx.io import XDMFFile
with XDMFFile(MPI.COMM_WORLD, "mesh.xdmf", "r") as xdmf:
    mesh = xdmf.read_mesh(name="Grid")
    ct = xdmf.read_meshtags(mesh, name="Grid")
mesh.topology.create_connectivity(mesh.topology.dim, mesh.topology.dim-1)
with XDMFFile(MPI.COMM_WORLD, "mt.xdmf", "r") as xdmf:
    ft = xdmf.read_meshtags(mesh, name="Grid")

from dolfinx.fem import (Constant, dirichletbc, Function, FunctionSpace, assemble_scalar,
                         form, locate_dofs_geometrical, locate_dofs_topological)
from ufl import (SpatialCoordinate, TestFunction, TrialFunction,
                 dx, grad, inner, as_tensor, inv)
import numpy as np

# Define function space
V = FunctionSpace(mesh, ("CG", 1))
volt = Function(V)
u = TestFunction(V)

rho_tensor = as_tensor([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
sigma_tensor = inv(rho_tensor)

# Reminder of the mesh:
# Physical Volume("the_wire", 13) = {1};
# Physical Surface("left_end", 14) = {5};
# Physical Surface("right_end", 15) = {6};
inj_current_surface = 14
vanish_voltage_surface = 15 # This corresponds to the surface the current leaves the material.
V_current_contact_out = 0.0 # Voltage value of the surface where the current leaves the material.
reading_voltage_surface_0 = 14
reading_voltage_surface_1 = 15

# We define the boundary conditions
from petsc4py.PETSc import ScalarType
u_bc = Function(V)
left_facets = ft.find(inj_current_surface)
left_dofs = locate_dofs_topological(V, mesh.topology.dim-1, left_facets)
bcs = [dirichletbc(ScalarType(0.0), left_dofs, V)]

from dolfinx.fem import assemble_scalar, form
from ufl import Measure
dx = Measure("dx", domain=mesh,subdomain_data=ct)
ds = Measure("ds", domain=mesh, subdomain_data=ft)
J = 2.0 / assemble_scalar(form(1 * ds(inj_current_surface, domain=mesh)))
print(J)
print('Area of surface where current is injected', assemble_scalar(form(1 * ds(inj_current_surface, domain=mesh))))
print('Area of surface where current leaves the wire', assemble_scalar(form(1 * ds(vanish_voltage_surface, domain=mesh))))

from ufl import dot
# Weak form.
F = dot(grad(u), sigma_tensor * grad(volt))*dx + u * J * ds(inj_current_surface)

# Solve the PDE.
from dolfinx.fem.petsc import NonlinearProblem
from dolfinx.nls.petsc import NewtonSolver
#solve(F == 0, volt, bcs, solver_parameters={"newton_solver":{"relative_tolerance": 1e-11, "absolute_tolerance": 2e-9, "maximum_iterations":1000}})
problem = NonlinearProblem(F, volt, bcs=bcs)
solver = NewtonSolver(MPI.COMM_WORLD, problem)
solver.convergence_criterion = "incremental"
solver.rtol = 1e-11
solver.report = True

from petsc4py import PETSc
ksp = solver.krylov_solver
opts = PETSc.Options()
option_prefix = ksp.getOptionsPrefix()
opts[f"{option_prefix}ksp_type"] = "cg"
opts[f"{option_prefix}pc_type"] = "gamg"
opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"
ksp.setFromOptions()

from dolfinx import log
log.set_log_level(log.LogLevel.WARNING)
n, converged = solver.solve(volt)
assert(converged)
print(f"Number of interations: {n:d}")

avg_voltage_surface_integral_0 = assemble_scalar(form(volt * ds(reading_voltage_surface_0, domain=mesh))) / assemble_scalar(form(1 * ds(reading_voltage_surface_0, domain=mesh)))
avg_voltage_surface_integral_1 = assemble_scalar(form(volt * ds(reading_voltage_surface_1, domain=mesh))) / assemble_scalar(form(1 * ds(reading_voltage_surface_1, domain=mesh)))
voltage_diff = avg_voltage_surface_integral_1 - avg_voltage_surface_integral_0
print(voltage_diff,'voltage conf. 1')

I think everything is here now.

The issue with your code is that your problem is solved with the zero solution (the initial condition). This can be seen by changing your code as follows:

solver.convergence_criterion = "residual"
solver.rtol = 1e-11
solver.report = True

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

You then get


2022-11-20 09:24:12.707 (   2.348s) [main            ]       NewtonSolver.cpp:36    INFO| Newton iteration 0: r (abs) = 0 (tol = 1e-10) r (rel) = -nan(tol = 1e-11)
2022-11-20 09:24:12.707 (   2.348s) [main            ]       NewtonSolver.cpp:255   INFO| Newton solver finished in 0 iterations and 0 linear solver iterations.
Number of interations: 0

As you can see the initial residual is 0.

This is because you are only trying to describe boundary conditions on inj_current_surface. You are adding both Robin type boundary conditions and Dirichlet boundary conditions on this boundary.
You can only have one of these boundary conditions on this boundary.

1 Like

Thank you very much @dokken for everything, your tips regarding the solver, etc.
I have changed the weak form, the part with ds, I changed the surface to the other end of the wire, and I get a result that makes sense: I get that Ohm’s law is satisfied. The solution converges to -20 kV when I apply a current of 2 A in the wire whose resistivity is 1 ohm x m. Great.