1 Introduction

Topology optimization is the optimization method which relies on distributing a given design variable (which, in this work, represents the solid/fluid material) over a design domain. This method was originally considered for structural optimization (Rozvany et al. 1992; Rozvany 2001), but was later introduced in fluid flow problems (Borrvall and Petersson 2003). The first approach that has been considered in topology optimization is the “pseudo-density approach”, but there are also other approaches, such as the “level-set method” (Duan et al. 2016; Zhou and Li 2008), and topological derivatives (Sokolowski and Zochowski 1999; Sá et al. 2016). In this work, topology optimization is considered through the “pseudo-density approach”.

From the initial work of topology optimization for fluids, various other types of fluid flow types have been considered, such as Stokes flows (Borrvall and Petersson 2003), Navier-Stokes flows (Evgrafov 2004; Olesen et al. 2006), Darcy-Stokes flows (Guest and Prévost 2006; Wiker et al. 2007), compressible flows (Sá et al. 2021), non-Newtonian flows (Pingen and Maute 2010; Hyun et al. 2014; Alonso et al. 2020), thermal-fluid flows (Sato et al. 2018; Ramalingom et al. 2018; Lv and Liu 2018), turbulent flows (Papoutsis-Kiachagias et al. 2011, 2015; Yoon 2016; Dilgen et al. 2018), 2D swirl flows (Alonso et al. 2018, 2019), unsteady flows (Nørgaard et al. 2016; Hasund 2017) etc. Also, various fluid flow devices can be designed through topology optimization, such as valves (Song et al. 2009; Sato et al. 2017), mixers (Andreasen et al. 2009; Deng et al. 2018), rectifiers (Jensen et al. 2012), and flow machine rotors (Romero and Silva 2014, 2017; Zhang et al. 2016).

When performing topology optimization, it is necessary to compute the sensitivities for all of the distributed design variable values inside the design domain. One way to efficiently compute them is by considering the adjoint model. For this, there are essentially two approaches: the continuous adjoint approach and the discrete adjoint approach (see Fig. 1).

Fig. 1
figure 1

Diagram illustrating the continuous adjoint approach and some possibilities of the discrete adjoint approach [figure based on Farrell et al. (2013) and Funke (2013)]

The continuous adjoint approach (indicated by the label “C” in Fig. 1) consists of directly specifying the adjoint equations and may be implemented by deriving the adjoint equations manually (“by hand”) [or symbolically, by using, for example, the SymPy library (Meurer et al. 2017)]. However, this approach is specific to each problem (Papoutsis-Kiachagias et al. 2011, 2015), may be laborious (Funke 2013), and even when it is symbolically derived, the adjoint equations may be presented in a format that is not computationally efficient. In this last case, the equations would normally require further manipulation in order to get to a computationally efficient format. The implementation of the adjoint model may become highly non-intuitive, especially when considering more complex fluid flow modeling, such as turbulent, non-Newtonian, and compressible flows. When considering the finite volume method, the resulting continuous adjoint model equations are normally solved in the same way as the simulation, such as from the iterative SIMPLE (Semi-Implicit Method for Pressure-Linked Equations) algorithm (Patankar 1980; OpenFOAM Wiki 2014). It can also be mentioned that it should also be possible to derive the continuous adjoint model equations for a coupled approach (i.e., a single equation) in OpenFOAM® (Mangani et al. 2014).

The discrete adjoint approach would consist of using, for example, a low-level approach, from C++ generic automatic differentiation (AD) tools [such as CoDiPack (Sagebaum et al. 2018) and Adept (Adept 2021)] (indicated by the label “D.2” in Fig. 1), which are normally considered to be non-intuitive and may be computationally inefficient (since the low-level C++ code would have to be automatically differentiated at each iteration of the optimization). More into the implementation in OpenFOAM®, Towara and Naumann (2013) use a SIMPLE iterative scheme to solve the adjoint model and obtain the adjoint variables. An alternative is by performing finite differences (He et al. 2018, 2020) (indicated by the label “D.3” in Fig. 1), which is automated, but there may be a significant increase in the computational cost of the topology optimization.

Another way is by considering the finite element method for a single equation (coupled pressure-velocity formulation), by automatically deriving the adjoint equations in a high-level approach (i.e., in a high-level representation of the equations) (indicated by the label “D.1” in Fig. 1) (Farrell et al. 2013; Funke 2013). This way, the resulting linear system of equations can be solved directly, without the need of any iterative method such as the SIMPLE algorithm. In this work, the discrete adjoint approach is considered in this high-level representation.

The well-known and established open-source software FEniCS (based on finite elements) (Logg et al. 2012; Farrell et al. 2013; Mitusch et al. 2019) can be used for fluid flow simulations (Mortensen et al. 2011) and, when coupled with the dolfin-adjoint library, can provide an efficiently computed discrete adjoint solution from a defined forward model (indicated by the label “D.1” in Fig. 1). However, more complex fluid flow modeling may require various possibly non-intuitive adjustments to the implementation for convergence and may result in an implementation that is less efficient than what OpenFOAM® provides (Mortensen et al. 2011).

The also well-known and established open-source software OpenFOAM® (based on finite volumes) (Weller et al. 1998; Chen et al. 2014) is capable of performing efficient fluid flow simulations, but its main drawback is the computation of the adjoint model (required for computing the sensitivities), which can be a highly demanding task for the programmer (indicated by the label “C” in Fig. 1) or may result in loss of computational efficiency (indicated by the labels “D.2” and “D.3” in Fig. 1).

Therefore, this work proposes using two well-known and established open-source softwares, combining the automated method provided by FEniCS/dolfin-adjoint with the simulation computed by OpenFOAM®. In terms of implementation, this approach only requires the specification of both simulation solvers (in FEniCS and OpenFOAM®), which makes it relatively simpler to implement than the other approaches, and should be, therefore, interesting for performing fluid flow topology optimization. In relation to the continuous adjoint approach, the proposed solution using the high-level discrete adjoint approach shows an inherent computational cost due to the interfacing between OpenFOAM® and FEniCS/dolfin adjoint. However, in relation to a continuous adjoint model in OpenFOAM®, it does not require an iterative procedure (SIMPLE) to solve the adjoint model.

In the point of view of the OpenFOAM® software, the automation of the generation of the adjoint model means that any model (such as any objective function, any turbulent/compressible/non-Newtonian model) may be considered with only an additional implementation consisting of specifying the forward model both in finite elements and finite volumes, which is much easier than deriving the adjoint model by hand for a complex model. In the point of view of FEniCS/dolfin-adjoint, the fluid simulation may be computed more efficiently by using OpenFOAM® (Mortensen et al. 2011), while significantly reducing the need of complex implementations and adjustments for convergence in the FEniCS/dolfin-adjoint implementation (Mortensen et al. 2011).

Therefore, the main objective of this work is to present a framework for topology optimization by using OpenFOAM® and finite element-based high-level discrete adjoint method (FEniCS/dolfin-adjoint). The numerical examples consider the traditional material model of fluid topology optimization (Borrvall and Petersson 2003). Three types of computational domains are illustrated: 2D, 2D axisymmetric, and 3D domains. Laminar or turbulent (Spalart-Allmaras model) flows are considered. The design variable is assumed to be nodal. The objective function is the energy dissipation. OpenFOAM® (Weller et al. 1998; Chen et al. 2014) is used for the finite volume simulation, while the sensitivities are computed by the adjoint model generated by FEniCS/dolfin-adjoint (Logg et al. 2012; Farrell et al. 2013; Mitusch et al. 2019), and IPOPT (Interior-Point Optimization algorithm) is used as the optimization algorithm (Wächter and Biegler 2006). The “FEniCS TopOpt Foam” library used in the implementation of this work is to be made available in a git repository.Footnote 1

This paper is organized as follows: in Sect. 2, the fluid flow model is described; in Sect. 3, the weak formulation (finite element method) of the problem is presented; in Sect. 4, the finite element/volume modeling is presented; in Sect. 5, the topology optimization problem is stated; in Sect. 6, the numerical implementation is described, along with the interfacing between OpenFOAM® and FEniCS/dolfin-adjoint; in Sect. 7, numerical examples are presented; and in Sect. 8, some conclusions are inferred.

2 Equilibrium equations

In this work, in order to exemplify the approach of interfacing OpenFOAM® with FEniCS/dolfin-adjoint, the fluid flow modeling is performed for incompressible fluid, and steady-state regime (Munson et al. 2009; White 2011). Therefore, the continuity and linear momentum (Navier-Stokes) equations considered are:

$$\begin{aligned}&\nabla {\tiny \cdot } \varvec{v} = 0 \end{aligned}$$
(1)
$$\begin{aligned}&\rho \nabla \varvec{v} {\tiny \cdot } \varvec{v} = \nabla {\tiny \cdot }(\varvec{T} + \varvec{T}_R) + \rho \varvec{f} - \kappa (\alpha ) \varvec{v}_\text{mat} \end{aligned}$$
(2)

where \(\varvec{v}\) is the fluid velocity, p is the fluid pressure, \(\rho\) is the fluid density, \(\mu\) is the fluid dynamic viscosity, \(\rho \varvec{f}\) is the body force per unit volume acting on the fluid, \(\varvec{f}_{r}(\alpha ) = - \kappa (\alpha ) \varvec{v}_\text{mat}\) is the resistance force of the porous medium used in topology optimization (\(\kappa (\alpha )\) is the inverse permeability (“absorption coefficient”), and \(\varvec{v}_{\text {mat}} = \varvec{v} - \varvec{v}_{\text {material}}\) is the velocity in relation to the porous material – when \(\varvec{v}_{\text {material}} = \varvec{0}\) (i.e., the solid material is stationary), \(\varvec{v}_{\text {mat}} = \varvec{v}\)), \(\alpha\) is the pseudo-density, which assumes values from 0 (solid) to 1 (fluid) (and is the design variable in topology optimization), and \(\varvec{T}\) is the fluid stress tensor given by

$$\begin{aligned} {\varvec{T} = 2\mu \varvec{\epsilon } - p\varvec{I}} , {\varvec{\epsilon } = \frac{1}{2}(\nabla \varvec{v} + \nabla {\varvec{v}}^{T})} \end{aligned}$$
(3)

The term \(\varvec{T}_R\) in Eq. (2) is the Reynolds (turbulent) stress tensor, which appears in RANS (Reynolds-Averaged Navier-Stokes) formulations. When considering a RANS formulation, the velocity (\(\varvec{v}\)) and pressure (p) fields refer to statistical time-averaged values.

