Utilization of user-defined operators in Fortran

Hello Dear all,

Inspired by Arjen Markus’ talk at FortranCon 2020, I created a library named flop for describing partial differential equations, especially incompressible Navier-Stokes equation using user-defined operators. (README and all comments are still in Japanese)

The library consists of unary/binary operators and classes for extending the behavior of the operators. A solving procedure using the Fractional Step method for a well-known two-dimensional lid-driven cavity flow can be described as follows:

! solving top lid driven cavity flow

u = .init.(u .on. grid)
p = .init.(p .on. grid)

BC_u = BC_u .set. (Dirichlet([   0d0, 0d0]) .on. B1) &
            .set. (Dirichlet([   0d0, 0d0]) .on. B2) &
            .set. (Dirichlet([   0d0, 0d0]) .on. B3) &
            .set. (Dirichlet([U_wall, 0d0]) .on. B4)
BC_p = BC_p .set. (Neumann(0d0) .on. B1) &
            .set. (Neumann(0d0) .on. B2) &
            .set. (Neumann(0d0) .on. B3) &
            .set. (Neumann(0d0) .on. B4)

do n = 1, Nt
    u_aux = (u + dt*(-(.l.(u.dot.nabla).r.u) + kvisc*.laplacian.u)) &
            .impose. BC_u
    ! (.l.(u.dot.nabla).r.u) can be rewritten in (.div. (u .times. u))

    p = .inverse.(( &
                  (laplacian(p).with.BC_p) .results. (dens/dt*.div.u_aux)) &
                .until. below_criterion &
        )

    u = (u_aux - dt/dens*.grad.p) .impose. BC_u
end do

(I’m embarrassed if I made a mistake in choosing verbs, prepositions, etc.)

I feel that the flexible definition of user-defined operators is one of the advantages of Fortran, and it may be better to use operators more to hide gruesome details of numerical methods and promote their effectiveness.

As Arjen Markus presented, I think that some contributions to user-defined operators have been made. If anyone has more information, good practices, or tips for performance improvement, I would be grateful if you could let me know.

Thanks.

6 Likes

Thanks for your project and post.

When is it better for code to invoke user-defined operators instead of functions? The functions already exist in the module where the operators are defined. Sometimes code with a binary operator is easier to read than with a function, for example for arrays i(:) and j(:)

i .in. j

instead of

in(i,j)

I don’t see when a unary operator offers much benefit over the corresponding function, and function names have more flexibility, with allowed underscores. In the posted code, for example, I think .inverse. in the last line could be replaced by an inverse() function.

Thank you for your valuable comments and questions.
I don’t have clear answers to when to use unary operators and what is superiorities over functions.

I used operators to express the intention of “imitating mathematical operators.”
Initially, I tried to create an operator (\nabla^2)^{-1} for solving Poisson equation, but I couldn’t. As a result of trial and error, I chose an expression like OpenFOAM:

fvVectorMatrix UEqn
(
    fvm::ddt(U)
  + fvm::div(phi, U)
  - fvm::laplacian(nu, U)
);

if (piso.momentumPredictor())
{
    solve(UEqn == -fvc::grad(p));
}

Therefore, the opinion that the unary operator does not offer much benefit over the corresponding functions is reasonable.

Your questions are linked to what I wanted to ask the community.
I could only find basic descriptions of user-defined operators in Fortran. Please let me know if you know the purpose or intention of providing the user-defined unary operator feature and past discussions about its usage.

Thank you.

There is a subtle semantic implication of operators over functions. Operators must have intent(in) arguments. Thus, the usage alone tells a reader that the argument is not modified. While it would be considered bad practice, a function could possibly modify its argument. If a project’s style is to never modify function arguments, then it does become mostly a style preference.

2 Likes