FEM - how should I impose periodic boundary conditions in pure space problems?

Too long for a comment:

OK, first set up your system that you only have non periodic BCs. Then look at the Finite Element programming tutorial and use NDSolve ProcessEquations and follow the steps until the call to DiscretizePDE and DiscretizeBoundaryConditions. At this point you can extract the system matrices. Deploy the (non periodic) boundary conditions. Until here everything is documented.

Periodic boundary conditions come in pairs; an algebraic constraint it added to the system of equations.

Here is an example: Create a system matrix and a load vector:

n = 5;
matrix = Table[ 
   Switch[ j - i, -1, 1, 0, -2, 1, 1, _, 0], {i, n}, {j, n} ];
loadVector = Table[ i, { i, n } ];

Now, for example, we would like to change the system equations such that u1 == u3. We do this with Lagrangian multipliers:

lv = ConstantArray[0, {n}];
(* set u1-u3, for example *)
lv[[1]] = 1;
lv[[3]] = -1;

(* add lagrange row *)
matrix2 = Join[matrix, {lv}];
(* add lagrange col *)
matrix3 = Join[matrix2, Partition[Join[lv, {0}], 1], 2]

(*
{{-2, 1, 0, 0, 0, 1}, {1, -2, 1, 0, 0, 0}, {0, 1, -2, 1, 0, -1}, {0, 
  0, 1, -2, 1, 0}, {0, 0, 0, 1, -2, 0}, {1, 0, -1, 0, 0, 0}}
*)

The system is now larger because we added the equation u1 - u3 and we need to enlarge the load vector as well; we want the the equation u1 - u3 == 0

(* set u1-u3 == 0 *)
load2 = Join[loadVector, {0}];

Solve the equations:

res = LinearSolve[matrix3, load2]
{-(31/4), -(35/4), -(31/4), -(19/2), -(29/4), -(23/4)}

And check that u1 == u3

res[[{1, 3}]]
{-(31/4), -(31/4)}

Hope this helps.

OK, after the update, here is how I'd do it:

reg = Rectangle[{0, 0}, {1, 1}];
(*Obtain FEM state*)
{state} = 
  NDSolve`ProcessEquations[{eq0 == 0, conds}, u, 
   Element[{x1, x2}, reg], 
   Method -> {"FiniteElement", 
     "MeshOptions" -> {"MaxCellMeasure" -> 0.2, "MeshOrder" -> 1}, 
     "IntegrationOrder" -> 2}];
femstate = state["FiniteElementData"];
methodData = femstate["FEMMethodData"];
initBCs = femstate["BoundaryConditionData"];
initCoeff = femstate["PDECoefficientData"];
sd = state["SolutionData"][[1]];
discretePDE = DiscretizePDE[initCoeff, methodData, sd];
stiffness = discretePDE["StiffnessMatrix"];
load = discretePDE["LoadVector"];
discreteBCs = DiscretizeBoundaryConditions[initBCs, methodData, sd];
DeployBoundaryConditions[{load, stiffness}, discreteBCs]

So far so good. The "InterpolationOrder" setting you used is to be able specify different interpolation order for coupled systems, which we do not have here. So the interpolation order will always be the same as the mesh order. Also, we extract everything from the state data. That may be a bit more convenient then to set this up manually. But by all means do as you like.

Extract and visualize the mesh:

mesh = NDSolve`SolutionDataComponent[sd, "Space"]["ElementMesh"];
Show[
 mesh["Wireframe"["MeshElementIDStyle" -> Blue]]
 , mesh["Wireframe"["MeshElement" -> "PointElements", 
   "MeshElementIDStyle" -> Red]]]

enter image description here

Now, we extract the positions of the relevant coordinates:

c = mesh["Coordinates"];
cornerPos = Position[c, Alternatives @@ Tuples[{0., 1.}, 2]];
tc = Transpose[c];
zeroPos = Position[tc[[1]], 0.]
onePos = Position[tc[[1]], 1.]
(*
{{1}, {2}, {3}, {4}}
{{13}, {14}, {15}, {16}}
*)

left = Flatten[Complement[zeroPos, cornerPos]]
right = Flatten[Complement[onePos, cornerPos]]
(*
{2, 3}
{14, 15}
*)

We use the left nodes as master nodes and the right nodes as slave nodes.

dof = methodData["DegreesOfFreedom"];
lmdof = Length[left];

Now, we are going to hack a bit. This is not documented. We are going to create a DiscretizedBoundaryConditionData that we can then use in DeployBoundaryConditions. This is a bit risky as the internal data structure may change in the future. (You may want to wrap this in a function and if the data structure changes then you'd only need to fix it in that function and not everywhere where you used the code.)

We create a matrix where positions in every row are set to +1 and -1 as explained above.

diriMat = SparseArray[{}, {lmdof, dof}];
MapThread[(diriMat[[#1, #2]] = {1, -1}) &, {Range[lmdof], 
   Transpose[{left, right}]}];

Specify where we have Dirichlet rows:

diriRows = left;

Specify the Dirichlet values which are zero in our case:

diriVals = SparseArray[{}, {lmdof, 1}];

We create the DiscretizedBoundaryConditionData data structure:

lmDiscrete = 
  DiscretizedBoundaryConditionData[{SparseArray[{}, {dof, 1}], 
    SparseArray[{}, {dof, dof}], diriMat, diriRows, 
    diriVals, {dof, 0, lmdof}}, 1];

The first sparse array (which is zero) is added to the load, the second (which is also zero in our case) is added to the stiffness matrix. Then diriMat, diriRows and diriVals are specified. Last some size parameters are given. (What I'll need to think about is to provide an interface to generate DiscretizedBoundaryConditionData such that such hacks are not requited.)

DeployBoundaryConditions[{load, stiffness}, lmDiscrete, 
 "ConstraintMethod" -> "Append"]
(*Solution,interpolation function and plot*)
solution = LinearSolve[stiffness, load];
(* Truncate solution back to dof size *)
solution = Take[solution, dof];

When you crank up "MeshOptions" -> {"MaxCellMeasure" -> 0.02, "MeshOrder" -> 2} you get something like this:

uif = ElementMeshInterpolation[{mesh}, solution];
Plot3D[uif[x1, x2], Element[{x1, x2}, reg], PlotRange -> All, 
 AxesLabel -> {x1, x2}]

enter image description here

And

Plot[uif[0, y] - uif[1, y], {y, 0, 1}]

All good? ;-)

enter image description here


Version 11.0 has an additional boundary condition PeriodicBoundaryCondition. With this you can then use:

ufun = NDSolveValue[{-Laplacian[u[x, y], {x, y}] == 10 x^5*y^3,
    DirichletCondition[u[x, y] == 0, x == 0 && y == 0], 
    PeriodicBoundaryCondition[u[x, y], 0 <= y <= 1 && x == 1, 
     FindGeometricTransform[{{0, 0}, {0, 1}}, {{1, 0}, {1, 1}}][[2]]],
     PeriodicBoundaryCondition[u[x, y], 0 <= x <= 1 && y == 1, 
     FindGeometricTransform[{{0, 0}, {1, 0}}, {{0, 1}, {1, 1}}][[
      2]]]}, u, {x, y} \[Element] Rectangle[{0, 0}, {1, 1}]];

Plot3D[ufun[x, y], {x, y} \[Element] Rectangle[{0, 0}, {1, 1}]]

This also works on non-rectangular regions. One thing to be aware of is that you only need one DirichletCondition since that is mapped to the opposite side and the by now two conditions are mapped to the other opposite side making them four all in all.

enter image description here