The Solution computed is not periodic despite the implementation of periodic boundary conditions

Hello,
Let’s summarize my issue, I’ve been trying to solve the Toner-Tu equation, basically the Navier-Stokes equation with a nonlinear term, and it worked pretty well. Previously I was solving with Dirichlet boundary conditions.
Now I want to implement Periodic Boundary Conditions. I’ve faced two major issues. First, the solution returned ain’t periodic. Then, after multiple time steps, I a peak in density appears on the border and few steps later the simulations diverge. I join an Image of what’s happening then…


I work with mixed space and it wasn’t a problem before. I join some parts of my code in case you have any ideas of where it could come from…

The implementation of the periodic boundary conditions:

class PeriodicBoundary(SubDomain):

    def inside(self, x, on_boundary):
        return bool((near(x[0], SIZE/2) or near(x[1], SIZE/2)) 
                    and not(near(x[0], -SIZE/2) or near(x[1], SIZE/2)) and on_boundary)

    def map(self, x, y):
        if near(x[0], SIZE/2) and near(x[0], SIZE/2):
            y[0] = x[0]-SIZE
            y[1] = x[1]-SIZE
        elif near(x[0], SIZE/2):
            y[0] = x[0]-SIZE
            y[1] = x[1]
        elif near(x[1], SIZE/2) and not(near(x[0], -SIZE/2) or near(x[0], SIZE/2)):
            y[0] = x[0]
            y[1] = x[1]-SIZE
        else:
            y[0] = x[0]-100*SIZE
            y[1] = x[1]-100*SIZE

Then I implement the domain:

    V1 = VectorElement("Lagrange", mesh.ufl_cell(), DEG)
    V2 = FiniteElement("Lagrange", mesh.ufl_cell(), DEG)
    V = VectorFunctionSpace(mesh, 'P', DEG)
    Vs = FunctionSpace(mesh, 'P', DEG)

    # Make a mixed space
    TH = V1*V2
    if CL_P:
        # Create periodic boundary condition
        pbc = PeriodicBoundary()
        W = FunctionSpace(mesh, TH, constrained_domain=pbc)
        V = VectorFunctionSpace(mesh, 'P', DEG, constrained_domain=pbc)
        Vs = FunctionSpace(mesh, 'P', DEG, constrained_domain=pbc)

And even if it’s ugly the form that worked pretty well before:

    lamb = Constant(LAMBDA)  # SD
    alph = Constant(1e2)  # s-1
    pho_c = Constant(3e-3)  # SD
    pho = Constant(0.1)  # SD
    a2 = Constant(alph*(pho-pho_c))  # s-1
    a4 = Constant(10)  # mm-2 s
    D = Constant(1e-2)  # mm2 s-1
    D2 = Constant(4e-6*FAC_D_RHO)  # mm2 s-1
    sigma = Constant(5)  # mm2 s-2
    Dt = Constant(DT)

    # Declare the form
    F = dot((u-u_1)/Dt, v)*dx \
        +lamb*dot(dot(u, nabla_grad(u)), v)*dx \
        +D*(dot(grad(u[0]), grad(v[0]))+dot(grad(u[1]), grad(v[1])))*dx \
        -D*(dot(grad(u[0]),n)*v[0]+dot(grad(u[1]),n)*v[1])*ds\
        -(alph*(p-pho_c)-a4*dot(u, u))*dot(u, v)*dx \
        +sigma*dot(grad(p), v)*dx \
        +(((p-p_1)/Dt+div(p*u))*q+D2*dot(grad(p), grad(q)))*dx \
        -D2*dot(grad(p),n)*q*ds

    np.savetxt("./Solutions/coordo.txt", np.reshape(np.array(x.vector()), (-1, 2)))

    for i in range(0, NUM_STEP):
        plotfigure = False
        try:
            if CL_P:
                solve(F == 0, w)
            else:
                solve(F == 0, w, bcu)
        except RuntimeError:
            break

        assign(w_1, w)

Any help would be appreciated.
Thanks!

Without a minimal working example which we can copy and paste without editing, you’re unlikely to get a response. Can you reproduce your issue with a simple problem?