In this work, the Spalart-Allmaras model is used for considering turbulence. The Spalart-Allmaras model (Spalart and Allmaras 1994; Bueno-Orovio et al. 2012; Wilcox 2006) is a single-equation turbulence RANS model, which is said to be adequate for mild boundary layer separations (Ansys 2006). According to Bardina et al. (1997), the Spalart-Allmaras model does not require a finer mesh resolution near walls in wall-bounded flows as two-equation turbulence models (such as k-\(\varepsilon\) and k-\(\omega\) models), and shows good convergence for simpler flows. Also, it is said to show improvements in the prediction of fluid flows under adverse pressure gradients (when the pressure increases toward the outlet) when compared to the standard k-\(\varepsilon\) and k-\(\omega\) models (Bardina et al. 1997). There are various modifications that have been proposed in the Spalart-Allmaras model along the years (NASA 2019). In this work, the modifications that are considered are based in the OpenFOAM® (OpenFOAM Foundation 2020) implementation. An additional term based on Yoon (2016), Dilgen et al. (2018), and Papoutsis-Kiachagias and Giannakoglou (2016) is included in order to take the effect of the modeled solid material (of topology optimization) into account. This way, the Spalart-Allmaras model is given by (OpenFOAM Foundation 2020):

$$\begin{aligned}&\varvec{T}_R = \mu _\text{T} (\nabla \varvec{v} + \nabla {\varvec{v}}^{T}) \text {, } \mu _{\mathrm {T}} = \rho f_{v1} {{\tilde{\nu }}_{\mathrm{T}}} \end{aligned}$$
(4)
$$\begin{aligned}&\rho \varvec{v}{\tiny \cdot }\nabla {\tilde{v}}_\text{T} = \underbrace{c_{b1}\rho {{\tilde{S}}}{{\tilde{v}}_\text{T}}}_{\text {Production}} + \underbrace{\left[ -c_{w1}f_{w}\rho \left( \frac{{{\tilde{v}}_\text{T}}}{\ell _{w}}\right) ^2 \right] }_{\text {Destruction}}\nonumber \\&\quad + \underbrace{\frac{1}{\sigma }\nabla {\tiny \cdot }(\rho (v+ {{\tilde{v}}_\text{T}})\nabla {{\tilde{v}}_\text{T}})}_{\text {Diffusion (conservative)}}\nonumber \\&\quad + \underbrace{\frac{c_{b2}}{\sigma }\rho \nabla {{\tilde{v}}_\text{T}}{\tiny \cdot }\nabla {{\tilde{v }}_\text{T}}}_{\text {Diffusion (non-conservative)}}\nonumber \\&\quad + \underbrace{\left[ - \lambda _{{\tilde{v }}_\text{T}} \kappa (\alpha ) {{\tilde{v }}_\text{T, mat}} \right] }_{\begin{subarray}{c} \text {Attenuation of turbulence}\\ \text {in the porous medium} \end{subarray}} \end{aligned}$$
(5)

where \({{\tilde{\nu }}_\text{T}}\) is the auxiliary turbulent viscosity of the Spalart-Allmaras model, \(\mu _\text{T}\) is the turbulent viscosity (i.e., the flow parameter which accounts for the statistical time-averaged effect of turbulence in the stress tensor), \({{\tilde{\nu }}_\text{T, mat}} = {{\tilde{\nu }}_\text{T}} - {{\tilde{\nu }}_\text{T, wall}}\) is the auxiliary turbulent viscosity in relation to its “wall value” (\({{\tilde{\nu }}_\text{T, wall}}\), which is assumed as equal to 0 \(\hbox {m}^2\)/s), and \(\lambda _{{\tilde{\nu }}_\text{T}}\) is an adjustable parameter for the intensity of the attenuation of turbulence inside the solid material (it can be chosen, for example, as \(\lambda _{{\tilde{\nu }}_\text{T}} = 1\)). The other terms of Eq. (5) are specified as follows:

$$\begin{aligned} {} &c_{b1} = 0.1355, \,c_{b2} = 0.6220\\&c_{v1} = 7.1 , \sigma = \frac{2}{3}\, \text {, }f_{\Omega}= 0.3\\&c_{w1} = \frac{c_{b1}}{\kappa ^2} + \frac{1 + c_{b2}}{\sigma } , c_{w2} = 0.3 , c_{w3} = 2\\&\chi = \frac{{{\tilde{\nu }}_{\mathrm{T}}}}{\nu }\\&{\tilde{S}} = \text {max} \left[ S + \frac{{{\tilde{\nu }}_{\mathrm {T}}}}{\kappa ^2\ell _{w}^2}f_{v2},f_{\Omega}{\Omega }_{\mathrm{m}}\right] \\&S = {\Omega }_{\mathrm{m}},{\Omega }_{\mathrm{m}} = \sqrt{2\varvec{{\Omega }}{\tiny \cdot }\varvec{{\Omega }}}, \varvec{{\Omega }} = \frac{1}{2}(\nabla \varvec{v} - \nabla {\varvec{v}}^{T})\\&f_{v1} = \frac{\chi ^3}{\chi ^3 + c_{v1}^3},\,f_{v2} = 1 - \frac{\chi }{1 + \chi f_{v1}}\\&f_{w} = g \left( \frac{1 + c_{w3}^6}{g^6 + c_{w3}^6}\right) ^{1/6} , g = r_i + c_{w2} (r_i^6 - r_i)\\&r_i = \text {min}\left[ \frac{{{\tilde{\nu }}_{\mathrm {T}}}}{{{\tilde{S}}_r}\kappa ^2\ell _w^2}, 10\right] , {\tilde{S}}_r = \text {max}\left[ {\tilde{S}}, 10^{-6}\right] \\ \end{aligned}$$
(6)

where \(\kappa = 0.41\) is the von Kármán constant, \(\ell _{w}\) is the wall distance, and \(\nu = \frac{\mu }{\rho }\) is the kinematic viscosity.

In fluid topology optimization, the walls change according to the distribution of the pseudo-density (\(\alpha\)), which means that the wall distance (\(\ell _{w}\)) also changes accordingly. Thus, in order to consider such changes in the simulation and in the adjoint model, a modified Eikonal equation (Yoon 2016) is considered, which is given as:

$$\begin{aligned}&\ell _w = \frac{1}{G} - \frac{1}{G_0}\text {, }G_0 = \frac{1}{\ell _\text{ref}} \end{aligned}$$
(7)
$$ \begin{gathered} \underbrace {{\nabla G \cdot \nabla G}}_{\begin{subarray}{l} {\text{From}}\,{\text{the}}\,{\text{original}}\, \\ {\text{Eikonal}}\,{\text{equation}} \end{subarray} } + \,\underbrace {{\sigma _{w} G\left( {\nabla ^{2} G} \right)}}_{\begin{subarray}{l} {\text{Elliptic}}\,{\text{diffusion}}\, \\ {\text{for}}\,{\text{allevaiting}} \\ {\text{non - linearities}} \end{subarray} } = \hfill \\ \underbrace {{\left( {1 + 2\sigma _{w} } \right)}}_{\begin{subarray}{l} {\text{For}}\,{\text{satisfying}} \\ {\text{inverse}}\,{\text{linear}} \\ {\text{behaviour}} \end{subarray} }\,\,\underbrace {{G^{4} }}_{\begin{subarray}{l} {\text{From}}\,{\text{the}}\,{\text{original}} \\ {\text{Eikonal}}\,{\text{equation}} \end{subarray} } + \,\underbrace {{\gamma \left( \alpha \right)\left( {G - G_{0} } \right)}}_{\begin{subarray}{l} {\text{Porous}}\,{\text{medium}} \\ {\text{penalization}} \end{subarray} } \hfill \\ \end{gathered} $$
(8)

where G is the reciprocal wall distance, \(\ell _\text{ref}\) is a reference value for the wall distance [which leads \(\ell _w\) to emphasize objects that are larger than it, and can be chosen, for example, as the maximum size of the elements of the mesh (largest of the maximum distances between two vertices of an element)], \(\gamma (\alpha )\) is the wall penalization, which varies according to the pseudo-density (\(\alpha\)) in order to consider the presence of a wall changing during the topology optimization, and \(\sigma _w\) is a relaxation factor for the wall distance computation.

2.1 Boundary value problem

The three types of computational domain considered in this work are shown in Fig. 2. It can be reminded that the definition of the differential operators and coordinates are different in the 2D axisymmetric domain (due to axisymmetry and cylindrical coordinates) (Alonso et al. 2018). Generically, a 2D axisymmetric domain may include the symmetry axis or not. The boundary value problem is specified for the three types of computational domain considered in this work as:

$$\begin{aligned} \rho \nabla \varvec{v}{\tiny \cdot }\varvec{v} = \nabla {\tiny \cdot }(\varvec{T}&+ \varvec{T}_R) + \rho \varvec{f} - \kappa (\alpha )\varvec{v}_{\mathrm {mat}}&\text { in } {\Omega}\\ \nabla {\tiny \cdot }\varvec{v}&= 0&\text { in } {\Omega}\\ \rho \varvec{v}{\tiny \cdot }\nabla {{\tilde{\nu }}_{\mathrm {T}}}&= c_{b1}\rho {{\tilde{S}}}{{\tilde{\nu }}_{\mathrm{T}}} + \\ \left[ -c_{w1}f_{w}\rho \left( \frac{{{\tilde{\nu }}_{\mathrm {T}}}}{\ell _{w}}\right) ^2 \right]&+ \frac{1}{\sigma }\nabla {\tiny \cdot }(\rho (\nu + {{\tilde{\nu }}_{\mathrm {T}}})\nabla {{\tilde{\nu }}_{\mathrm {T}}}) + \\ \frac{c_{b2}}{\sigma }\rho \nabla {{\tilde{\nu }}_{\mathrm {T}}}{\tiny \cdot }\nabla {{\tilde{\nu }}_{\mathrm {T}}}&+ \left[ - \lambda _{{\tilde{\nu }}_{\mathrm {T}}} \kappa (\alpha ) {{\tilde{\nu }}_\text{T, mat}} \right]&\text { in } {\Omega}\\ {\nabla {G}}{\tiny \cdot }{\nabla {G}} + \sigma _w G&(\nabla ^2 G) = \\ (1 + 2 \sigma _w) G^4&+ \gamma (\alpha )(G ~-~ G_0)&\text { in } {\Omega}\\ \varvec{v} = \varvec{v}_{in} \text { and } {{\tilde{\nu }}_{\mathrm {T}}}&= {{\tilde{\nu }}_{\mathrm {T}, in}} \text { and } \nabla {G} {\tiny \cdot } \varvec{n} = 0&\text { on } {\Gamma }_{\text {in}}\\ \varvec{v} = \varvec{0} \text { and } {{\tilde{\nu }}_{\mathrm {T}}}&= {{\tilde{\nu }}_{\mathrm{T}, _{wall}}} \text { and } G = G_0& \quad \text { on } {\Gamma }_{\text {wall}}\\ {v_r}&= {0} \\ \text { and } \frac{\partial v_{r}}{\partial r } = \frac{\partial v_{z}}{\partial r }&= \frac{\partial p}{\partial r } = \frac{\partial {{\tilde{\nu }}_{\mathrm {T}}}}{\partial r } = \frac{\partial G}{\partial r } = {0}&\text { on } {\Gamma }_{\text {sym}}\\ (\varvec{T} + \varvec{T}_R) {\tiny \cdot } \varvec{n} = \varvec{0}&\text { and } \nabla {{\tilde{\nu }}_{\mathrm {T}}} {\tiny \cdot } \varvec{n} = 0 \\ \text { and } \nabla {G}&{\tiny \cdot } \varvec{n} = 0&\text { on } {\Gamma }_{\text {out}}\\ \end{aligned}$$
(9)

