Exemplo n.º 1
0
int main(int argc, char* argv[])
{
    plbInit(&argc, &argv);
    global::directories().setOutputDir("./");
    global::IOpolicy().activateParallelIO(false);
    plint resolution=0;
    plint referenceDirection=0;

    string stlFileName, outFileName;
    try {
        global::argv(1).read(stlFileName);
        global::argv(2).read(outFileName);
        global::argv(3).read(resolution);
        global::argv(4).read(referenceDirection);
    }
    catch (PlbIOException& exception) {
        pcout << "Wrong parameters; the syntax is: " 
              << (std::string)global::argv(0) << " inputSTL.stl outputSTL.stl resolution referenceDirection[0,1,2]" << std::endl;
        exit(-1);
    }

    TriangleSet<T>* triangleSet = 0;
    try {
        triangleSet = new TriangleSet<T>(stlFileName, DBL);
    }
    catch (PlbIOException& exception) {
        pcout << "Error, could not read STL file " << stlFileName
              << ": " << exception.what() << std::endl;
        exit(-1);
    }
    DEFscaledMesh<T> mesh(*triangleSet, resolution, referenceDirection, margin, extraLayer);
    TriangleBoundary3D<T> boundary(mesh);
    T dx = boundary.getDx();
    Array<T,3> location(boundary.getPhysicalLocation());
    VoxelizedDomain3D<T> voxelizedDomain (
            boundary, typeOfVoxelization, extraLayer, borderWidth, extendedEnvelopeWidth, blockSize );

    MultiScalarField3D<T> flagMatrix((MultiBlock3D&)voxelizedDomain.getVoxelMatrix());
    setToConstant(flagMatrix, voxelizedDomain.getVoxelMatrix(),
                  voxelFlag::inside, flagMatrix.getBoundingBox(), 1.0);
    setToConstant(flagMatrix, voxelizedDomain.getVoxelMatrix(),
                  voxelFlag::innerBorder, flagMatrix.getBoundingBox(), 1.0);
    pcout << "Number of inside cells: " << computeSum(flagMatrix) << std::endl;

   TriangleSet<T> triangles (
           vofToTriangles<T,descriptors::D3Q19Descriptor>( 
               flagMatrix, 0.5, flagMatrix.getBoundingBox().enlarge(-2) ) );

    DEFscaledMesh<T> newMesh(triangles);
    TriangleBoundary3D<T> newBoundary(newMesh);
    newBoundary.getMesh().scale(dx);
    newBoundary.getMesh().translate(location);
    newBoundary.getMesh().writeBinarySTL(outFileName);

    return 0;
}
Exemplo n.º 2
0
void iniLattice( MultiBlockLattice3D<T,DESCRIPTOR>& lattice,
                 VoxelizedDomain3D<T>& voxelizedDomain )
{
    // Switch all remaining outer cells to no-dynamics, except the outer
    //   boundary layer, and keep the rest as BGKdynamics.
    defineDynamics(lattice, voxelizedDomain.getVoxelMatrix(), lattice.getBoundingBox(),
                   new NoDynamics<T,DESCRIPTOR>, voxelFlag::outside);
    initializeAtEquilibrium(lattice, lattice.getBoundingBox(), (T) 1., Array<T,3>((T)0.,(T)0.,(T)0.));
    lattice.initialize();
}
void runProgram()
{
    /*
     * Read the obstacle geometry.
     */

    pcout << std::endl << "Reading STL data for the obstacle geometry." << std::endl;
    Array<T,3> center(param.cx, param.cy, param.cz);
    Array<T,3> centerLB(param.cxLB, param.cyLB, param.czLB);
    // The triangle-set defines the surface of the geometry.
    TriangleSet<T> triangleSet(param.geometry_fname, DBL);

    // Place the obstacle in the correct place in the simulation domain.
    // Here the "geometric center" of the obstacle is computed manually,
    // by computing first its bounding cuboid. In cases that the STL
    // file with the geometry of the obstacle contains its center as
    // the point, say (0, 0, 0), then the following variable
    // "obstacleCenter" must be set to (0, 0, 0) manually.
    Cuboid<T> bCuboid = triangleSet.getBoundingCuboid();
    Array<T,3> obstacleCenter = 0.5 * (bCuboid.lowerLeftCorner + bCuboid.upperRightCorner);
    triangleSet.translate(-obstacleCenter);
    triangleSet.scale(1.0/param.dx); // In lattice units from now on...
    triangleSet.translate(centerLB);
    triangleSet.writeBinarySTL(outputDir+"obstacle_LB.stl");

    // The DEFscaledMesh, and the triangle-boundary are more sophisticated data
    // structures used internally by Palabos to treat the boundary.
    plint xDirection = 0;
    plint borderWidth = 1;      // Because Guo acts in a one-cell layer.
                                // Requirement: margin>=borderWidth.
    plint margin = 1;           // Extra margin of allocated cells around the obstacle, for the case of moving walls.
    plint blockSize = 0;        // Size of blocks in the sparse/parallel representation.
                                // Zero means: don't use sparse representation.
    DEFscaledMesh<T> defMesh(triangleSet, 0, xDirection, margin, Dot3D(0, 0, 0));
    TriangleBoundary3D<T> boundary(defMesh);
    //boundary.getMesh().inflate();

    pcout << "tau = " << 1.0/param.omega << std::endl;
    pcout << "dx = " << param.dx << std::endl;
    pcout << "dt = " << param.dt << std::endl;
    pcout << "Number of iterations in an integral time scale: " << (plint) (1.0/param.dt) << std::endl;

    /*
     * Voxelize the domain.
     */

    // Voxelize the domain means: decide which lattice nodes are inside the obstacle and which are outside.
    pcout << std::endl << "Voxelizing the domain." << std::endl;
    plint extendedEnvelopeWidth = 2;   // Extrapolated off-lattice BCs.
    const int flowType = voxelFlag::outside;
    VoxelizedDomain3D<T> voxelizedDomain (
            boundary, flowType, param.boundingBox(), borderWidth, extendedEnvelopeWidth, blockSize );
    pcout << getMultiBlockInfo(voxelizedDomain.getVoxelMatrix()) << std::endl;

    /*
     * Generate the lattice, the density and momentum blocks.
     */

    pcout << "Generating the lattice, the rhoBar and j fields." << std::endl;
    MultiBlockLattice3D<T,DESCRIPTOR> *lattice = new MultiBlockLattice3D<T,DESCRIPTOR>(voxelizedDomain.getVoxelMatrix());
    if (param.useSmago) {
        defineDynamics(*lattice, lattice->getBoundingBox(),
                new SmagorinskyBGKdynamics<T,DESCRIPTOR>(param.omega, param.cSmago));
        pcout << "Using Smagorinsky BGK dynamics." << std::endl;
    } else {
        defineDynamics(*lattice, lattice->getBoundingBox(),
                new BGKdynamics<T,DESCRIPTOR>(param.omega));
        pcout << "Using BGK dynamics." << std::endl;
    }
    bool velIsJ = false;
    defineDynamics(*lattice, voxelizedDomain.getVoxelMatrix(), lattice->getBoundingBox(),
            new NoDynamics<T,DESCRIPTOR>(), voxelFlag::inside);
    lattice->toggleInternalStatistics(false);

    MultiBlockManagement3D sparseBlockManagement(lattice->getMultiBlockManagement());

    // The rhoBar and j fields are used at both the collision and at the implementation of the
    // outflow boundary condition.
    plint envelopeWidth = 1;
    MultiScalarField3D<T> *rhoBar = new MultiScalarField3D<T> (
            MultiBlockManagement3D (
                sparseBlockManagement.getSparseBlockStructure(),
                sparseBlockManagement.getThreadAttribution().clone(),
                envelopeWidth ),
            defaultMultiBlockPolicy3D().getBlockCommunicator(),
            defaultMultiBlockPolicy3D().getCombinedStatistics(),
            defaultMultiBlockPolicy3D().getMultiScalarAccess<T>() );
    rhoBar->toggleInternalStatistics(false);

    MultiTensorField3D<T,3> *j = new MultiTensorField3D<T,3> (
            MultiBlockManagement3D (
                sparseBlockManagement.getSparseBlockStructure(),
                sparseBlockManagement.getThreadAttribution().clone(),
                envelopeWidth ),
            defaultMultiBlockPolicy3D().getBlockCommunicator(),
            defaultMultiBlockPolicy3D().getCombinedStatistics(),
            defaultMultiBlockPolicy3D().getMultiTensorAccess<T,3>() );
    j->toggleInternalStatistics(false);

    std::vector<MultiBlock3D*> lattice_rho_bar_j_arg;
    lattice_rho_bar_j_arg.push_back(lattice);
    lattice_rho_bar_j_arg.push_back(rhoBar);
    lattice_rho_bar_j_arg.push_back(j);
    integrateProcessingFunctional(
            new ExternalRhoJcollideAndStream3D<T,DESCRIPTOR>(),
            lattice->getBoundingBox(), lattice_rho_bar_j_arg, 0);
    integrateProcessingFunctional(
            new BoxRhoBarJfunctional3D<T,DESCRIPTOR>(),
            lattice->getBoundingBox(), lattice_rho_bar_j_arg, 3); // rhoBar and j are computed at level 3 because
                                                                  // the boundary conditions are on levels 1 and 2.

    /*
     * Generate the off-lattice boundary condition on the obstacle and the outer-domain boundary conditions.
     */

    pcout << "Generating boundary conditions." << std::endl;

    OffLatticeBoundaryCondition3D<T,DESCRIPTOR,Velocity> *boundaryCondition;
    
    BoundaryProfiles3D<T,Velocity> profiles;
    bool useAllDirections=true;
    OffLatticeModel3D<T,Velocity>* offLatticeModel=0;
    if (param.freeSlipWall) {
        profiles.setWallProfile(new FreeSlipProfile3D<T>);
    }
    else {
        profiles.setWallProfile(new NoSlipProfile3D<T>);
    }
    offLatticeModel =
         new GuoOffLatticeModel3D<T,DESCRIPTOR> (
            new TriangleFlowShape3D<T,Array<T,3> >(voxelizedDomain.getBoundary(), profiles),
            flowType, useAllDirections );
    offLatticeModel->setVelIsJ(velIsJ);
    boundaryCondition = new OffLatticeBoundaryCondition3D<T,DESCRIPTOR,Velocity>(
            offLatticeModel, voxelizedDomain, *lattice);
    
    boundaryCondition->insert();

    // The boundary condition algorithm or the outer domain.
    OnLatticeBoundaryCondition3D<T,DESCRIPTOR>* outerBoundaryCondition
        = createLocalBoundaryCondition3D<T,DESCRIPTOR>();
    outerDomainBoundaries(lattice, rhoBar, j, outerBoundaryCondition);

    /*
     * Implement the outlet sponge zone.
     */

    if (param.numOutletSpongeCells > 0) {
        T bulkValue;
        Array<plint,6> numSpongeCells;

        if (param.outletSpongeZoneType == 0) {
            pcout << "Generating an outlet viscosity sponge zone." << std::endl;
            bulkValue = param.omega;
        } else if (param.outletSpongeZoneType == 1) {
            pcout << "Generating an outlet Smagorinsky sponge zone." << std::endl;
            bulkValue = param.cSmago;
        } else {
            pcout << "Error: unknown type of sponge zone." << std::endl;
            exit(-1);
        }

        // Number of sponge zone lattice nodes at all the outer domain boundaries.
        // So: 0 means the boundary at x = 0
        //     1 means the boundary at x = nx-1
        //     2 means the boundary at y = 0
        //     and so on...
        numSpongeCells[0] = 0;
        numSpongeCells[1] = param.numOutletSpongeCells;
        numSpongeCells[2] = 0;
        numSpongeCells[3] = 0;
        numSpongeCells[4] = 0;
        numSpongeCells[5] = 0;

        std::vector<MultiBlock3D*> args;
        args.push_back(lattice);

        if (param.outletSpongeZoneType == 0) {
            applyProcessingFunctional(new ViscositySpongeZone<T,DESCRIPTOR>(
                        param.nx, param.ny, param.nz, bulkValue, numSpongeCells),
                    lattice->getBoundingBox(), args);
        } else {
            applyProcessingFunctional(new SmagorinskySpongeZone<T,DESCRIPTOR>(
                        param.nx, param.ny, param.nz, bulkValue, param.targetSpongeCSmago, numSpongeCells),
                    lattice->getBoundingBox(), args);
        }
    }

    /*
     * Setting the initial conditions.
     */

    // Initial condition: Constant pressure and velocity-at-infinity everywhere.
    Array<T,3> uBoundary(param.getInletVelocity(0), 0.0, 0.0);
    initializeAtEquilibrium(*lattice, lattice->getBoundingBox(), 1.0, uBoundary);
    applyProcessingFunctional(
            new BoxRhoBarJfunctional3D<T,DESCRIPTOR>(),
            lattice->getBoundingBox(), lattice_rho_bar_j_arg); // Compute rhoBar and j before VirtualOutlet is executed.
    lattice->executeInternalProcessors(1); // Execute all processors except the ones at level 0.
    lattice->executeInternalProcessors(2);
    lattice->executeInternalProcessors(3);

    /*
     * Particles (streamlines).
     */

    // This part of the code that relates to particles, is purely for visualization
    // purposes. Particles are used to compute streamlines essentially.

    // Definition of a particle field.
    MultiParticleField3D<ParticleFieldT>* particles = 0;

    if (param.useParticles) {
        particles = new MultiParticleField3D<ParticleFieldT> (
            lattice->getMultiBlockManagement(),
            defaultMultiBlockPolicy3D().getCombinedStatistics() );

        std::vector<MultiBlock3D*> particleArg;
        particleArg.push_back(particles);

        std::vector<MultiBlock3D*> particleFluidArg;
        particleFluidArg.push_back(particles);
        particleFluidArg.push_back(lattice);

        // Functional that advances the particles to their new position at each predefined time step.
        integrateProcessingFunctional (
                new AdvanceParticlesEveryWhereFunctional3D<T,DESCRIPTOR>(param.cutOffSpeedSqr),
                lattice->getBoundingBox(), particleArg, 0);
        // Functional that assigns the particle velocity according to the particle's position in the fluid.
        integrateProcessingFunctional (
                new FluidToParticleCoupling3D<T,DESCRIPTOR>((T) param.particleTimeFactor),
                lattice->getBoundingBox(), particleFluidArg, 1 );

        // Definition of a domain from which particles will be injected in the flow field.
        Box3D injectionDomain(0, 0, centerLB[1]-0.25*param.ny, centerLB[1]+0.25*param.ny,
                centerLB[2]-0.25*param.nz, centerLB[2]+0.25*param.nz);

        // Definition of simple mass-less particles.
        Particle3D<T,DESCRIPTOR>* particleTemplate=0;
        particleTemplate = new PointParticle3D<T,DESCRIPTOR>(0, Array<T,3>(0.,0.,0.), Array<T,3>(0.,0.,0.));

        // Functional which injects particles with predefined probability from the specified injection domain.
        std::vector<MultiBlock3D*> particleInjectionArg;
        particleInjectionArg.push_back(particles);

        integrateProcessingFunctional (
                new InjectRandomParticlesFunctional3D<T,DESCRIPTOR>(particleTemplate, param.particleProbabilityPerCell),
                injectionDomain, particleInjectionArg, 0 );

        // Definition of an absorbtion domain for the particles.
        Box3D absorbtionDomain(param.outlet);

        // Functional which absorbs the particles which reach the specified absorbtion domain.
        integrateProcessingFunctional (
                new AbsorbParticlesFunctional3D<T,DESCRIPTOR>, absorbtionDomain, particleArg, 0 );

        particles->executeInternalProcessors();
    }

    /*
     * Starting the simulation.
     */

    plb_ofstream energyFile((outputDir+"average_energy.dat").c_str());

    pcout << std::endl;
    pcout << "Starting simulation." << std::endl;
    for (plint i = 0; i < param.maxIter; ++i) {
        if (i <= param.initialIter) {
            Array<T,3> uBoundary(param.getInletVelocity(i), 0.0, 0.0);
            setBoundaryVelocity(*lattice, param.inlet, uBoundary);
        }

        if (i % param.statIter == 0) {
             pcout << "At iteration " << i << ", t = " << i*param.dt << std::endl;
             Array<T,3> force(boundaryCondition->getForceOnObject());
             T factor = util::sqr(util::sqr(param.dx)) / util::sqr(param.dt);
             pcout << "Force on object over fluid density: F[x] = " << force[0]*factor << ", F[y] = "
                   << force[1]*factor << ", F[z] = " << force[2]*factor << std::endl;
             T avEnergy = boundaryCondition->computeAverageEnergy() * util::sqr(param.dx) / util::sqr(param.dt);
             pcout << "Average kinetic energy over fluid density: E = " << avEnergy << std::endl;
             energyFile << i*param.dt << "  " << avEnergy << std::endl;
             pcout << std::endl;
        }

        if (i % param.vtkIter == 0) {
            pcout << "Writing VTK at time t = " << i*param.dt << endl;
            writeVTK(*boundaryCondition, i);
            if (param.useParticles) {
                writeParticleVtk<T,DESCRIPTOR> (
                        *particles, createFileName(outputDir+"particles_", i, PADDING) + ".vtk",
                        param.maxNumParticlesToWrite );
            }
        }

        if (i % param.imageIter == 0) {
            pcout << "Writing PPM image at time t = " << i*param.dt << endl;
            writePPM(*boundaryCondition, i);
        }

        lattice->executeInternalProcessors();
        lattice->incrementTime();
        if (param.useParticles && i % param.particleTimeFactor == 0) {
            particles->executeInternalProcessors();
        }
    }
    
    energyFile.close();
    delete outerBoundaryCondition;
    delete boundaryCondition;
    if (param.useParticles) {
        delete particles;
    }
    delete j;
    delete rhoBar;
    delete lattice;
} 
Exemplo n.º 4
0
int main(int argc, char* argv[])
{
    plbInit(&argc, &argv);
    global::directories().setOutputDir("./");

    // Create the cylinder surface as a set of triangles.
    TriangleSet<T> triangleSet;
    triangleSet = constructCylinder<T>(originalCenter, radius, radius, length, nAxial, nCirc);

    // The next few lines of code are typical. They transform the surface geometry of the
    //   tube to more efficient data structures that are internally used by palabos.
    //   The TriangleBoundary3D structure will be later used to assign proper boundary conditions.
    DEFscaledMesh<T> defMesh(triangleSet, ny, yDirection, margin, extraLayer);
    defMesh.getMesh().inflate();
    TriangleBoundary3D<T> boundary(defMesh);

    pcout << "Number of inlets/outlets which were closed: "
          << boundary.getInletOutlet(xDirection).size() << std::endl;
    inletCenter = computeBaryCenter(boundary.getMesh(),boundary.getInletOutlet(xDirection)[0]);
    outletCenter = computeBaryCenter(boundary.getMesh(),boundary.getInletOutlet(xDirection)[1]);
    radius = computeInnerRadius(boundary.getMesh(),boundary.getInletOutlet(xDirection)[0]);

    pcout << "Inlet center: " << inletCenter[0] << "," << inletCenter[1] << "," << inletCenter[2] << std::endl;
    pcout << "Outlet center: " << outletCenter[0] << "," << outletCenter[1] << "," << outletCenter[2] << std::endl;

    T nu = uAverage * 2.*radius / Reynolds;
    T omega = 1./(3.*nu+0.5);
    pcout << "omega=" << omega << std::endl;

    boundary.getMesh().writeAsciiSTL("cylinder.stl");
    pcout << "Number of triangles: " << boundary.getMesh().getNumTriangles() << std::endl;

    // The tube simulation is an interior (as opposed to exterior) flow problem. For
    //   this reason, the lattice nodes that lie inside the computational domain must
    //   be identified and distinguished from the ones that lie outside of it. This is
    //   handled by the following voxelization process.
    pcout << std::endl << "Voxelizing the domain." << std::endl;
    const int flowType = voxelFlag::inside;
    VoxelizedDomain3D<T> voxelizedDomain (
            boundary, flowType, extraLayer, borderWidth, extendedEnvelopeWidth, blockSize);
    pcout << getMultiBlockInfo(voxelizedDomain.getVoxelMatrix()) << std::endl;

    std::auto_ptr<MultiBlockLattice3D<T,DESCRIPTOR> > lattice 
        = generateMultiBlockLattice<T,DESCRIPTOR> (
                voxelizedDomain.getVoxelMatrix(), extendedEnvelopeWidth, new BGKdynamics<T,DESCRIPTOR>(omega) );

    // The Guo off lattice boundary condition is set up.
    pcout << "Creating boundary condition." << std::endl;
    BoundaryProfiles3D<T,Velocity> profiles;
    profiles.defineInletOutletTags(boundary, xDirection);
    profiles.setInletOutlet (
            new PoiseuilleProfile3D<T>(uAverage),
            new PoiseuilleProfile3D<T>(-uAverage) );
    GuoOffLatticeModel3D<T,DESCRIPTOR>* model =
            new GuoOffLatticeModel3D<T,DESCRIPTOR> (
                new TriangleFlowShape3D<T,Array<T,3> > (
                    voxelizedDomain.getBoundary(), profiles),
                flowType, useAllDirections );
    model->selectUseRegularizedModel(useRegularized);
    OffLatticeBoundaryCondition3D<T,DESCRIPTOR,Velocity> boundaryCondition (
            model, voxelizedDomain, *lattice);
    boundaryCondition.insert();

    pcout << std::endl << "Initializing lattice." << std::endl;
    iniLattice(*lattice, voxelizedDomain);

    // Particles

    // Definition of a particle field.
    MultiParticleField3D<DenseParticleField3D<T,DESCRIPTOR> >* particles=0;
    particles = new MultiParticleField3D<DenseParticleField3D<T,DESCRIPTOR> > (
        lattice->getMultiBlockManagement(),
        defaultMultiBlockPolicy3D().getCombinedStatistics() );

    std::vector<MultiBlock3D*> particleArg;
    particleArg.push_back(particles);

    std::vector<MultiBlock3D*> particleFluidArg;
    particleFluidArg.push_back(particles);
    particleFluidArg.push_back(lattice.get());

    // Functional that advances the particles to their new position at each
    //   predefined time step.
    integrateProcessingFunctional (
            new AdvanceParticlesFunctional3D<T,DESCRIPTOR>(cutOffSpeedSqr),
            lattice->getBoundingBox(), particleArg, 0);
    // Functional that assigns the particle velocity according to the particle's
    //   position in the fluid.
    integrateProcessingFunctional (
            new FluidToParticleCoupling3D<T,DESCRIPTOR>((T)particleTimeFactor),
            lattice->getBoundingBox(), particleFluidArg, 1 );

    // Definition of a domain from which particles will be injected in the flow field.
    //   The specific domain is close to the inlet of the tube.
    Box3D injectionDomain(lattice->getBoundingBox());
    injectionDomain.x0 = inletCenter[0] +2;
    injectionDomain.x1 = inletCenter[0] +4;

    // Definition of simple mass-less particles.
    Particle3D<T,DESCRIPTOR>* particleTemplate=0;
    particleTemplate = new PointParticle3D<T,DESCRIPTOR>(0, Array<T,3>(0.,0.,0.), Array<T,3>(0.,0.,0.));

    // For the sake of illustration, particles are being injected in an area close to the
    // center of the tube.
    T injectionRadius = radius/4.;
    // Functional which injects particles with predefined probability from the specified injection domain.
    integrateProcessingFunctional (
            new AnalyticalInjectRandomParticlesFunctional3D<T,DESCRIPTOR,CircularInjection> (
                particleTemplate, particleProbabilityPerCell, CircularInjection(injectionRadius, inletCenter) ),
            injectionDomain, particleArg, 0 );

    // Definition of an absorbtion domain for the particles. The specific domain is very close to the
    //   exit of the tube.
    Box3D absorbtionDomain(lattice->getBoundingBox());
    absorbtionDomain.x0 = outletCenter[0] -2;
    absorbtionDomain.x1 = outletCenter[0] -2;

    // Functional which absorbs the particles which reach the specified absorbtion domain.
    integrateProcessingFunctional (
            new AbsorbParticlesFunctional3D<T,DESCRIPTOR>,
            absorbtionDomain, particleArg, 0 );

    particles->executeInternalProcessors();

    pcout << std::endl << "Starting simulation." << std::endl;
    for (plint i=0; i<maxIter; ++i) {
        if (i%outIter==0) {
            pcout << "Iteration= " << i << "; "
                  << "Average energy: "
                  << boundaryCondition.computeAverageEnergy() << std::endl;
            pcout << "Number of particles in the tube: "
                  << countParticles(*particles, particles->getBoundingBox()) << std::endl;
                  
        }
        if (i%saveIter==0 && i>0) {
            pcout << "Write visualization files." << std::endl;
            VtkImageOutput3D<T> vtkOut("volume", 1.); 
            vtkOut.writeData<float>(*boundaryCondition.computePressure(), "p", 1.);
            vtkOut.writeData<float>(*boundaryCondition.computeVelocityNorm(), "u", 1.);

            pcout << "Write particle output file." << std::endl;
            writeAsciiParticlePos(*particles, "particle_positions.dat");
            writeParticleVtk(*particles, "particles.vtk");
        }

        lattice->collideAndStream();

        if (i%particleTimeFactor==0) {
            particles->executeInternalProcessors();
        }
    }

    delete particles;
    delete particleTemplate;
    return 0;
}