Something that stands out separate from your question is that it looks like you’re trying to use a Taylor-Hood type element with equal order pressure and velocity approximation. Isn’t this unstable?

Hmm Yeah it would be easier I guess sorry Here is a full example:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from fenics import *
from dolfin import *
from mshr import *
import numpy as np
import matplotlib.pyplot as plt


def fig_defaut(u, p, ifig=-1, plotfigure=False, nom='no_name'):
    if plotfigure:
        plt.figure(figsize=(13, 13))

        pl = plot(project(u/dot(u, u)**0.5))
        pl.set_clim(0.0, 10.0)
        plot(p, alpha=0.3)

        axes = plt.gca()
        axes.set_xlabel("x (mm)", fontsize=14)
        axes.set_ylabel("y (mm)", fontsize=14)
        plt.savefig(f"./Image/{nom}_v_{ifig:05}.png", bbox_inches='tight', format='png')
        plt.close()


def get_value(x, y, XX, YY, L):
    for i in range(len(L)):
        if abs(XX[i]-x) < 1e-3 and abs(YY[i]-y) < 1e-3:
            return L[i]
    print(x, y)
    raise ValueError


# Sub domain for Periodic boundary condition
class PeriodicBoundary(SubDomain):

    # Left boundary is "target domain" G
    def inside(self, x, on_boundary):
        return bool((near(x[0], SIZE/2) or near(x[1], SIZE/2)) 
                    and not(near(x[0], -SIZE/2) or near(x[1], SIZE/2)) and on_boundary)

    # Map right boundary (H) to left boundary (G)
    def map(self, x, y):
        if near(x[0], SIZE/2) and near(x[0], SIZE/2):
            y[0] = x[0]-SIZE
            y[1] = x[1]-SIZE
        elif near(x[0], SIZE/2):
            y[0] = x[0]-SIZE
            y[1] = x[1]
        elif near(x[1], SIZE/2) and not(near(x[0], -SIZE/2) or near(x[0], SIZE/2)):
            y[0] = x[0]
            y[1] = x[1]-SIZE
        else:
            y[0] = x[0]-100*SIZE
            y[1] = x[1]-100*SIZE