where \({\Omega}\), \({\Gamma}_{\text {in}}\), \({\Gamma}_{\text {wall}}\), \({\Gamma}_{\text {sym}}\) , and \({\Gamma}_{\text {out}}\) can be visualized in Fig. 2. The inlet boundary (\({\Gamma}_{\text {in}}\)) consists of an inlet velocity profile (\(\varvec{v}_{\text {in}}\)), an imposed auxiliary turbulent viscosity value (\({{\tilde{\nu }}_{\mathrm {T}, {\text {in}}}}\)), and a zero normal flux boundary condition for the reciprocal wall distance (G). On the walls (\({\Gamma}_{\text {wall}}\)), the no-slip condition is imposed for the velocity, a fixed value is imposed for the auxiliary turbulent viscosity (\({{\tilde{\nu }}_\text{T, wall}} = 0 \text { m}^2\text {/s}\)), and a fixed value is imposed for the reciprocal distance (\(G_0\)). The outlet boundary (\({\Gamma}_{\text {out}}\)) consists of an outlet stress free condition (i.e., open to the atmosphere) for the pressure-velocity formulation, where \(\varvec{n}\) is the normal vector to the boundaries, which points outside the computational domain. On the outlet boundary (\({\Gamma}_{\text {out}}\)), a developed auxiliary turbulent viscosity is imposed (through zero normal flux) and a zero normal flux boundary condition is imposed for the reciprocal wall distance (G). In the 2D axisymmetric domain, if there is a symmetry axis (\({\Gamma}_{\text {sym}}\)) bordering it, the derivatives toward the r coordinate are imposed to be zero, as well as the radial velocity.

Fig. 2
figure 2

Examples of boundaries for 2D, 2D axisymmetric, and 3D domains

In the boundary value problem [Eq. (9)], the wall distance may be computed separately during topology optimization, since it only depends on the current distribution of the pseudo-density (\(\alpha\)). However, it has to be later included in the adjoint model.

3 Finite element method

In order to automatically derive the adjoint model, it is needed to specify the weak form of the finite element method in FEniCS. The weak form is defined as follows.

3.1 Weak form

In the finite element method, the equilibrium equations are modeled by a corresponding weak form. In the following equations, the computational domain is represented as \(d{{\Pi}}\), and the boundary of the computational domain is represented as \(d{{\Gamma}_{{\Pi}}}\). For 2D and 3D flows, \(d{{\Pi}}=d{\Omega}\) and \(d{{\Gamma}_{{\Pi}}}=d{\Gamma}\), while, for 2D axisymmetric flow, \(d{{\Pi}}=2\pi r d{\Omega}\) and \(d{{\Gamma}_{{\Pi}}}=2\pi r d{\Gamma}\). By considering the weighted-residual and Galerkin methods for the mixed (velocity-pressure) formulation, (Reddy and Gartling 2010; Alonso et al. 2018)

$$\begin{aligned} {R}_{c} = \int _{{\Pi}}[\nabla {\tiny \cdot }\varvec{v}]{\textit{w}}_{p}d{\Pi}\end{aligned}$$
(10)
$$ \begin{gathered} R_{m} = \int_{\Pi } {\left[ {\rho \nabla \varvec{v} \cdot \varvec{v} - \rho \varvec{f}} \right]\varvec{w_{v}} d\Pi } + \int _{\Pi}{\varvec{T} \cdot \left( {\nabla \varvec{w_{v}} } \right)} d\Pi \hfill - \mathop{\int\mkern-20.8mu \circlearrowleft} \nolimits_{{\Gamma _{\Pi } }} {\left( {\varvec{T} \cdot \varvec{w_{v}} } \right) \cdot \varvec{n}d\Gamma _{\Pi } } - \int_{\Pi } {\varvec{f}_{r} \left( \alpha \right) \cdot \varvec{w_{v}} d\Pi } \hfill \\ \end{gathered} $$
(11)
$$\begin{aligned} R_{{SA}} = &\int_{\Pi} {\left[ {\rho \varvec{v} \cdot \nabla \widetilde{v}_{{\rm T}} - c_{{b1}} \rho \widetilde{S}\widetilde{v}_{{\rm T}} + c_{{w1}} f_{w} \rho \left( {\frac{{\widetilde{v}_{\rm T}}}{{\ell _{w} }}} \right)^2 - \frac{{c_{{b2}} }}{\sigma }\rho \nabla \widetilde{v}_{\rm T}} \right]w_{{\widetilde{v}_{\rm T}}} d\Pi } \\& { + \int_{\Pi } {\frac{1}{\sigma } \left( {\rho \left( {v + \widetilde{v}_{{\rm T}} } \right)\nabla \widetilde{v}_{{\text{T}}} } \right) \cdot \nabla w_{{\widetilde{v}_{\text{T}}}} } }d\Pi \\& { - \mathop{\int\mkern-20.8mu \circlearrowleft}\nolimits_{\Gamma_\Pi } {\frac{1}{\sigma }\varvec{n} \cdot \left( {\rho \left( {v + \widetilde{v}_{{\rm T}} } \right)\nabla \widetilde{v}_{{\text{T}}} w_{\widetilde{v}_{\text{T}}} } \right)d\Gamma_ \Pi } } \\& { - \int_{\Pi } {\left[ { - \lambda _{{\widetilde{v}_T}} \kappa \left( \alpha \right)\widetilde{v}_{{\rm T, mat}} } \right]w_{\widetilde{v}_{\text {T}}}d\Pi } } \end{aligned} $$
(12)
$$ \begin{aligned} R_{w} =& {\int_{\Pi } {\left[ {\nabla G \cdot \nabla G - \left( {1 + 2\sigma _{w} } \right)G^{4} } \right]} w_{G} d\Pi } \\ & { - \int_{\Pi } {\left[ {\left( {\nabla G} \right) \cdot \nabla \left( {\sigma _{w} Gw_{G} } \right)} \right]d\Pi } } \\ &{ + \mathop{\int{\mkern-20.8mu \circlearrowleft}}\nolimits_{\Gamma_\Pi} {\varvec{n} \cdot \left[ {\left( {\nabla G} \right)\left( {\sigma _{w} Gw_{G} } \right)} \right]d\Gamma_\Pi } } \\ & { - \int_{\Pi } {\left[ {\gamma \left( \alpha \right)\left( {G - G_{0} } \right)} \right]w_{G} d\Pi } } \end{aligned} $$
(13)

where the subscripts “c”, “m”, “\(\text {SA}\)” and “w” refer to the “continuity” equation, the “linear momentum” (Navier-Stokes) equations, the “Spalart-Allmaras” equation and the “wall distance” equation (modified Eikonal equation), respectively. The test functions of the state variables (p, \(\varvec{v}\), \({{\tilde{\nu }}_\text{T}}\) and G) are given by \({\textit{w}}_{p}\), \({{\varvec{{w}}}}_{v}\), \({\textit{w}}_{{\tilde{\nu }}_\text{T}}\) and \({\textit{w}}_G\), respectively. Under 2D axisymmetric flow, since the integration domain (\(2\pi r d{\Omega}\)) has a constant multiplier (\(2\pi\)), which does not influence when solving the weak form, Eqs. (10), (11), (12) and (13) may be optionally divided by \(2\pi\) (Alonso et al. 2018, 2019).

From the mutual independence of the test functions, the equations of the weak form can be summed to a single equation:

$$\begin{aligned} F = {R}_{c} + {R}_{m} + {R}_\text {SA} + {R}_{w} = 0 \end{aligned}$$
(14)

where it is also possible to solve \({R}_{w} = 0\) separately, because the computation of the wall distance is uncoupled from the other equations, depending only on the pseudo-density (\(\alpha\)). In such case, which is considered in this work, the two weak forms may be sequentially solved:

$$\begin{aligned} F_1= & {} {R}_{w} = 0 \end{aligned}$$
(15)
$$\begin{aligned} F_2= & {} {R}_{c} + {R}_{m} + {R}_\text {SA} = 0 \end{aligned}$$
(16)

4 Finite element/finite volume modeling

The LBB (Ladyžhenskaya-Babuška-Brezzi) condition is a necessary condition for the numerical stability of the fluid flow simulation when considering the finite element formulation (Brezzi and Fortin 1991; Reddy and Gartling 2010; Langtangen and Logg 2016). The main effect of respecting the LBB condition is numerical, in which the pressure distribution becomes consistent with the velocity field. Some LBB-stable elements are Taylor-Hood and MINI elements. In this work, MINI elements (linear elements enriched by a bubble function) (Arnold et al. 1984; Logg et al. 2012) are used for the velocity-pressure formulation (see Fig. 3) (in 3D, the order of the bubble enrichment is increased to 4), due to their lower computational cost in relation to Taylor-Hood elements. The auxiliary turbulent viscosity of the Spalart-Allmaras model (\({\tilde{\nu }}_\text{T}\)) and the wall distance (\(\ell _w\) and, therefore, G) are selected with 1st degree interpolation (P1 element). The pseudo-density (design variable) is chosen with 1st degree interpolation (P1 element), which also enables the possible use of a Helmholtz filter in topology optimization if needed (Lazarov and Sigmund 2010), due to the fact that this filter requires the existence of the first derivative (nonexistent for element-wise (dP0, “DG0”) variables). As can be noticed, there may be some “loss” of precision when converting between finite element and finite volume methods, due to the different interpolation schemes. In such case, it is also possible to consider different discretizations/resolutions for the OpenFOAM® and FEniCS meshes, but, in this work, for simplicity, they are assumed to be the same.

Fig. 3
figure 3

Finite elements and volumes choice for the state variables: pressure, velocity, auxiliary turbulent viscosity of the Spalart-Allmaras model (\({\tilde{\nu }}_\text{T}\)), wall distance (\(\ell _w\) and, therefore, G), and pseudo-density (design variable) (\(\alpha\))

Although Fig. 3 shows a 2D representation of the finite elements/volumes as triangles, they are implemented differently for each computational domain shown in Fig. 2 while taking into account Fig. 6: for the 2D case, the FEniCS mesh is composed of triangles, while the OpenFOAM® mesh is composed of prisms; for the 2D axisymmetric case, the FEniCS mesh is composed of triangles, while the OpenFOAM® mesh is composed of prisms/tetrahedrons/pyramids; and, for the 3D case, the FEniCS mesh is composed of tetrahedrons, as well as the OpenFOAM® mesh. The conversion between the variables in FEniCS and OpenFOAM® is detailed in Sect. 6.2.

5 Formulation of the topology optimization problem

5.1 Material model for the inverse permeability

