Skip to content

Disappearing internal boundary conditions with periodicity

OpenLB – Open Source Lattice Boltzmann Code Forums on Lattice Boltzmann Methods General Topics Disappearing internal boundary conditions with periodicity

Viewing 1 post (of 1 total)
  • Author
    Posts
  • #4654
    loganrk
    Participant

    I am using Electron with TypeScript to prototype some fluid simulation code which will eventually go into a game. So far, I have been using static boundary conditions (with simulation calculations only occurring on the interior of the grid, and values for the boundary cells remaining fixed), and everything appears to work fine in that regime. In particular, I can impose internal boundary conditions (for example, enforcing that a certain density of fluid always exits a certain lattice site on every frame, to simulate a hose/rocket nozzle/whatever) by just manually setting the cell values in between each simulation step.

    However, if I switch to using periodic boundary conditions, the whole simulation become static. I just get constant fluid density everywhere, for all time, and it’s like all of my internal boundary conditions are erased, no matter where in the simulation cycle (before streaming or before collision) I choose to assert them. I am not sure if periodic boundary conditions will end up being relevant for the game, but this failure makes me think there must be some other subtle bug somewhere in the simulation.

    The complete code is available at https://github.com/gliese1337/balloon-prototype/tree/deopt , but what I expect are the relevant portions are as follows:

    
        class LatticeBoltzmann {        
          private streamed: Float32Array; // microscopic densities along each lattice direction
          private collided: Float32Array;
          public rho: Float32Array;    // macroscopic density; cached for rendering
    
          ...
    
          public stream(barriers: boolean[]) {
            const { xdim, ydim, collided, streamed } = this;
            const index = (x: number, y: number) => (x%xdim)+(y%ydim)*xdim;
            const cIndex = (x: number, y: number, s: -1|1, j: number) =>
                              9*(((x+s*cxs[j])%xdim)+((y+s*cys[j])%ydim)*xdim)+j;
    
            // Move particles along their directions of motion:
            for (let y=1; y<ydim-1; y++) {
              for (let x=1; x<xdim-1; x++) {
                const i = index(x, y);
                const i9 = i*9;
                for (let j=0;j<9;j++) {
                  streamed[i9 + j] = collided[cIndex(x, y, -1, j)];
                }
              }
            }
    
            // Handle bounce-back from barriers
            for (let y=0; y<ydim; y++) {
              for (let x=0; x<xdim; x++) {
                const i = index(x, y);
                const i9 = i*9;
                if (barriers[i]) {
                  for (let j=1;j<9;j++) {
                    streamed[cIndex(x, y, 1, j)] = collided[i9 + opp[j]];
                  }
                }
              }
            }
          }
    
          // Set all densities in a cell to their equilibrium values for a given velocity and density:
          public setEquilibrium(x: number, y: number, ux: number, uy: number, rho: number) {
            const { xdim, streamed } = this;
            const i = x + y*xdim;
            this.rho[i] = rho;
    
            const i9 = i*9;
            const u2 =  1 - 1.5 * (ux * ux + uy * uy);
            for (let j = 0; j < 9; j++) {
              const dir = cxs[j]*ux + cys[j]*uy;
              streamed[i9+j] =  weights[j] * rho * (u2 + 3 * dir + 4.5 * dir * dir);
            }
          }
        }
    

    Lattice data is stored in two flat arrays, collided which holds the end states after the collision step and serves as input to the streaming step, and streamed, which holds the end states after the streaming step and serves as input to the next collision step. The 9 vector components for the D2Q9 lattice are stored in contiguous blocks, which are then grouped into rows. Note that I am already using mod operations to calculate array indices from lattice coordinates; this is completely irrelevant as long as the simulation calculations only range over the interior of the lattice, but it should make periodic boundaries ready-to-go as soon as the for (let y=1; y<ydim-1; y++) and for (let x=1; x<xdim-1; x++) loops have their bounds changed to for (let y=0; y<ydim; y++) and for (let x=0; x<xdim; x++), respectively. And indeed it is that specific 6-character change that I am having trouble with.

    The setEquilibrium method is used to impose boundary conditions. In the driver code, it is currently being called like this, once per frame:

    
        // Make fluid flow in from the left edge and out through the right edge
        function setBoundaries(LB: LatticeBoltzmann, ux: number) {
            for (let y=0; y<ydim; y++) {
                LB.setEquilibrium(0, y, ux, 0, 1);
                LB.setEquilibrium(xdim-1, y, ux, 0, 1);
            }
        }
    

    With static boundary conditions, calling that once per frame happens to be superfluous, because it only alters the boundary lattice sites. Shifting the hard-coded x-values to the interior of the lattice, however, (where reasserting the boundary conditions once per frame is in fact necessary) does exactly what you would expect–it makes fluid appear or disappear at specific locations. Switching to periodic boundary conditions, however, results in that code ceasing to have any visible effect.

    So… anybody know what I might be doing wrong?

Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.