if __name__ == '__main__':
    # définition de l'expérience
    CL_P = True  # False pour CL tangeante
    FROM_DATA = False
    FROM_SOLU = False

    # définition de la physique
    SIZE = 3
    DX = 0.05
    DT = 1e-4  # pas de temps
    LAMBDA = 0.7
    RHO_0 = 0.1
    FAC_D_RHO = 1

    # définition de la simulation
    N = int(SIZE/DX)  # Nombre d'élément par ligne/colonne
    DEG = 1  # degré des éléments
    NUM_STEP = 50000

    # définition de la sauvegarde
    NB_IM = 400
    NB_SOL = 10
    NOM = 'TT_'

    # Define the mesh
    tx = SIZE
    ty = tx
    nbx = N
    nby = nbx

    mesh = RectangleMesh(Point(-tx/2, -ty/2), Point(tx/2, ty/2), nbx, nby)

    V1 = VectorElement("Lagrange", mesh.ufl_cell(), DEG)
    V2 = FiniteElement("Lagrange", mesh.ufl_cell(), DEG)
    V = VectorFunctionSpace(mesh, 'P', DEG)
    Vs = FunctionSpace(mesh, 'P', DEG)

    # Make a mixed space
    TH = V1*V2
    if CL_P:
        # Create periodic boundary condition
        pbc = PeriodicBoundary()
        W = FunctionSpace(mesh, TH, constrained_domain=pbc)
        V = VectorFunctionSpace(mesh, 'P', DEG, constrained_domain=pbc)
        Vs = FunctionSpace(mesh, 'P', DEG, constrained_domain=pbc)
    else:
        W = FunctionSpace(mesh, TH)

    X = Expression(('x[0]', 'x[1]'), element=V.ufl_element())
    x = interpolate(X, V)

    # Initial conditions
    if FROM_DATA:
        nb_pr = 127
        mesh_pr = RectangleMesh(Point(-tx/2, -ty/2), Point(tx/2, ty/2), nb_pr, nb_pr)
        V_pr = VectorFunctionSpace(mesh_pr, 'P', 1)
        Vs_pr = FunctionSpace(mesh_pr, 'P', 1)
        X_pr = Expression(('x[0]', 'x[1]'), element=V_pr.ufl_element())
        x_pr = interpolate(X_pr, V_pr)
        coordo_xy = np.array(x_pr.vector()).reshape((-1, 2))

        a = np.loadtxt("./data.csv", delimiter=',')
        rho = np.zeros(128**2)
        vx = np.zeros(128**2)
        vy = np.zeros(128**2)
        for i in range(128**2):
            rho[i] = get_value(10/tx*(tx/2+coordo_xy[i, 0]), 10/tx*(tx/2+coordo_xy[i, 1]), a[:, 0], a[:, 1], a[:, 4])
            vx[i] = get_value(10/tx*(tx/2+coordo_xy[i, 0]), 10/tx*(tx/2+coordo_xy[i, 1]), a[:, 0], a[:, 1], a[:, 5])
            vy[i] = get_value(10/tx*(tx/2+coordo_xy[i, 0]), 10/tx*(tx/2+coordo_xy[i, 1]), a[:, 0], a[:, 1], a[:, 6])

        v = np.vstack((vx, vy)).T.flatten()
        f_v = Function(V_pr)
        f_v.vector()[:] = v
        f_rho = Function(Vs_pr)
        f_rho.vector()[:] = rho
        u_0 = project(f_v, V)
        p_0 = project(f_rho, Vs)


    elif FROM_SOLU:
        p = Function(Vs)
        u = Function(V)
        p.vector()[:] = np.loadtxt('./soluce_ini_p.txt')
        u.vector()[:] = np.loadtxt('./soluce_ini_v.txt').flatten()

        p_0 = interpolate(p, W.sub(1).collapse())
        u_0 = interpolate(u, W.sub(0).collapse())
    else:
        r = 2
        u_in = Expression(('r*(rand()-0.5)', 'r*(rand()-0.5)'), degree=DEG, r=r)
        u_0 = interpolate(u_in, W.sub(0).collapse())

        rho_0 = Constant(RHO_0)
        p_in = Expression('rho_0', degree=0, rho_0=rho_0)
        p_0 = interpolate(p_in, W.sub(1).collapse())
    # Declare functions
    w = Function(W)
    w_1 = Function(W)

    assign(w, [u_0, p_0])
    assign(w_1, [u_0, p_0])

    w_t = TestFunction(W)

    (u, p) = split(w)
    (u_1, p_1) = split(w_1)
    (v, q) = split(w_t)

    # Declare constants
    n = FacetNormal(mesh)

    if not (CL_P):
        mur_g = f"near(x[0],-{SIZE/2})"
        mur_d = f"near(x[0],{SIZE/2})"
        mur_b = f"near(x[1],-{SIZE/2})"
        mur_h = f"near(x[1],{SIZE/2})"

        bcu_b = DirichletBC(W.sub(0), Constant((1, 0)), mur_b)
        bcu_d = DirichletBC(W.sub(0), Constant((0, 1)), mur_d)
        bcu_h = DirichletBC(W.sub(0), Constant((-1, 0)), mur_h)
        bcu_g = DirichletBC(W.sub(0), Constant((0, -1)), mur_g)
        bcu = [bcu_d, bcu_b, bcu_g, bcu_h]

        bcu_d = DirichletBC(W.sub(1), RHO_0, mur_d)
        bcu_g = DirichletBC(W.sub(1), RHO_0, mur_g)
        bcu_h = DirichletBC(W.sub(1), RHO_0, mur_h)
        bcu_b = DirichletBC(W.sub(1), RHO_0, mur_b)
        bcu += [bcu_d, bcu_b, bcu_g, bcu_h]

    lamb = Constant(LAMBDA)  # SD
    alph = Constant(1e2)  # s-1
    pho_c = Constant(3e-3)  # SD
    pho = Constant(0.1)  # SD
    a2 = Constant(alph*(pho-pho_c))  # s-1
    a4 = Constant(10)  # mm-2 s
    D = Constant(1e-2)  # mm2 s-1
    D2 = Constant(4e-6*FAC_D_RHO)  # mm2 s-1
    sigma = Constant(5)  # mm2 s-2
    Dt = Constant(DT)

    # Declare the form
    F = dot((u-u_1)/Dt, v)*dx \
        +lamb*dot(dot(u, nabla_grad(u)), v)*dx \
        +D*(dot(grad(u[0]), grad(v[0]))+dot(grad(u[1]), grad(v[1])))*dx \
        -D*(dot(grad(u[0]),n)*v[0]+dot(grad(u[1]),n)*v[1])*ds\
        -(alph*(p-pho_c)-a4*dot(u, u))*dot(u, v)*dx \
        +sigma*dot(grad(p), v)*dx \
        +(((p-p_1)/Dt+div(p*u))*q+D2*dot(grad(p), grad(q)))*dx \
        -D2*dot(grad(p),n)*q*ds

    np.savetxt("./Solutions/coordo.txt", np.reshape(np.array(x.vector()), (-1, 2)))

    for i in range(0, NUM_STEP):
        plotfigure = False
        try:
            if CL_P:
                solve(F == 0, w)
            else:
                solve(F == 0, w, bcu)
        except RuntimeError:
            break

        assign(w_1, w)

        try:
            p_ana = project(w[2], Vs)
            u_ana = project(as_vector([w[0], w[1]]), V)

            if i%NB_IM == 0:
                plotfigure = True
                fig_defaut(u_ana, p_ana, ifig=i, plotfigure=plotfigure, nom=NOM)
        except:
            print("not plotted")

        if i%NB_SOL == 0:
            np.savetxt(f"./Solutions/soluce_v_{i+1:05}.txt", np.reshape(np.array(u_ana.vector()), (-1, 2)))
            np.savetxt(f"./Solutions/soluce_p_{i+1:05}.txt", p_ana.vector()[:])