The material model in fluid topology optimization aims to block fluid flow, while aiming to obtain a sufficiently discrete distribution for the pseudo-density (\(\alpha\)) inside the design domain (with values 0 for solid, and 1 for fluid). The subtle transition between solid (0) and fluid (1) (binary values) is normally relaxed for better numerical conditioning, allowing an intermediate porous medium (“gray”, with a pseudo-density between 0 and 1) (real values). The amount of “strength” to block the fluid is referred as “inverse permeability”, which, as the name says, provides an opposite behavior to that of permeability. Borrvall and Petersson (2003) consider a convex interpolation function for the inverse permeability, given by:

$$\begin{aligned} \kappa (\alpha ) = \kappa _{\text {max}} + (\kappa _{\text {min}} - \kappa _{\text {max}})\alpha \frac{1 + q}{\alpha + q} \end{aligned}$$
(17)

where \(\kappa _{\text {max}}\) and \(\kappa _{\text {min}}\) are, respectively, the maximum and minimum values of the inverse permeability of the porous medium. The parameter \(q > 0\) is a penalization parameter that controls the convexity (i.e., the relaxation) of the material model, where large values of q lead to a less relaxed material model. There is no clear rule on how q should be chosen, since the specific fluid flow topology optimization problem may behave better with either one value or another. In general, it is better not to leave the material model overly relaxed (i.e., \(q \leqslant 0.01\)), at least in the last optimization iterations, due to the consequently worse fluid flow blocking capacity.

5.2 Material model for the wall penalization

For the modified Eikonal equation, the material model may be based on Eq. (17), being given as the wall penalization

$$\begin{aligned} \gamma (\alpha ) = \gamma _{\text {max}} + (\gamma _{\text {min}} - \gamma _{\text {max}})\alpha \frac{1 + q}{\alpha + q} \end{aligned}$$
(18)

where \(\gamma _{\text {max}}\) and \(\gamma _{\text {min}}\) are, respectively, the maximum and minimum values of the wall penalization of the porous medium, and q is the same as in Eq. (17).

5.3 Topology optimization problem

The topology optimization problem can be formulated as follows.

(19)

where f is the specified volume fraction, \(V_{0} = \int _{{\Pi}_{\alpha }} d{\Pi}_{\alpha }\) is the volume of the design domain (represented as \({\Pi}_{\alpha }\)), \(J(p(\alpha ),\varvec{v}(\alpha ){{\tilde{\nu }}_\text{T}}(\alpha ),\ell _w(\alpha ),\alpha )\) is the objective function, and \(p(\alpha )\), \(\varvec{v}(\alpha )\), \({{\tilde{\nu }}_\text{T}}(\alpha )\) and \(\ell _w(\alpha )\) are the state variables obtained by solving the boundary value problem [Eq. (9)], which features an indirect dependency with respect to the design variable \(\alpha\).

5.4 Objective function

The objective function (J) is chosen as the energy dissipation (\({\Phi }\)) (Borrvall and Petersson 2003) including the turbulence effect [as in Yoon (2016)]. The energy dissipation is closely related to the head loss (Borrvall and Petersson 2003), and generally behaves well in fluid topology optimization. By considering zero external body forces,

$$\begin{aligned} \begin{aligned} {\Phi }&= \int _{{\Pi}}\left[ \frac{1}{2} (\mu + \mu _\text{T}) (\nabla \varvec{v} + {\nabla \varvec{v}}^{T}){\tiny \cdot }(\nabla \varvec{v} + {\nabla \varvec{v}}^{T})\right] d{\Pi}\\&+ \int _{{\Pi}}\varvec{\kappa } (\alpha )\varvec{v}_{\text {mat}}{\tiny \cdot }\varvec{v} d{\Pi}\end{aligned} \end{aligned}$$
(20)

5.5 Sensitivity analysis

The sensitivity is given by the adjoint method from the finite element matrices and automatic differentiation as

$$\begin{aligned}&\left( \frac{dJ}{d\alpha }\right) ^{\text {*}} = \left( \frac{\partial J}{\partial \alpha }\right) ^{\text {*}} - \left( \frac{\partial F}{\partial \alpha }\right) ^{\text {*}}{\varvec{\lambda }}_{J} \end{aligned}$$
(21)
$$\begin{aligned}&\left( \frac{\partial {F}}{\partial (p,\varvec{v}, {{\tilde{\nu }}_\text{T}},\ell _w)}\right) ^{\text {*}}{\varvec{\lambda }}_{J} = \left( \frac{\partial J}{\partial (p,\varvec{v}, {{\tilde{\nu }}_\text{T}},\ell _w)}\right) ^{\text {*}} \nonumber \\&\quad \quad \quad \text { (adjoint equation)} \end{aligned}$$
(22)

where \(J = {\Phi }\) is the objective function, which is the energy dissipation, the weak form equation is given by \({F} = 0\), “*” represents conjugate transpose, and \({\varvec{\lambda }}_{J}\) is the adjoint variable (Lagrange multiplier of the weak form) for this case. If the uncoupled form given by Eqs. (15) and (16) is considered, the two weak form dependencies need to be sequentially combined into a new equation for the sensitivity.

5.6 Helmholtz pseudo-density filter

Some of the topology optimization results in this work consider the use of a regularization. Regularizations are a common mechanism in topology optimization in order to counter possible numerical instabilities due to the lack of smoothness in the finite element equations (Kawamoto et al. 2013), which would possibly lead to mesh dependency and local minima (Sigmund and Petersson 1998; Bendsøe and Sigmund 2003; Sigmund 2007). The regularization that is considered is the use of a Helmholtz filter, which is a PDE-based topology optimization pseudo-density filter, having been proposed by Lazarov and Sigmund (2010). It is schematically shown in Fig. 4, where \(\alpha\) is the original design variable and \({\alpha }_{f}\) is the filtered design variable.

Fig. 4
figure 4

Application of a Helmholtz filter

Figure 4 illustrates the fact that the Helmholtz filter consists of weighting all values of the original design variable (\(\alpha\)) with a Green’s function, which is a function that is always positive and whose integral is equal to 1 (“100%”) (Lazarov and Sigmund 2010). When choosing smaller values for the filter length parameter (\(r_H\)), this function approaches a Dirac’s delta function \(\left({\alpha }_{f} \mathop{\rightarrow}\limits ^{r_H\rightarrow 0^{+}} \alpha \right)\). This “Green’s function” averaging is the same as solving a modified Helmholtz equation with homogeneous Neumann boundary conditions, whose boundary value problem is given by (Lazarov and Sigmund 2010; Zauderer 1989)

$$\begin{aligned} \begin{aligned} - r_{H}^2 \nabla ^2 {\alpha }_{f} + {\alpha }_{f}&= \alpha&\text { in } {\Pi}\\ \frac{\partial {{\alpha }_{f}}}{\partial {\varvec{n}}}&= \varvec{0}&\text { on } {\Gamma}_{\Pi}\end{aligned} \end{aligned}$$
(23)

where \({\alpha }\) is the original design variable, \({\alpha }_{f}\) is the filtered design variable, and \(r_H\) is the filter length parameter.

The weak form is obtained by multiplying Eq. (23) by the test function \({\textit{w}}_{HF}\) and integrating in the whole design domain, which leads to

$$\begin{aligned} \begin{aligned} r_{H}^2 \int _{{\Pi}} (\nabla {\alpha }_{f}){\tiny \cdot }\nabla {\textit{w}}_{HF} d {\Pi}+ \int _{{\Pi}}&{\alpha }_{f} {\textit{w}}_{HF} d {\Pi}\\&- \int _{{\Pi}} \alpha {\textit{w}}_{HF} d {\Pi}= 0 \end{aligned} \end{aligned}$$
(24)

When a Helmholtz filter is considered, the value given by \({\alpha }_{f}\) is used in the place of \(\alpha\) in all other equations, and the sensitivities need to include the dependency of \({\alpha }_{f}\) in relation to \(\alpha\) [i.e., from the chain rule for derivatives (\(\frac{{\text{d}} J}{{\text{d}} \alpha } = \frac{{\text{d}} J}{{\text{d}}{\alpha }_{f}} \frac{{\text{d}} {\alpha }_{f}}{{\text{d}}\alpha }\))] (Lazarov and Sigmund 2010).

6 Numerical implementation of the optimization problem

The fluid flow simulation is solved in the finite volumes software OpenFOAM® (version from “The OpenFOAM foundation”) (Weller et al. 1998; Chen et al. 2014), by using the SIMPLE (Semi-Implicit Method for Pressure-Linked Equations) algorithm (Patankar 1980; OpenFOAM Wiki 2014). The implementation of the SIMPLE algorithm is practically the same as the “simpleFoam” solver from OpenFOAM®, but including the additional inverse permeability term shown in Eq. (2). Then, the additional inverse permeability term is also included in the Spalart-Allmaras model in OpenFOAM®. The adjoint model is computed in the finite elements software FEniCS (Logg et al. 2012) through dolfin-adjoint (Farrell et al. 2013; Mitusch et al. 2019). The topology optimization problem is solved with IPOPT (Wächter and Biegler 2006), from the interface provided by the dolfin-adjoint library.

6.1 Interfacing OpenFOAM® with FEniCS/dolfin-adjoint

The main idea for performing an interfacing between OpenFOAM® (finite volume method) with FEniCS/dolfin-adjoint (finite element method) is for efficiently computing the fluid flow simulation in OpenFOAM®, while the adjoint model can be automatically derived and computed in FEniCS/dolfin-adjoint.

FEniCS (Logg et al. 2012) is a finite element software implemented in C++ that uses automatic differentiation and a high-level language (UFL) for representing the weak form and functionals for the finite element matrices. From its high-level notation, the adjoint model can be automatically derived from the weak form and objective functions by the dolfin-adjoint library (Farrell et al. 2013; Mitusch et al. 2019). The dolfin-adjoint library is restricted to the Python interface of FEniCS.

OpenFOAM® (Weller et al. 1998; Chen et al. 2014) is an open-source CFD (Computational Fluid Dynamics) software written in C++, in which the syntax for specifying the finite volume equations is, as in the case of FEniCS UFL, close to the representation of the equations themselves. Since OpenFOAM® operates in the lowest degree of finite volumes (element-wise), the simulation should become less computationally expensive than when using finite elements with the traditional Taylor-Hood elements or MINI elements for a same discretization (although the numerical precision should be lower due to the lower interpolation degree of the finite volumes in OpenFOAM®). Also, the finite volume method is based on the local conservation of fluxes (i.e., between finite volumes), which is different from the finite element method [i.e., based on the global conservation of fluxes – except for Discontinuous Galerkin finite elements (Li 2006)]. The main drawback regarding the use of OpenFOAM® in topology optimization is the derivation of the adjoint model, which was mentioned in Sect. 1.

Since dolfin-adjoint is a Python-only library, OpenFOAM®’s C++ and shell script functionalities should be made accessible in Python. The interfacing between FEniCS/dolfin-adjoint and OpenFOAM®, for topology optimization, is performed through a library developed in this work (“FEniCS TopOpt Foam”).

6.2 Interfacing OpenFOAM® with dolfin-adjoint for computing the sensitivities

The objective function is computed directly with FEniCS after the simulation with OpenFOAM® is performed, while the computation of the sensitivities uses the simulation result for later solving the adjoint model equations. A diagram illustrating the computation of the sensitivities is shown in Fig. 5.