I am not sure to have understood … Are you saying that I have to take like degree 1 elements for the density? Will it be consistent?
I will try but it has always been stable with Dirichlet BCs…

Hello,
First, let me apologize for the bad quality of my former post, it was not very clear…
So here I come with a simpler issue.
I simplified my equation till obtaining only diffusion on the density which is now:
\partial_t {\bf v}=0
\partial_t \rho=D\Delta\rho
I chose a gaussian as my initial condition and all I want is periodic boundary conditions.
I obtain the periodicity on the edges but not on the corners. This creates some unwanted behaviours…
I try to change the belonging of the corners between the target domain and the source domain…
I also tried to give conditions of the corners on y instead of x
Until now, nothing that I tried worked so I am asking for help, and maybe it’s just a little mistake from me.

Here is a sample of my code pastable, which plot the solution:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from fenics import *
from dolfin import *
from mshr import *
import numpy as np
import matplotlib.pyplot as plt


def fig_defaut(u, p, ifig=-1, plotfigure=False, nom='no_name'):
    if plotfigure:
        plt.figure(figsize=(13, 13))

        pl = plot(project(u/dot(u, u)**0.5))
        pl.set_clim(0.0, 10.0)
        plot(p, alpha=0.3)

        axes = plt.gca()
        axes.set_xlabel("x (mm)", fontsize=14)
        axes.set_ylabel("y (mm)", fontsize=14)
        plt.savefig(f"./Image/{nom}_v_{ifig:05}.png", bbox_inches='tight', format='png')
        plt.close()


# Sub domain for Periodic boundary condition
class PeriodicBoundary(SubDomain):

    # Left and bottom without edges is the target domain
    def inside(self, x, on_boundary):
        return bool((near(x[0], -SIZE/2) or near(x[1], -SIZE/2)) and 
                (not ((near(x[0], -SIZE/2) and near(x[1], SIZE/2)) or 
                        (near(x[0], SIZE/2) and near(x[1], -SIZE/2)))) and on_boundary)

    # # Map top and right to left and bottom
    def map(self, x, y):
        if near(x[0], SIZE/2) and near(x[1], SIZE/2):
            y[0] = x[0] - SIZE
            y[1] = x[1] - SIZE
        elif near(x[0], SIZE/2):
            y[0] = x[0] - SIZE
            y[1] = x[1]
        elif near(x[1], SIZE/2):
            y[0] = x[0]
            y[1] = x[1] - SIZE
        else:
            raise NotImplementedError
    
            
if __name__ == '__main__':

    # définition de la physique
    SIZE = 5
    DX = 0.09
    DT = 1e-2  # pas de temps
    LAMBDA = 0.7
    RHO_0 = 0.1
    FAC_D_RHO = 1

    # définition de la simulation
    N = int(SIZE/DX)  # Nombre d'élément par ligne/colonne
    DEG = 1  # degré des éléments
    NUM_STEP = 100

    # définition de la sauvegarde
    NB_IM = 10
    NOM = 'TT_'

    # Define the mesh
    tx = SIZE
    ty = tx
    nbx = N
    nby = nbx

    mesh = RectangleMesh(Point(-tx/2, -ty/2), Point(tx/2, ty/2), nbx, nby)

    V1 = VectorElement("Lagrange", mesh.ufl_cell(), DEG)
    V2 = FiniteElement("Lagrange", mesh.ufl_cell(), DEG)
    V = VectorFunctionSpace(mesh, 'P', DEG)
    Vs = FunctionSpace(mesh, 'P', DEG)

    # Make a mixed space
    TH = V1*V2
    # Create periodic boundary condition
    pbc = PeriodicBoundary()
    W = FunctionSpace(mesh, TH, constrained_domain=pbc)


    u_in = Expression(('1', '1'), degree=0)
    u_0 = interpolate(u_in, W.sub(0).collapse())

    rho_0 = Constant(RHO_0)
    p_in = Expression('rho_0*exp(-4*(x[0]-1.5)*(x[0]-1.5)-4*(x[1]-1.5)*(x[1]-1.5))', degree=DEG, rho_0=rho_0)
    p_0 = interpolate(p_in, W.sub(1).collapse())
        
        
    # Declare functions
    w = Function(W)
    w_1 = Function(W)

    assign(w, [u_0, p_0])
    assign(w_1, [u_0, p_0])

    w_t = TestFunction(W)

    (u, p) = split(w)
    (u_1, p_1) = split(w_1)
    (v, q) = split(w_t)

    # Declare constants
    n = FacetNormal(mesh)
    D2 = Constant(1)
    Dt=Constant(DT)

    # Declare the form
    F = dot((u-u_1), v)*dx \
        +((p-p_1)/Dt*q+D2*dot(grad(p), grad(q)))*dx \
        -D2*dot(grad(p),n)*q*ds

    for i in range(0, NUM_STEP):
        plotfigure = False
        try:
            solve(F == 0, w)
        except RuntimeError:
            break
        assign(w_1, w)
        try:
            p_ana = project(w[2], Vs)
            u_ana = project(as_vector([w[0], w[1]]), V)

            if i%NB_IM == 0:
                plotfigure = True
                fig_defaut(u_ana, p_ana, ifig=i, plotfigure=plotfigure, nom=NOM)
        except:
            print("not plotted")

Any help would be appreciated.
Thanks in advance!

Ok, so it was kind of a monologue… But I may found the solution but I have no idea why this works…:sweat_smile:
Here is the code that works:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from fenics import *
from dolfin import *
from mshr import *
import numpy as np
import matplotlib.pyplot as plt


def fig_defaut(u, p, ifig=-1, plotfigure=False, nom='no_name'):
    if plotfigure:
        plt.figure(figsize=(13, 13))

        pl = plot(project(u/dot(u, u)**0.5))
        pl.set_clim(0.0, 10.0)
        plot(p, alpha=0.3)

        axes = plt.gca()
        axes.set_xlabel("x (mm)", fontsize=14)
        axes.set_ylabel("y (mm)", fontsize=14)
        plt.savefig(f"./Image/{nom}_v_{ifig:05}.png", bbox_inches='tight', format='png')
        plt.close()