Fig. 5
figure 5

Diagram illustrating the computation of the sensitivities when using OpenFOAM® and the “FEniCS TopOpt Foam” library for the fluid flow simulation

The diagram of Fig. 5 starts with a call from the optimizer for dolfin-adjoint to compute the sensitivities. The first step is computing the forward model (i.e., the simulation). It starts by passing the mesh (FEniCS “Mesh”), together with a boundary marking (FEniCS “MeshFunction”) (i.e., names of each group of facets [edges (2D/2D axisymmetry) or faces (3D)] of the boundary), to “FEniCS TopOpt Foam” to convert to the OpenFOAM® mesh format. It can be mentioned that OpenFOAM® operates only in 3D meshes/coordinates, but allows simulating for 2D and 2D axisymmetric flows if the mesh has a specific construction [i.e., one-element uniform thickness (for the 2D mesh), and one-element “wedge” thickness (i.e., thickness linearly varying from zero radius, for a sufficiently small wedge angle) (for the 2D axisymmetric mesh)] and specific boundary conditions [“empty” for the parallel faces with respect to the 2D plane (of the 2D mesh), and “wedge” for the parallel faces with respect to the 2D plane (of the 2D axisymmetric mesh)] (see Fig. 6). Since, in OpenFOAM®, the boundary conditions are applied on the external faces of the 3D mesh, the symmetry axis boundary condition (from 2D axisymmetry) is implicitly considered when applying the “wedge” boundary conditions in OpenFOAM®. A similar scheme of using a 3D mesh for 2D/2D axisymmetric simulation is also used in Ansys®CFX. If the mesh is the same during all iterations of the topology optimization, this conversion can be performed a single time.

Fig. 6
figure 6

Representation of 2D and 2D axisymmetric domains in FEniCS and OpenFOAM® (Obs. The specific boundary conditions “wedge” are presented separately, because they are imposed separately on each “almost parallel” face with respect to the 2D plane in OpenFOAM®)

Then, the state variables (FEniCS “Function” ’s), the design variable (FEniCS “Function”), the boundary conditions (specified as required by OpenFOAM®) and other setup variables are converted by “FEniCS TopOpt Foam” to variable and configuration files. The variable and configuration files in OpenFOAM® are located in three subfolders: “0” (initial guess for the simulation), “constant” (mesh and properties) and “system” (solver parameters). With the OpenFOAM® files prepared, a specific solver for OpenFOAM®, which corresponds to the simulation defined in FEniCS, is selected for using in the simulation. In case the simulation includes the design variable, the “default” OpenFOAM® solvers can not be used without an adjustment that includes the design variable in it (i.e., a “new” solver has to be programmed). Then, the OpenFOAM® simulation is performed. After the simulation, the state variable files of the result of the OpenFOAM® simulation are converted to the state variables in FEniCS. With the simulation result, dolfin-adjoint is now used to compute the adjoint model that is automatically generated from the forward model specified in FEniCS. The conversion from the OpenFOAM® files to the FEniCS variables (see Fig. 7) is performed by first mapping the internal values of the OpenFOAM® variables to element-wise variables in FEniCS (dP0, “DG0”). Then, the element-wise variables are projected (FEniCS “project”) into the interpolation that is being used in the adjoint model. The isolated state variables are then joined together in a single state vector by using a “FunctionAssigner” in FEniCS. In the case of turbulent variables, it may be needed to guarantee that their conversion to FEniCS is strictly positive and non-zero (compensating any numerical error that may appear in the conversions), because some turbulence models rely on some specific square-roots/divisions, and some other specific square-roots/divisions may arise due to the automatic differentiation performed by FEniCS. After this imposition, a small-radius Helmholtz filter (Lazarov and Sigmund 2010) may be applied in the turbulent variables in order to slightly filter (“alleviate”) some consequent sharp transitions which may hinder post-processing operations in FEniCS. An additional step is reimposing the original Dirichlet boundary conditions (FEniCS “DirichletBC”) onto the state vector, because the converted values from OpenFOAM® to FEniCS correspond only to the internal values of each cell and not to the external facets, which may generate numerical error on the boundaries. For the sake of completeness, the weak form that corresponds to a projection (FEniCS “project” function) is:

$$\begin{aligned} \begin{aligned} \int _{{\Pi}} a_{\mathrm {orig}} {\textit{w}}_{p} d {\Pi}= \int _{{\Pi}} a_p {\textit{w}}_{p} d {\Pi}\end{aligned} \end{aligned}$$
(25)

where \(a_{\mathrm {orig}}\) is the function that is being projected, while \(a_p\) is the projected function [obtained from solving Eq. (25)] and \({\textit{w}}_{p}\) is the corresponding test function for the projection.

Fig. 7
figure 7

Diagram illustrating the conversion of variables between OpenFOAM® and FEniCS

The interfacing of the simulation with dolfin-adjoint requires “overloading” a specific internal function of the solver object in the dolfin-adjoint library, regarding the “forward simulation” (which is called “_forward_solve”, and is located inside the “SolveBlock” class).

In terms of a parallel computation of the simulation and optimization, both OpenFOAM® and FEniCS provide independent implementations of parallelism out of the box, which means that both softwares may partition the mesh differently according to their needs and what is set up by the user, and also independently call MPI operations. In the current version of “FEniCS TopOpt Foam”, it is possible to consider both parallelisms independently, which means that FEniCS may be set to run in parallel, such as from “mpiexec -n 2 python my_code.py” (for 2 processes), while OpenFOAM® may be set up to run in parallel from “FEniCS TopOpt Foam” functions independently.

6.3 Choice of boundary conditions in OpenFOAM®

The boundary conditions that are possible to impose in OpenFOAM® may be different from the ones that are imposed in FEniCS due to the different solution methods and systems of equations (of finite volumes and finite elements, respectively). Therefore, the boundary conditions should be chosen to be with a close resemblance for corresponding simulation results. Although other variations are possible, one possibility for velocity and pressure is shown in Fig. 8. For the auxiliary turbulent viscosity of the Spalart-Allmaras model (\({{\tilde{\nu }}_{\mathrm{T}}}\)), the boundary conditions are the same as the ones used in Eq. (9) (i.e., the same as in the finite element method). The wall distance [\(\ell _w\), from Eq. (8)] is computed through the finite element method and is later imported into OpenFOAM® – This procedure avoids having to implement and solve a similar equation that should aim to attain the same wall distance value from FEniCS in OpenFOAM®.

Fig. 8
figure 8

Correspondence of boundary conditions for velocity and pressure between finite elements (FEniCS) and finite volumes (OpenFOAM®) considered in this work

Although in finite elements (FEniCS), no boundary conditions need to be explicitly imposed for the pressure, and for the outlet velocity (because of the stress free boundary condition), OpenFOAM® (finite volumes) requires all boundary conditions to be explicitly imposed.

On the walls, the normal gradient of the pressure is set to zero (\(\frac{\partial p}{\partial \varvec{n}} = \varvec{0}\)) in OpenFOAM® (Neumann boundary condition). This boundary condition is originated from Prandtl’s boundary layer equations (Schlichting 1979), where, inside the boundary layer, \(\frac{1}{\rho } \frac{\partial p}{\partial \varvec{n}} = {\mathcal {O}}(\delta_{\mathrm{BL}}) \approx \varvec{0}\), where \(\delta _{\mathrm{BL}}\) is the thickness of the boundary layer, \({\mathcal {O}}(\delta _{\mathrm{BL}})\) represents the order of magnitude (i.e., in the “big O notation”) of \(\delta _{\mathrm{BL}}\), and the fluid is assumed to be attached to the wall. Particularly when the fluid is incompressible, \(\frac{\partial p}{\partial \varvec{n}} \approx \varvec{0}\). Therefore, setting the normal gradient of the pressure to zero is an approximation. In reality, \(\frac{\partial p}{\partial \varvec{n}}\) is non-zero (Rempfer 2006), but the “correct” boundary condition would lead to a mathematically ill-posed problem (Rempfer 2006). According to Rempfer (2006), due to the approximation, the “pressure” value used in finite volumes numerical methods [such as the SIMPLE algorithm (Patankar 1980; OpenFOAM Wiki 2014)], would, in reality, correspond to an “articial pressure” value, which should attain a systematic deviation from the “correct” pressure value, and may be corrected due the execution of the SIMPLE algorithm.

The normal gradient of the pressure is set to zero (\(\frac{\partial p}{\partial \varvec{n}} = \varvec{0}\)) on the inlet in OpenFOAM® (Neumann boundary condition), because the velocity profile is already specified (Dirichlet boundary condition) and no previous knowledge outside the computational domain is known.

The outlet boundary condition in OpenFOAM® is given by imposing zero normal gradient for the velocity (\(\frac{\partial \varvec{v}}{\partial \varvec{n}} = \varvec{0}\)) (Neumann boundary condition) and a fixed pressure value (\(p = 0\)) (Dirichlet boundary condition). In FEniCS, the corresponding boundary condition is selected as “stress free”: \((\varvec{T} + \varvec{T}_R) {\tiny \cdot } \varvec{n} = \varvec{0}\), which corresponds to a weak imposition of a fixed zero pressure value (\(p = 0\)).

6.4 Topology optimization loop

The topology optimization loop is schematized in Fig. 9, showing the interconnection between the software packages. The topology optimization starts with an initial guess for the design variable (pseudo-density). Then, the forward model defined in FEniCS is “annotated” (“stored”) in dolfin-adjoint for the automatic derivation of the adjoint model. The optimization loop is started with IPOPT, which interacts with dolfin-adjoint for the computation of the objective function, constraints and sensitivities from the adjoint method. The solver that includes all computations of the forward and adjoint models is referred in Fig. 9, for simplicity, as “Solver”. In order to obtain the sensitivities, it is necessary to compute the forward model, which is given from the following steps: (1) The wall distance is computed in FEniCS; (2) The computed wall distance is transferred to OpenFOAM® by using the “FEniCS TopOpt Foam” library; (3) The fluid flow simulation is executed in OpenFOAM®; (4) The fluid flow variables computed in OpenFOAM® are converted to FEniCS; (5) The converted variables and the computed wall distance are sent to dolfin-adjoint, for assembling the adjoint model. Then, the objective function, constraints and sensitivities are computed in dolfin-adjoint by using FEniCS. In each loop of the IPOPT algorithm, the values of the design variable are updated, defining new topologies. The optimization loop proceeds until a specified tolerance is reached (convergence criterion).

Fig. 9
figure 9

Flowchart representing the topology optimization loop implemented with OpenFOAM® and FEniCS/dolfin-adjoint

The computed sensitivities (of the objective function and constraint) are adjusted by the volume of each element. This is similar to considering the use of a Riesz map in the sensitivity analysis, which leads to mesh independency in the computed sensitivities. This mesh independency is particularly interesting in the case of considering non-uniform meshes, where the non-adjusted sensitivity distribution may achieve a seemingly less-smooth distribution, which may hinder the topology optimization process. For a nodal design variable, the adjusted sensitivity is given by:

$$\begin{aligned} \begin{aligned}&\left. \frac{dJ}{d\alpha }\right| _{\mathrm{adjusted}} = \\&\frac{1}{V_{\begin{subarray}{c} \mathrm {neighbor\ elements} \\ \mathrm {of\ the\ node} \end{subarray}}} \frac{dJ}{d\alpha } \underbrace{ \left[ \frac{\sum \limits _{\mathrm {nodes}}^{} V_{\begin{subarray}{c} \mathrm {neighbor\ elements} \\ \mathrm {of\ the\ node} \end{subarray}}}{n_\text{nodes}} \right] }_\text {Average neighbor elements' volume} \end{aligned} \end{aligned}$$
(26)

where \(V_{\begin{subarray}{c} \mathrm {neighbor\, elements} \\ \mathrm {of\, the\, node} \end{subarray}}\) is the summed volume of the neighbor elements touching a node/vertex in the mesh, and \(n_\text{nodes}\) is the number of nodes/vertices in the mesh. In the 2D case, the volume computations (\(V_{\begin{subarray}{c} \text {neighbor\, elements} \\ \text {of\, the\, node} \end{subarray}}\)) are substituted by their area counterparts (\(A_{\begin{subarray}{c} \mathrm {neighbor\, elements} \\ \mathrm {of\, the\, node} \end{subarray}}\)), while in the 2D axisymmetric case, the volume computations are performed considering axisymmetry (i.e., “ring-shaped” element volumes).

A comparison of the computed sensitivities from dolfin-adjoint with respect to finite differences is presented in “Appendix A”.

7 Numerical examples

In the following numerical examples (with the exception of Sect. 7.1), the fluid is considered as water, with a dynamic viscosity (\(\mu\)) of 0.001 Pa s, and a density (\(\rho\)) of 1000.0 kg/m\(^3\).

An initial numerical example is performed for 2D laminar flow for checking the implementation. Then, three numerical examples (for 2D, 2D axisymmetric and 3D domains) are presented in order to illustrate the application of topology optimization with the coupling between OpenFOAM® and FEniCS/dolfin-adjoint.

The inlet velocity profiles are considered to be parabolic for the laminar flow examples, but are considered to be turbulent velocity profiles for the turbulent flow examples (see Fig. 10). The turbulent velocity profiles are implemented according to De Chant (2005), in which the velocity profile is analytically deduced from a simplified fluid flow model. The difference of this turbulent velocity profile with respect to the \(1/{7}^{\mathrm{th}}\) power law (Munson et al. 2009) is that the derivative is zero in the middle of the velocity profile (see the highly enlarged view of the difference in derivatives in Fig. 10). It can be reminded that this zero derivative in the middle of the turbulent velocity profile is expected for turbulent fluid flows (Munson et al. 2009). For reference, a turbulent velocity profile in the y direction, between a minimum (\(x_\text{min}\)) and a maximum (\(x_\text{max}\)) coordinate becomes (De Chant 2005):

$$\begin{aligned} v_{in,y} = v_{in,y,\mathrm {max}} \sqrt{ \sin \left( \frac{\pi }{2} \sqrt{1 - \left| \frac{x - x_\text{middle}}{x_1}\right| } \right) } \end{aligned}$$
(27)

where \(x_\text{middle} = \frac{x_\text{max} + x_\text{min}}{2}\) is the coordinate of the middle of the velocity profile, \(x_1 = \frac{x_\text{max} - x_\text{min}}{2}\) is an auxiliary coordinate, and \(v_{in,y,\mathrm {max}}\) is the maximum velocity of the turbulent velocity profile (computed from numerical integration for a given flow rate).

Fig. 10
figure 10

Laminar and turbulent velocity profiles for the same “2D flow rate” (area below the curves)

The optimization loop considers the convergence criterion as a tolerance of \({10^{-10}}\) for the optimality error of the IPOPT barrier problem, which consists of the maximum norm of the KKT conditions (Wächter and Biegler 2006).

The external body force term (\(\rho \varvec{f}\)) is not considered in the numerical examples (\(\rho \varvec{f} = \varvec{0}\)). The porous medium is considered to be stationary (\(\varvec{v}_{\text {mat}} = \varvec{v}\)). The minimum value of the inverse permeability is considered as zero (\(\kappa _{\text {min}} = 0 \text { kg/(m}^3\text { s)}\)). The parameter \(\lambda _{{\tilde{\nu }}_{\mathrm{T}}}\) is chosen as 1.0.

The reference value for the wall distance (\(\ell _\text{ref}\)) is used as the maximum size of the elements of the mesh (largest of the maximum distances between two vertices of an element), and the relaxation factor for the wall distance computation (\(\sigma _w\)) is chosen as 0.1. The minimum value of the wall penalization of the porous medium is considered as zero (\(\gamma _{\text {min}} = 0 \text { m}^{-3}\)).

The mesh is post-processed after topology optimization has been performed (i.e., for the optimized topology), from the values of the design variable, from a threshold (step) function:

$$\begin{aligned} \alpha _{\text {th}} = \left\{ \genfrac{}{}{0.0pt}0{1\text { (fluid), if }\alpha \geqslant 0.5}{0\text { (solid), if }\alpha < 0.5}\right. \end{aligned}$$
(28)

where \(\alpha _{\text {th}}\) is the thresholded function. The resulting thresholded design variable (\(\alpha _{\text {th}}\)) is cut in order to remove the solid material (\(\alpha = 0\)) from the computational domain (see Fig. 11). Therefore, the final simulations are performed with the fluid flow equations without the effect of the porous medium. In all of the optimized topologies, the final values of the design variable (pseudo-density) are close to the variable bounds (0 and 1).

The post-processed simulations are computed entirely in OpenFOAM®, which means that a “default” OpenFOAM® wall distance calculation method can be used in this case (such as “meshWave”).

Fig. 11
figure 11

Post-processing applied to an optimized topology

The inlet values for the turbulent variable (\({{\tilde{\nu }}_{\mathrm {T}, in}}\)) are given from the turbulence intensity (\(I_T\)) and the turbulence length scale (\(\ell _T\)) based on the mean absolute velocity on the inlet (\(\overline{|\varvec{v}_{\mathrm {abs}, in}|}\)), as:

$$\begin{aligned} {{\tilde{\nu }}_{\mathrm {T},in}} = \sqrt{\frac{n_{v}}{2}} I_T \ell _T \overline{|\varvec{v}_{\mathrm {abs}, in}|} \end{aligned}$$
(29)

where \(\overline{|\varvec{v}_{\mathrm {abs}, in}|} = \frac{\int _{{\Gamma}_{{\Pi}, \text {in}}} {|\varvec{v}_{\mathrm {abs}, in}|} d{{\Gamma}_{{\Pi}, \text {in}}} }{\int _{{\Gamma}_{{\Pi}, \text {in}}} d{{\Gamma}_{{\Pi}, \text {in}}} }\) is the mean absolute velocity on the inlet, and \(n_{v}\) is the number of velocity components (for 2D, \(n_{v} = 2\); for 2D axisymmetry and 3D, \(n_{v} = 3\)).

The maximum inlet Reynolds number (considering only the inlet velocity) and the maximum local Reynolds number (considering the local velocities) are defined as, respectively,

$$\begin{aligned}&{\text {Re}_{\text{in,max}} = \frac{\mu \left| \varvec{v}_{\text {abs},in}\right| _\text {max} L_\text{ref}}{\rho }} \end{aligned}$$
(30)
$$\begin{aligned}&{\text {Re}_{\text {ext, }\ell \text {, max}} = \frac{\mu \left| \varvec{v}_{\text {abs}}\right| _\text {max} L_\text{ref}}{\rho }} \end{aligned}$$
(31)

where \(L_\text{ref}\) is a characteristic length given, in this work, as the inlet diameter (in the 2D case, it is given as the width of the inlet).

In order to accelerate the execution of the optimization, the OpenFOAM® simulation for each optimization step reuses the simulation result from the immediately previous optimization step. A maximum number of SIMPLE iterations per optimization step is also considered, which is set, in this work, as 500\(\sim\)2000.

7.1 Laminar flow 2D double pipe

This initial example is for checking the implemented framework for the classical laminar flow 2D double pipe (Borrvall and Petersson 2003) (see Fig. 12). Differently from the other numerical examples, the fluid properties, topology optimization setup, boundary conditions, and dimensions are set according to Borrvall and Petersson (2003): \(\mu = 1\) Pa s; \(\rho = 1\) kg/m\(^3\); \(\kappa _{\text {max}} = 2.5 \times 10^4\mu\); \(\kappa _{\text {min}} = 2.5 \times 10^{-4}\mu\); q is set as 0.01 for 20 iterations, and then changed to 0.1; the specified fluid volume fraction (f) is selected as \(\frac{1}{3}\); parabolic velocity profiles are imposed (also including outlet velocity profiles) with the maximum value of the parabolas set as 1 m/s; and h = 1 m. Particularly, in this work, the more generic Navier-Stokes flow implementation is considered, which should not deviate much from the original Stokes flow results, since the Navier-Stokes equations tend to the Stokes equations when the Reynolds number is much smaller than 1 (in this case, the maximum inlet Reynolds number is equal to 0.17). The initial guess for topology optimization is chosen as “fluid fraction” (\(\alpha = f - 1\%\), where \(1\%\) is a margin, in order to avoid the fluid volume constraint to be violated due to numerical precision). The mesh is composed of 30,251 nodes and 60,000 elements (see Fig. 13).

Fig. 12
figure 12

Design domain for the laminar flow 2D double pipe (Borrvall and Petersson 2003)

Fig. 13
figure 13

Mesh used for the laminar flow 2D double pipe (check Fig. 6 for the correspondence of meshes between FEniCS and OpenFOAM®)

The convergence curve for the laminar flow 2D double pipe is shown in Fig. 14.

Fig. 14
figure 14

Convergence curve for the laminar flow 2D double pipe

The optimized topology for the laminar flow 2D double pipe is shown in Fig. 15. As can be seen, the optimized topology is the same as Borrvall and Petersson (2003), which shows that the proposed framework is able to achieve the classical laminar flow 2D double pipe optimized topology.

Fig. 15
figure 15

Optimized topology for the laminar flow 2D double pipe

7.2 2D bend channel

The second example is the design of the classical 2D bend channel. This numerical example has been extensively treated in topology optimization, such as for Stokes flow (Borrvall and Petersson 2003), Navier-Stokes flow (Gersborg-Hansen 2003; Dai et al. 2018), and turbulent flows (Dilgen et al. 2018; Yoon 2016). The 2D bend channel is illustrated in Fig. 16.

Fig. 16
figure 16

Design domain for the 2D bend channel

The mesh is composed of 5101 nodes and 10,000 elements (see Fig. 17). The input parameters and geometric dimensions of the design domain that are used are shown in Table 1. The inlet flow rates correspond to maximum inlet Reynolds numbers of 12.5 (for the laminar flow) and 8460.0 (for the turbulent flow). The initial guesses are chosen as “full fluid” (\(\alpha = 1\)) for the laminar flow case, and “fluid fraction” (\(\alpha = f - 1\%\)) for the turbulent flow case. The specified fluid volume fraction (f) is selected as 30%. For the wall distance computation, \(\gamma _{\text {max}} = 10^{10}\,\text {m}^{-3}\). The inverse permeability (\(\kappa _{\text {max}}\)) and the penalization parameter (q) are selected, respectively, as \(2.5\times 10^8 \mu\) [kg/(m\(^3\)s)] and 0.1, for the laminar flow; and as \(1.5\times 10^9 \mu\) [kg/(m\(^3\)s)] and 0.1, for the turbulent flow.

The optimized topology for laminar flow is consistent with Borrvall and Petersson (2003), because the optimized topology directly connects the inlet to the outlet, in almost a straight line. In the optimized topology for turbulent flow, due to this same fact, and also due to the optimized channel slight bulging toward the origin ((0,  0) coordinates), it bears some resemblance to some of the results from Yoon (2016), but is essentially different mainly because of the different volume fraction (Yoon (2016) considered \(f =\) 20%), different problem dimensions, fluid properties, boundary conditions and Reynolds numbers.

Fig. 17
figure 17

Mesh used for the 2D bend channel (check Fig. 6 for the correspondence of meshes between FEniCS and OpenFOAM®)

Table 1 Parameters used for the topology optimization of the 2D bend channel

The convergence curves for the 2D bend channel are shown in Fig. 18.

Fig. 18
figure 18

Convergence curves for the 2D bend channel

The simulation results for the post-processed meshes are shown in Fig. 19. The maximum local Reynolds numbers are computed as 143 (for the laminar flow) and \(2.7 \times 10^{5}\) (for the turbulent flow). The energy dissipation values in the post-processed meshes are \(6.66 \times 10^{-8}\) W/m (for the laminar flow) and 1.48 W/m (for the turbulent flow). The difference in magnitude of the energy dissipation values is expected, because the fluid velocities are much higher in the turbulent flow, and also because of the presence of the turbulent viscosity in Eq. (20), for turbulent flow. As can be noticed in Fig. 19, the topology optimization results show different formats for both cases: the optimized topology for the laminar flow case shows a direct connection between inlet and outlet, with a small bulging toward the origin ((0, 0) coordinates) of the left side of the channel, due to the change of direction near the inlet, probably in order to redirect the fluid flow toward the outlet; the optimized topology for the turbulent flow case is more bent to the left, which is probably due to the higher viscosity (due to the turbulent viscosity) that is formed to the left of the channel. For reference, the maximum turbulent viscosity ratio, which is a simple measure of the influence of the turbulence in the simulation, is given as \(\text {max}(\frac{\mu _{\mathrm{T}}}{\mu }) = 40\), which shows that the effect of the turbulent viscosity is high in at least a part of the computational domain.

Fig. 19
figure 19

Optimized topologies, pressure, and velocity for the 2D bend channel

7.3 2D axisymmetric nozzle

The third example is a design that relies on 2D axisymmetry, which is considered in the design of a nozzle. A nozzle is a device that is used to control the fluid flow characteristics entering or leaving another fluid device. This type of design is here analyzed for 2D axisymmetric flow, but has already been considered for 2D flow in Borrvall and Petersson (2003) and 2D swirl flow in Alonso et al. (2018).

In this work, as opposed to Alonso et al. (2018), where the size of the fluid flow outlet was left to be determined according to the specified fluid volume fraction (f), the size of the fluid flow outlet is fixed with a radius \(R_{out}\) (see Fig. 20). Also, in order to avoid any issue of the topology optimization blocking the low velocity part of the inlet velocity profile [as can be seen in Borrvall and Petersson (2003)], a small non-optimizable inlet height is included before the design domain.

Fig. 20
figure 20

Design domain for the 2D axisymmetric nozzle

The mesh is composed of 19,401 nodes and 38,400 elements (see Fig. 21). The input parameters and geometric dimensions of the design domain that are used are shown in Table 2. The inlet flow rates correspond to maximum inlet Reynolds numbers of 325 (for the laminar flow) and 3,253 (for the turbulent flow). In order to facilitate the convergence of the topology optimization, a “conical” initial guess (i.e., connecting the inlet (R) of the design domain (\(H - h_{in}\)) directly to the outlet (\(R_{out}\)) with a straight line) is considered for \(\alpha\). The specified fluid volume fraction (f) is selected as 50%. For the wall distance computation, \(\gamma _{\text {max}} = 10^{10} \text {m}^{-3}\). The inverse permeability (\(\kappa _{\text {max}}\)) and the penalization parameter (q) are selected, respectively, as \(2.5\times 10^7 \mu\) [kg/(m\(^3\)s)] and 1.0, for the laminar flow; and as \(5\times 10^8 \mu\) [kg/(m\(^3\)s)] and 1.0, for the turbulent flow.

Fig. 21
figure 21

Mesh used for the 2D axisymmetric nozzle (check Fig. 6 for the correspondence of meshes between FEniCS and OpenFOAM®)

Table 2 Parameters used for the topology optimization of the 2D axisymmetric nozzle

The convergence curves for the 2D bend channel are shown in Fig. 22.

Fig. 22
figure 22

Convergence curves for the 2D axisymmetric nozzle

The simulation results for the post-processed meshes are shown in Fig. 23. The maximum local Reynolds numbers are computed as 505 (for the laminar flow) and 12,023 (for the turbulent flow). The energy dissipation values in the post-processed meshes are \(1.04 \times 10^{-7}\) W (for the laminar flow) and \(3.10 \times 10^{-4}\) W (for the turbulent flow). The difference in magnitude of the energy dissipation values is expected, as in the 2D bend channel example, because of the higher fluid velocities in relation to the turbulent flow, and also because of the presence of the turbulent viscosity in Eq. (20) for turbulent flow. As can be noticed in Fig. 23a, the laminar case topology features a small bump near the low velocity part of the parabolic inlet velocity profile. This small velocity means that this zone of the fluid flow is given a lower importance with respect to the objective function in relation to the rest of the computational domain. A similar effect is also observed in Borrvall and Petersson (2003)’s nozzle example. In the optimized topology for tubulent flow (Fig. 23b), the inlet of the optimized topology becomes smoother than the optimized topology for the laminar flow case. This is probably due to the different inlet velocity profile (turbulent velocity profile), which features higher velocity values at larger radii than the parabolic velocity profile, and the inlet turbulence value, which influences the objective function near the inlet. For reference, the maximum turbulent viscosity ratio is given as \(\text {max}(\frac{\mu _{\mathrm{T}}}{\mu }) = 0.73\), which shows that the effect of the turbulent viscosity is comparable to the fluid (water) viscosity in at least a part of the computational domain.

Fig. 23
figure 23

Optimized topologies, 3D representation, pressure, and velocity for the 2D axisymmetric nozzle

7.4 3D channel

The fourth example is based on a 3D model, for the design of a channel that bifurcates into other two. Fig. 24 shows the computational domain with the inlet channel and the two outlet channels. The inlet and outlet channels are left outside the design domain.

Fig. 24
figure 24

Design domain for the 3D channel

The mesh is composed of 18,308 nodes and 102,254 tetrahedral elements (see Fig. 25), whose quantities are slightly increased for the turbulent case (18,344 nodes and 102,720 tetrahedral elements). The input parameters and geometric dimensions of the design domain that are used are shown in Table 3. The inlet flow rates correspond to maximum inlet Reynolds numbers of 1,062 (for the laminar flow) and 2,603 (for the turbulent flow). The initial guess for the laminar case is chosen as “fluid fraction” (\(\alpha = f - 1\%\)), while the initial guess for the turbulent case is chosen as the optimized topology of the laminar case. The specified fluid volume fraction (f) is selected as 20%. For the wall distance computation, \(\gamma _{\text {max}} = 10^{8} \text {m}^{-3}\). The inverse permeability (\(\kappa _{\text {max}}\)) and the penalization parameter (q) are selected, respectively, as \(5.0\times 10^7 \mu\) [kg/(m\(^3\)s)] and 1, for the laminar flow; and as \(8.0\times 10^7 \mu\) [kg/(m\(^3\)s)] and 1000, for the turbulent flow.

Fig. 25
figure 25

Mesh used for the 3D channel (laminar case)

Table 3 Parameters used for the topology optimization of the 3D channel

The convergence curves for the 3D channel are shown in Fig. 26. It can be highlighted that there is a maximum number of SIMPLE iterations per optimization step (which is set, in this work, as 500), which means that the “quality” of the simulation is lower in the first iterations of the topology optimization.

Fig. 26
figure 26

Convergence curves for the 3D channel (Obs. For ease of visualization of the optimized topology, only the values of \(\alpha\) with \(\alpha \geqslant 0.5\) are shown in nontransparent color). It can be highlighted that the optimized topologies (in the final iterations) are highly discrete

The simulation results for the post-processed meshes are shown in Fig. 27, where only a slice of the scalar fields (p, \({\tilde{\nu }}_{\mathrm{T}}\), \(\mu _{\mathrm{T}}\)) is plotted, for illustrative purposes. The maximum local Reynolds numbers are computed as 1,254 (for the laminar flow) and 5,645 (for the turbulent flow). The energy dissipation values in the post-processed meshes are \(1.08 \times 10^{-5}\) W (for the laminar flow) and \(3.65 \times 10^{-4}\) W (for the turbulent flow). The difference in magnitude of the energy dissipation values is expected as mentioned in the other numerical examples. It can be noticed, when comparing Fig. 27a and b, that the channels are thicker in the laminar flow case, which is probably due to the effect of the lower velocities and the higher effect of the viscosity of the fluid. In the turbulent flow case, the channels are thinner, which is probably due to the higher velocities and turbulent viscosity effect in the turbulent flow case. Also, the channels are split near the outlet in the laminar flow case, while they are split near the inlet in the turbulent flow case. This may be due to the fact that, if the channel is split near the outlet in the turbulent flow case, the fluid will be at a higher velocity, meaning that the energy dissipated in the “collision” with the “splitting edge” would become higher. One more observation is that the fluid volume is different in both optimized topologies, which is acceptable, since the constraint that is being imposed is a maximum fluid volume constraint [Eq. (9)]. For reference, the maximum turbulent viscosity ratio is given as \(\text {max}(\frac{\mu _{\mathrm{T}}}{\mu }) = 6.24\), which shows that the effect of the turbulent viscosity is higher than the fluid (water) viscosity in at least a part of the computational domain.

Fig. 27
figure 27

Optimized topologies, pressure, and velocity for the 3D channel

8 Conclusions

This work presents the approach of using the OpenFOAM® infrastructure for the computation of an efficient fluid flow simulation, while the adjoint model is automatically derived in an efficient manner by FEniCS/dolfin-adjoint. Although an even higher computational efficiency would be possible to be achieved through manually deriving the continuous adjoint model and adjusting its implementation (such as through reordering the terms/operations, block matrices, local preconditionings etc.), this procedure may become a hard and cumbersome task, especially for complex models. Therefore, this work presents a more convenient and comprehensive approach of obtaining the automatically derived adjoint model in an efficient manner when considering OpenFOAM®. In the point of view of OpenFOAM®, this means that the adjoint equations do not need to be derived by hand, while, in the point of view of FEniCS, the fluid flow simulation may be computed more efficiently, without needing to implement various adjustments for convergence of the algorithm. In terms of work required in the implementation, the additional work is to write the material model terms in the equations inside the OpenFOAM® solver and write the weak forms and boundary conditions in FEniCS. The required additional work for this implementation is far from having to derive the adjoint equations by hand, and even saves time when testing, since the derivation of the adjoint model is automated. In terms of computational cost, the implemented algorithm is able to deploy OpenFOAM® and FEniCS to run in parallel (independently), which may help in reducing the required computational time. Since the adjoint equations are linear, the resulting matrix system needs to be solved a single time at each iteration, and the computational cost is mostly due to the interpolation degrees of the state variables in finite elements (see Fig. 3), which the authors tried reducing by considering the use of MINI elements instead of Taylor-Hood elements. It is also possible to use linear finite elements by including a stabilization term in the fluid flow equations (Reddy and Gartling 2010; Logg et al. 2012; Elhanafy et al. 2017; Langtangen et al. 2002; Franca 1992).

It is also possible to extend the implemented approach to any type of optimization method implemented in the FEniCS platform, by including the adequate conversions to OpenFOAM®simulations by using the “FEniCS TopOpt Foam” library. Although this work is focused in topology optimization for fluid flow, this approach is extensible to any kind of physics that is modellable in OpenFOAM®.

As future work, it is suggested to consider this scheme for investigating topology optimization for turbulent, compressible, and non-Newtonian flows.

9 Replication of results

The part of the implementation that is performed in the FEniCS platform is direct from the description that is provided of the equations and numerical implementation in this article. This is because FEniCS is based on a high-level description for the variational formulation (UFL), which automates the generation of the necessary matrix equations. It may be reminded that, in the 2D axisymmetric case, the coordinates are considered to be cylindrical (i.e., the differential operators (“grad”, “curl”, “div”) must be programmed by hand by using the “Dx(var,component_num)” or “var.dx(component_num)” functions, because the default operators available in FEniCS consider Cartesian coordinates).

The part of the implementation that is performed in OpenFOAM® is, as mentioned in Sect. 6, including the additional inverse permeability term in the “simpleFoam” solver from OpenFOAM® (referred as “CustomSimpleFoam” in this work) [see Eq. (2)], and also in the “SpalartAllmaras” turbulence model (referred as “CustomSpalartAllmaras” in this work) [see Eq. (5)]. Another necessary implementation is to create an additional type of wall distance computation, which loads the wall distance from a file (referred as “Custom_externalImport” in this work).

The “FEniCS TopOpt Foam” library used in the implementation of this work is to be made available in a git repositoryFootnote 2. It also includes sample implementations of “CustomSimpleFoam”, “CustomSpalartAllmaras”, and “Custom_externalImport”. An implementation of a code by using “FEniCS TopOpt Foam” for a sample 2D bend channel topology optimization (slightly different from Sect. 7.2 in order to be simpler and easier to understand) is shown step by step in the following subsections. In the following code excerpts, when a line of code is split due to lack of space, its continuation is shown in the next line, preceded by an arrow (“”).

9.1 Sample 2D bend channel problem

In this section, the 2D bend channel problem is considered through a sample implementation, where \(\rho\) is set as 1.0, \(\mu\) is set as 0.1, and the inlet velocity is defined as such that the maximum velocity of the inlet parabola is 1.0, while the computational domain is a \(1{\times }1\) square. The implementation is performed by leaving a variable to set which flow regime (laminar or turbulent) is being considered (“flow_regime” variable) and another variable is left to set whether to consider OpenFOAM® in parallel or not (“run_openfoam_in_parallel”). The optimization parameters are prepared for the laminar and turbulent cases, but their specific values are set for a laminar flow topology optimization, and may be adjusted by the user for a turbulent flow case. Table 4 presents the main variable naming differences between this article and the implementations in FEniCS and OpenFOAM®.

Table 4 Variable naming in the equations of this article and the implementations in FEniCS and OpenFOAM®.

9.2 Necessary imports

The necessary imports should be included in the beginning of the code.

figure b

9.3 General configurations

The general configurations can be set as follows: First, an additional variable (“run_openfoam_in_parallel”) is set in order to control whether OpenFOAM® should run in parallel or not.

figure c

Then, the fluid properties are set alongside the corresponding inlet values and the flow regime.

figure d

9.4 Set topology optimization-related parameters

The topology optimization-related parameters are defined.

figure e

9.5 Create the output folder

A folder for including the results is created.

figure f

9.6 Create the 2D mesh in FEniCS

The mesh is created in FEniCS and saved to file for visualization. It can be mentioned that any mesh or mesh generation scheme in FEniCS may be considered, such as from FEniCS itself, from an external mesh imported to FEniCS, and from “mshr” (additional meshing module from FEniCS).

figure g

9.7 Define the function spaces for FEniCS

The FEniCS implementation requires the definition of the function spaces for the state and design variables.

figure h

9.8 Prepare the boundary definition in FEniCS

The boundaries of the computational domain are given names in FEniCS, which will also be used in OpenFOAM®, and saved to file, for visualization.

figure i

9.9 Prepare boundary values (for Dirichlet Boundary conditions) in FEniCS

Some of the boundary values that will be used for Dirichlet Boundary conditions are defined.

figure j

9.10 Function to set “FEniCS TopOpt Foam”

The function “prepareFEniCSFoamSolverWithUpdate” is created in order to prepare the whole setup for the OpenFOAM® simulation from “FEniCS TopOpt Foam”. First, the boundary data are gathered in a format that is more closely related to OpenFOAM® definitions.

figure k

Then, the basic parameters necessary for defining a solver in “FEniCS TopOpt Foam” are defined.

figure l

Following, it is necessary to prepare the OpenFOAM® dictionary entries for “controlDict”, “fvSchemes”, and “fvSolution”. These three dictionaries are required by OpenFOAM® for any simulation and are essential for controlling how these simulations will be executed, which means that they should be completely defined by the user. It should be reminded, though, that “writeFormat” (from “controlDict”) needs to be set to “ascii” for “FEniCS TopOpt Foam”.

figure m

The “libs” entry from “controlDict” is set to consider some C++ OpenFOAM® libraries provided by “FEniCS TopOpt Foam” (i.e., the OpenFOAM® libraries that are already mentioned in the beginning of Sect. 9), but the user may include any user-made library in this entry.

figure n

9.11 Solver that interacts with FEniCS and OpenFOAM®

Now, the solver can be created (called

FEniCSFoamSolverWithUpdate”) with the previously defined parameters, variables, mesh, boundary conditions, and the fluid properties.

figure o

The parallelism in OpenFOAM® is set here (if “run_openfoam_in_parallel = True”), where the “parallel_data” dictionary needs to be set according to OpenFOAM® conventions, and the value set for the “numberOfSubdomains” entry also corresponds to the number of processes for OpenFOAM® parallelism.

figure p

The boundary conditions are set as follows:

figure q

The fluid flow properties are set as follows:

figure r

The “plotResults” function from

FEniCSFoamSolver” can be left more readily accessible.

figure s

The main function for solving the simulation can then be defined as follows. First, the variables are retrieved from dolfin-adjoint (“replace_map”), and an initial guess for the state vector (called “u”) is set.

figure t

Then, the wall distance is computed in FEniCS and set to OpenFOAM®.

figure u

Following, the properties are optionally updated (if a continuation scheme in the property values is desired during topology optimization).

figure v

The variables are set to OpenFOAM®.

figure w

The OpenFOAM® simulation can now be performed. In this case, in order to help monitoring the residuals from the simulation, the parameter “continuously_plot_residuals_from_log” is set to “True”. This means that, inside the OpenFOAM® simulation folder (called “foam_problem” in Sect. 9.3), there will be a “logs” folder which will contain the plots made with Matplotlib (image files, “.png”) for each residual. These plots are renewed at each optimization iteration. In order for Matplotlib to be able to plot, it is essential that “matplotlib.use(‘Agg’)” is used in the beginning of the code, as shown in Sect. 9.3, because Matplotlib is set to create the plots simultaneously to the simulation in OpenFOAM® by spawning a child process, because it requires Matplotlib to be using a non-interactive backend (such as “Agg”), which is able to directly generate image files, but disables the capacity of Matplotlib opening GUI windows.

figure x

After the simulation, the computed variables are set back to FEniCS/dolfin-adjoint.

figure y

With “FEniCSFoamSolverWithUpdate” defined, it is now created.

figure z

9.12 Forward model in FEniCS

A function that prepares the forward model in FEniCS from a design variable distribution (“alpha”) has to be defined, because it will be used by dolfin-adjoint for the automatic derivation of the adjoint model. First, the state vector and test functions are defined, alongside some auxiliary definitions.

figure aa

In the case of using a turbulence model (Spalart-Allmaras model), the computation of the wall distance is performed.

figure ab

Then, the remaining weak forms and boundary conditions for FEniCS are defined and combined.

figure ac

Then, the “prepareFEniCSFoamSolverWithUpdate” is created and used as an input parameter for “UncoupledNonlinearVariationalSolver”, which will perform the coupling between the optimization and the simulation.

figure ad

9.13 Preparations for topology optimization

The initial setup for topology optimization is performed.

figure ae

An initial simulation is performed for dolfin-adjoint to prepare the automatic derivation of the adjoint model.

figure af

Some visualization files are prepared for visualizing the optimized topology during the topology optimization iterations.

figure ag

In order to continuously save the visualization files, it is necessary to create a callback for dolfin-adjoint, such as immediately after the computation of the sensitivities (“derivative_cb_post”).

figure ah

9.14 Topology optimization

The topology optimization problem can now be defined, as well as the IPOPT solver can be instantiated from dolfin-adjoint.

figure ai

To finalize, the topology optimization is performed.

figure aj

9.15 Plot the simulation

The simulation from OpenFOAM® may be plotted as follows.

figure ak

9.16 Running the code

The resulting code may be run as: (1) totally in serial mode, (2) with only OpenFOAM® in parallel, (3) with only FEniCS in parallel, or (4) with FEniCS and OpenFOAM® in parallel. Parallelism in OpenFOAM® is enabled by setting “run_openfoam_in_parallel = True” (Sect. 9.3) and adequately setting (depending on your computational resources) the “parallel_data” dictionary (Sect. 9.11). Parallelism in FEniCS is set directly in the Python call, such as “mpiexec -n 2 python my_code.py” (for 2 processes). The number of processes for each type of parallelism is set as desired by the user and in a value allowed by the user’s computational resources (such as 2,3,4 etc.). For no parallelism in OpenFOAM® and FEniCS (“serial mode”), set “run_openfoam_in_parallel = False” and run the code as “python my_code.py”. The plots, which contain the extensions .pvd and .vtk, may be visualized with the ParaView software.