# Sub domain for Periodic boundary condition
class PeriodicBoundary(SubDomain):

    # Left and bottom without edges is the target domain
    def inside(self, x, on_boundary):
        return bool((near(x[0], -SIZE/2) or near(x[1], -SIZE/2)) and 
                (not ((near(x[0], -SIZE/2) and near(x[1], SIZE/2)) or 
                        (near(x[0], SIZE/2) and near(x[1], -SIZE/2)))) and on_boundary)

    # # Map top and right to left and bottom
    def map(self, x, y):
        if near(x[0], SIZE/2) and near(x[1], SIZE/2):
            y[0] = x[0] - SIZE
            y[1] = x[1] - SIZE
        elif near(x[0], SIZE/2):
            y[0] = x[0] - SIZE
            y[1] = x[1]
        elif near(x[1], SIZE/2):
            y[0] = x[0]
            y[1] = x[1] - SIZE
        else:
            raise NotImplementedError
    
            
if __name__ == '__main__':

    # définition de la physique
    SIZE = 5
    DX = 0.09
    DT = 1e-2  # pas de temps
    LAMBDA = 0.7
    RHO_0 = 0.1
    FAC_D_RHO = 1

    # définition de la simulation
    N = int(SIZE/DX)  # Nombre d'élément par ligne/colonne
    DEG = 2  # degré des éléments
    NUM_STEP = 100

    # définition de la sauvegarde
    NB_IM = 10
    NOM = 'TT_deg1_'

    # Define the mesh
    tx = SIZE
    ty = tx
    nbx = N
    nby = nbx

    mesh = RectangleMesh(Point(-tx/2, -ty/2), Point(tx/2, ty/2), nbx, nby)

    V1 = VectorElement("Lagrange", mesh.ufl_cell(), DEG)
    V2 = FiniteElement("Lagrange", mesh.ufl_cell(), DEG)
    V = VectorFunctionSpace(mesh, 'P', DEG)
    Vs = FunctionSpace(mesh, 'P', DEG)

    # Make a mixed space
    TH = V1*V2
    # Create periodic boundary condition
    pbc = PeriodicBoundary()
    W = FunctionSpace(mesh, TH, constrained_domain=pbc)


    u_in = Expression(('1', '1'), degree=0)
    u_0 = interpolate(u_in, W.sub(0).collapse())

    rho_0 = Constant(RHO_0)
    p_in = Expression('rho_0*exp(-4*(x[0]-1.5)*(x[0]-1.5)-4*(x[1]-1.5)*(x[1]-1.5))', degree=DEG, rho_0=rho_0)
    p_0 = interpolate(p_in, W.sub(1).collapse())
        
        
    # Declare functions
    w = Function(W)
    w_1 = Function(W)

    assign(w, [u_0, p_0])
    assign(w_1, [u_0, p_0])

    w_t = TestFunction(W)

    (u, p) = split(w)
    (u_1, p_1) = split(w_1)
    (v, q) = split(w_t)

    # Declare constants
    n = FacetNormal(mesh)
    D2 = Constant(1)
    Dt=Constant(DT)

    # Declare the form
    F = dot((u-u_1), v)*dx \
        +((p-p_1)/Dt*q+D2*dot(grad(p), grad(q)))*dx \
        -D2*dot(grad(p),n)*q*ds

    for i in range(0, NUM_STEP):
        plotfigure = False
        try:
            solve(F == 0, w)
        except RuntimeError:
            break
        assign(w_1, w)
        try:
            p_ana = project(w[2], Vs)
            u_ana = project(as_vector([w[0], w[1]]), V)

            if i%NB_IM == 0:
                plotfigure = True
                fig_defaut(u_ana, p_ana, ifig=i, plotfigure=plotfigure, nom=NOM)
        except:
            print("not plotted")

The only thing I have done is changing the degree of the elements from 1 to 2.
And I obtain that: (which is perfect)

So, I’m gonna close this topic but if you have any idea why this works, please let me knwo.
Kind regard.