├── conanfile.txt ├── .gitignore ├── include ├── helper.h ├── parallel.h ├── io │ ├── vtk.h │ ├── vtk_legacy.hpp │ ├── vtk.hpp │ ├── configuration.h │ └── scenario.h ├── lbmdefinitions.h ├── cell.h ├── domain.h ├── collision.h ├── collision.hpp ├── cell.hpp ├── boundary.h ├── model.h ├── boundary.hpp └── domain.hpp ├── CMakeLists.txt └── src └── main.cpp /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | pugixml/1.10 3 | vtk/8.2.0 4 | boost/1.73.0 5 | 6 | [options] 7 | vtk:shared=True 8 | boost:shared=True 9 | 10 | [imports] 11 | lib, *.so* -> lib 12 | 13 | [generators] 14 | cmake 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # All hidden files, object files, the executable and vtk files 2 | .* 3 | !.gitignore 4 | *.o 5 | *.d 6 | lbm 7 | *.vtk 8 | !pipe.vtk 9 | *.vts 10 | *.pvts 11 | gmon.out 12 | cmake-build-debug 13 | cmake-build-release 14 | build 15 | -------------------------------------------------------------------------------- /include/helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | std::unique_ptr make_unique( Args&& ...args ) 8 | { 9 | return std::unique_ptr( new T( std::forward(args)... ) ); 10 | } 11 | 12 | inline bool file_exists (const std::string& name) { 13 | struct stat buffer; 14 | return (stat (name.c_str(), &buffer) == 0); 15 | } 16 | -------------------------------------------------------------------------------- /include/parallel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "io/configuration.h" 4 | #include "collision.h" 5 | 6 | namespace lbm 7 | { 8 | namespace parallel 9 | { 10 | 11 | // TODO: Not yet implemented 12 | template 13 | class ParallelBoundary : public NonFluidCollision 14 | { 15 | public: 16 | ParallelBoundary(Domain& domain) 17 | : NonFluidCollision(domain) 18 | {} 19 | 20 | void collide(Cell& cell, 21 | const uint_array& position) const override 22 | {} 23 | }; 24 | 25 | }//namespace parallel 26 | }//namespace lbm 27 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(lbm) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 7 | conan_basic_setup(KEEP_RPATHS NO_OUTPUT_DIRS) 8 | 9 | include_directories(include) 10 | include_directories(include/io) 11 | 12 | #include(${VTK_USE_FILE}) 13 | set(HEADERS 14 | include/io/configuration.h 15 | include/io/scenario.h 16 | include/io/vtk.h 17 | include/io/vtk.hpp 18 | include/io/vtk_legacy.hpp 19 | include/boundary.h 20 | include/boundary.hpp 21 | include/cell.h 22 | include/cell.hpp 23 | include/collision.h 24 | include/collision.hpp 25 | include/domain.h 26 | include/domain.hpp 27 | include/helper.h 28 | include/lbmdefinitions.h 29 | include/model.h 30 | include/parallel.h 31 | ) 32 | 33 | set(SOURCES 34 | src/main.cpp 35 | ) 36 | 37 | add_executable(lbm ${HEADERS} ${SOURCES}) 38 | 39 | target_compile_options(lbm PUBLIC -fopenmp) 40 | target_link_libraries(lbm PRIVATE ${CONAN_LIBS}) 41 | target_link_libraries(lbm PRIVATE -fopenmp ${CMAKE_DL_LIBS}) 42 | -------------------------------------------------------------------------------- /include/io/vtk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "domain.h" 4 | #include "boundary.h" 5 | #include "helper.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Since VTK uses some deprecated headers we want to ignore the related warnings while including 14 | // to avoid compiler output flooding 15 | #pragma GCC diagnostic push 16 | // TODO: Add GCC pragma! The following one is not working :-/ 17 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #pragma GCC diagnostic pop 29 | 30 | namespace lbm 31 | { 32 | namespace io 33 | { 34 | 35 | template 36 | void write_vtk_file(const Domain& domain, const std::string& output_dir, 37 | const std::string& output_filename, uint64_t t); 38 | 39 | template 40 | auto read_vtk_point_file(const std::string& filename, 41 | FluidCollision& fluid_collision_model) -> Domain_ptr; 42 | 43 | } //namespace io 44 | } //namespace lbm 45 | 46 | #if defined (LEGACY_WRITER) 47 | #include "vtk_legacy.hpp" 48 | #endif 49 | #include "vtk.hpp" 50 | -------------------------------------------------------------------------------- /include/lbmdefinitions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace lbm 8 | { 9 | 10 | // Lattice model constants 11 | template 12 | using lattice_velocities = std::array, Q>; 13 | template 14 | using lattice_weights = std::array; 15 | 16 | // Array types 17 | template 18 | using double_array = std::array; 19 | template 20 | using int_array = std::array; 21 | template 22 | using uint_array = std::array; 23 | 24 | // PDF fields 25 | template class Cell; 26 | template 27 | using Lattice_field = std::vector>; 28 | 29 | // Pointer types 30 | template class Domain; 31 | template 32 | using Domain_ptr = std::unique_ptr>; 33 | 34 | //template class Collision; 35 | //template 36 | // using Coll_ptr = std::shared_ptr>; 37 | 38 | template class FluidCollision; 39 | template 40 | using FluidColl_ptr = std::shared_ptr>; 41 | 42 | template class NonFluidCollision; 43 | template 44 | using NonFluidColl_ptr = std::shared_ptr>; 45 | 46 | // Constant for 3D lattice speed of sound 47 | static constexpr double C_S = 0.57735026919l; 48 | }//namespace lbm 49 | -------------------------------------------------------------------------------- /include/cell.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "collision.h" 4 | #include 5 | 6 | namespace lbm 7 | { 8 | 9 | template class Collision; 10 | 11 | template 12 | class Cell 13 | { 14 | double_array pdf; 15 | const Collision* collision; 16 | public: 17 | explicit Cell(const Collision* collision); 18 | 19 | /** 20 | * Indicates whether this cell is a fluid cell. 21 | */ 22 | auto is_fluid() const -> bool; 23 | 24 | /** 25 | * Returns i-th distribution function of the current cell. 26 | */ 27 | auto operator[](size_t index) -> double&; 28 | 29 | /** 30 | * Returns i-th distribution function of the current cell (const version) 31 | */ 32 | auto operator[](size_t index) const -> const double&; 33 | 34 | /** 35 | * Performs a collision step for the current cell. 36 | * @lattice_position (x,y,z) position of this cell. 37 | */ 38 | auto collide(const uint_array& lattice_position) -> void; 39 | 40 | /** 41 | * Computes density of this cell. 42 | */ 43 | auto density() const -> double; 44 | 45 | /** 46 | * Computes velocity of this cell. 47 | */ 48 | auto velocity(double density) const -> double_array; 49 | 50 | /** 51 | * Computes equilibrium function of this cell. 52 | */ 53 | auto equilibrium(double density, const double_array& velocity) const 54 | -> double_array; 55 | 56 | /** 57 | * Sets the collision handler for this cell. 58 | * This may be used to apply a boundary condition on this cell. 59 | */ 60 | auto set_collision_handler(const Collision* collision) -> void; 61 | 62 | /** 63 | * Currently installed collision handler. 64 | */ 65 | auto get_collision_handler() const -> decltype(collision); 66 | 67 | auto has_fluid_vicinity(const Domain& domain, 68 | const uint_array& position) const -> bool; 69 | }; 70 | 71 | } //namespace lbm 72 | 73 | #include "cell.hpp" 74 | -------------------------------------------------------------------------------- /include/domain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "lbmdefinitions.h" 5 | 6 | namespace lbm 7 | { 8 | 9 | template 10 | class Domain 11 | { 12 | const FluidCollision* const collision; 13 | Lattice_field collide_field; 14 | Lattice_field stream_field; 15 | const size_t xl { 0 }, yl { 0 }, zl { 0 }; 16 | // Origins 17 | double xo, yo, zo; 18 | // Spacings 19 | double xs, ys, zs; 20 | 21 | public: 22 | Domain(size_t xl, size_t yl, size_t zl, 23 | FluidCollision& _collision, 24 | double xorigin = 0, double yorigin = 0, double zorigin = 0, 25 | double xspacing = 1, double yspacing = 1, double zspacing = 1); 26 | 27 | // Getters 28 | auto xlength() const -> decltype(xl); 29 | auto ylength() const -> decltype(yl); 30 | auto zlength() const -> decltype(zl); 31 | auto xorigin() const -> decltype(xo); 32 | auto yorigin() const -> decltype(yo); 33 | auto zorigin() const -> decltype(zo); 34 | auto xspacing() const -> decltype(xs); 35 | auto yspacing() const -> decltype(ys); 36 | auto zspacing() const -> decltype(zs); 37 | 38 | // Helper functions 39 | auto idx(int x, int y, int z) const -> int; 40 | auto in_bounds(int x, int y, int z) const -> bool; 41 | auto cell(int x, int y, int z) const -> const Cell&; 42 | auto cell(int x, int y, int z) -> Cell&; 43 | auto set_nonfluid_cells_nullcollide() -> void; 44 | auto setBoundaryCondition(NonFluidCollision& condition, 45 | size_t x0, size_t xE, size_t y0, size_t yE, size_t z0, size_t zE) -> void; 46 | 47 | // Iteration functions 48 | auto stream() -> void; 49 | auto collide() -> void; 50 | auto swap() -> void; 51 | 52 | // Parallelization tools 53 | auto create_subdomain(parallel::ParallelBoundary& parallel_boundary, 54 | size_t xstart, size_t xend, 55 | int rank, int number_of_ranks) const -> Domain_ptr; 56 | }; 57 | 58 | } //namespace lbm 59 | 60 | #include "domain.hpp" 61 | -------------------------------------------------------------------------------- /include/collision.h: -------------------------------------------------------------------------------- 1 | // Collision class definition. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | #include "lbmdefinitions.h" 8 | #include "io/configuration.h" 9 | 10 | namespace lbm 11 | { 12 | 13 | template class Cell; 14 | /** 15 | * Base class for all collision types. 16 | */ 17 | template 18 | class Collision 19 | { 20 | public: 21 | auto compute_density(const Cell& cell) const -> double; 22 | auto compute_velocity(const Cell& cell, double density) const 23 | -> double_array; 24 | auto compute_feq(double density, const double_array& velocity) const 25 | -> double_array; 26 | 27 | virtual bool is_fluid() const = 0; 28 | virtual void collide(Cell& cell, 29 | const uint_array& position) const = 0; 30 | virtual ~Collision() {} 31 | }; 32 | 33 | template 34 | class FluidCollision: public Collision 35 | { 36 | public: 37 | bool is_fluid() const override final 38 | { 39 | return true; 40 | } 41 | }; 42 | 43 | template class Domain; 44 | /** 45 | * This class is the base for all collision types modeling boundaries/obstacles 46 | */ 47 | template 48 | class NonFluidCollision: public Collision 49 | { 50 | protected: 51 | Domain& domain; 52 | public: 53 | NonFluidCollision(Domain& domain) : domain (domain) {} 54 | 55 | bool is_fluid() const override final 56 | { 57 | return false; 58 | } 59 | }; 60 | 61 | ////////////////// BGKCollision ////////////////////////// 62 | 63 | template 64 | class BGKCollision: public FluidCollision 65 | { 66 | double tau { 0 }; 67 | public: 68 | explicit BGKCollision(double tau); 69 | void collide(Cell& cell, const uint_array& position) 70 | const override; 71 | }; 72 | 73 | template 74 | class NullCollision : public Collision 75 | { 76 | public: 77 | bool is_fluid() const override final 78 | { 79 | return false; 80 | } 81 | void collide(Cell& cell, const uint_array& position) 82 | const override 83 | { 84 | // Do nothing 85 | } 86 | }; 87 | 88 | } //namespace lbm 89 | 90 | #include "collision.hpp" 91 | -------------------------------------------------------------------------------- /include/collision.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace lbm 4 | { 5 | 6 | template 7 | inline double Collision::compute_density(const Cell& cell) const 8 | { 9 | double density = 0; 10 | for (auto q = 0u; q < lattice_model::Q; ++q) { 11 | density += cell[q]; 12 | } 13 | return density; 14 | } 15 | 16 | template 17 | inline double_array Collision::compute_velocity( 18 | const Cell& cell, double density) const 19 | { 20 | double_array velocity = { 0.0, 0.0, 0.0 }; 21 | // Compute velocity by momentum equation 22 | for (auto d = 0u; d < lattice_model::D; ++d) { 23 | for (auto q = 0u; q < lattice_model::Q; ++q) { 24 | velocity[d] += cell[q] * lattice_model::velocities[q][d]; 25 | } 26 | } 27 | velocity[0] /= density; 28 | velocity[1] /= density; 29 | velocity[2] /= density; 30 | return velocity; 31 | } 32 | 33 | template 34 | double_array Collision::compute_feq(double density, 35 | const double_array& velocity) const 36 | { 37 | double_array feq; 38 | // Compute equilibrium distributions for the current velocity/density 39 | for (auto q = 0u; q < lattice_model::Q; ++q) { 40 | double c_dot_u = 0.0; 41 | double u_dot_u = 0.0; 42 | for (auto d = 0u; d < lattice_model::D; ++d) { 43 | c_dot_u += lattice_model::velocities[q][d] * velocity[d]; 44 | u_dot_u += velocity[d] * velocity[d]; 45 | } 46 | feq[q] = lattice_model::weights[q] * density 47 | * (1 + c_dot_u / (C_S * C_S) + (c_dot_u) * (c_dot_u) / (2 * C_S * C_S * C_S * C_S) 48 | - u_dot_u / (2 * C_S * C_S)); 49 | } 50 | return feq; 51 | } 52 | 53 | ///////////////////////////////////// BGKCollision ///////////////////////////////////////// 54 | template 55 | BGKCollision::BGKCollision(double tau) : 56 | tau { tau } 57 | { 58 | } 59 | 60 | template 61 | inline auto BGKCollision::collide(Cell& cell, 62 | const uint_array& position) const -> void 63 | { 64 | const auto density = this->compute_density(cell); 65 | const auto velocity = this->compute_velocity(cell, density); 66 | const auto feq = this->compute_feq(density, velocity); 67 | for (auto q = 0u; q < lattice_model::Q; ++q) { 68 | cell[q] -= (cell[q] - feq[q]) / tau; 69 | } 70 | } 71 | 72 | } //namespace lbm 73 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "model.h" 2 | 3 | #define D3Q 19 4 | 5 | #if D3Q == 19 6 | using model = lbm::model::d3q19; 7 | #elif D3Q == 15 8 | using model = lbm::model::d3q15; 9 | #elif D3Q == 27 10 | using model = lbm::model::d3q27; 11 | #else 12 | #error "Set lattice model via -DD3Q=15, -DD3Q=19 or -DD3Q=27" 13 | #endif 14 | 15 | //#include 16 | #include 17 | #include 18 | 19 | #include "parallel.h" 20 | #include "lbmdefinitions.h" 21 | #include "helper.h" 22 | #include "collision.h" 23 | #include "boundary.h" 24 | #include "cell.h" 25 | #include "domain.h" 26 | #include "io/configuration.h" 27 | #include "io/vtk.h" 28 | #include "io/scenario.h" 29 | 30 | int main(int argc, char** argv) 31 | { 32 | try { 33 | // Read configuration file 34 | lbm::io::Config cfg(argc, argv); 35 | // Allocate collision operator 36 | auto collision = lbm::BGKCollision(cfg.tau()); 37 | auto domain = lbm::io::parse_scenario_file(cfg.scenario_xml(), cfg, collision); 38 | // Print configuration file values and domain lengths 39 | std::cout << cfg << std::endl; 40 | std::cout << "> Domain lengths (x,y,z): " << domain->xlength() << ", " << domain->ylength() 41 | << ", " << domain->zlength() << std::endl; 42 | std::cout << "Starting simulation..." << std::endl; 43 | 44 | domain->set_nonfluid_cells_nullcollide(); 45 | // Run simulation 46 | double mlups_duration = 0.0; 47 | double walltime = omp_get_wtime(); 48 | for (auto t = 1u; t <= cfg.timesteps(); ++t) { 49 | double start = omp_get_wtime(); 50 | domain->stream(); 51 | domain->swap(); 52 | domain->collide(); 53 | mlups_duration += omp_get_wtime() - start; 54 | 55 | // Output file only when timesteps-per-plot was set to != 0 56 | if (cfg.timesteps_per_plot() && t % cfg.timesteps_per_plot() == 0) 57 | lbm::io::write_vtk_file(*domain, cfg.output_dir(), cfg.output_filename(), t); 58 | // Print percents completed 59 | std::cout << "\r" << (int) ((double) t / cfg.timesteps() * 100) << " %"; 60 | std::cout.flush(); 61 | } 62 | std::cout << "\nFinished!" << std::endl; 63 | std::cout << "Total runtime: " << omp_get_wtime()-walltime << " seconds." << std::endl; 64 | double mlups = (2+domain->xlength())*(2+domain->ylength())*(2+domain->zlength()) 65 | *cfg.timesteps()/(mlups_duration*1e6); 66 | std::cout << "MLUPS: " << mlups << std::endl; 67 | } 68 | catch (const std::exception& ex) { 69 | std::cerr << "An error occured: " << ex.what() << std::endl; 70 | return EXIT_FAILURE; 71 | } 72 | return EXIT_SUCCESS; 73 | } 74 | -------------------------------------------------------------------------------- /include/cell.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace lbm 6 | { 7 | 8 | template 9 | inline Cell::Cell(const Collision* collision) 10 | : collision { collision } 11 | { 12 | std::copy(std::begin(lattice_model::weights), 13 | std::end(lattice_model::weights), 14 | std::begin(pdf)); 15 | } 16 | 17 | template 18 | inline auto Cell::is_fluid() const -> bool 19 | { 20 | return collision->is_fluid(); 21 | } 22 | 23 | template 24 | inline auto Cell::operator[](size_t index) const -> const double& 25 | { 26 | return pdf[index]; 27 | } 28 | 29 | template 30 | inline auto Cell::operator[](size_t index) -> double& 31 | { 32 | return pdf[index]; 33 | } 34 | 35 | template 36 | inline auto Cell::collide(const uint_array& lattice_position) 37 | -> void 38 | { 39 | collision->collide(*this, lattice_position); 40 | } 41 | 42 | template 43 | inline auto Cell::density() const -> double 44 | { 45 | return collision->compute_density(*this); 46 | } 47 | 48 | template 49 | inline auto Cell::velocity(double density) const -> double_array 50 | { 51 | return collision->compute_velocity(*this, density); 52 | } 53 | 54 | template 55 | inline auto Cell::equilibrium(double density, 56 | const double_array& velocity) const -> double_array 57 | { 58 | return collision->compute_feq(density, velocity); 59 | } 60 | 61 | template 62 | inline auto Cell::set_collision_handler(const Collision* collision) 63 | -> void 64 | { 65 | this->collision = collision; 66 | } 67 | 68 | template 69 | inline auto Cell::get_collision_handler() const -> decltype(collision) 70 | { 71 | return collision; 72 | } 73 | 74 | template 75 | auto Cell::has_fluid_vicinity(const Domain& domain, 76 | const uint_array& position) const -> bool 77 | { 78 | const auto x = position[0]; 79 | const auto y = position[1]; 80 | const auto z = position[2]; 81 | 82 | for (auto q = 0u; q < lattice_model::Q; ++q) { 83 | const auto dx = lattice_model::velocities[q][0]; 84 | const auto dy = lattice_model::velocities[q][1]; 85 | const auto dz = lattice_model::velocities[q][2]; 86 | if (domain.in_bounds(x + dx, y + dy, z + dz) 87 | && domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 88 | return true; 89 | } 90 | } 91 | return false; 92 | } 93 | 94 | }//namespace lbm 95 | -------------------------------------------------------------------------------- /include/boundary.h: -------------------------------------------------------------------------------- 1 | // Contains all boundary-related collision types 2 | #pragma once 3 | 4 | #include "collision.h" 5 | 6 | namespace lbm 7 | { 8 | 9 | // No-slip/bounce back boundary 10 | template 11 | class NoSlipBoundary: public NonFluidCollision 12 | { 13 | public: 14 | NoSlipBoundary(Domain& domain); 15 | void collide(Cell& cell, const uint_array& position) 16 | const override; 17 | }; 18 | 19 | template 20 | class MovingWallBoundary: public NonFluidCollision 21 | { 22 | double_array wall_velocity; 23 | public: 24 | MovingWallBoundary(Domain& domain, 25 | const double_array& wall_velocity); 26 | void collide(Cell& cell, const uint_array& position) 27 | const override; 28 | 29 | }; 30 | 31 | template 32 | class FreeSlipBoundary: public NonFluidCollision 33 | { 34 | public: 35 | FreeSlipBoundary(Domain& domain); 36 | void collide(Cell& cell, const uint_array& position) 37 | const override; 38 | }; 39 | 40 | template 41 | class OutflowBoundary: public NonFluidCollision 42 | { 43 | double reference_density { 0.0 }; 44 | public: 45 | OutflowBoundary(Domain& domain, double reference_density = 1.0); 46 | void collide(Cell& cell, const uint_array& position) 47 | const override; 48 | }; 49 | 50 | template 51 | class InflowBoundary: public NonFluidCollision 52 | { 53 | double reference_density { 0.0 }; 54 | double_array inflow_velocity; 55 | public: 56 | InflowBoundary(Domain& domain, 57 | const double_array& inflow_velocity, double reference_density = 1.0); 58 | void collide(Cell& cell, const uint_array& position) 59 | const override; 60 | }; 61 | 62 | template 63 | class PressureBoundary: public NonFluidCollision 64 | { 65 | double input_density; 66 | public: 67 | PressureBoundary(Domain& domain, double input_density); 68 | void collide(Cell& cell, const uint_array& position) 69 | const override; 70 | }; 71 | 72 | template 73 | class BoundaryKeeper 74 | { 75 | static std::list>> boundary_conditions; 76 | public: 77 | template 78 | static auto get_collision(Args&& ...args) -> NonFluidCollision&; 79 | }; 80 | 81 | template 82 | std::list>> 83 | BoundaryKeeper::boundary_conditions; 84 | 85 | template 86 | template 87 | auto BoundaryKeeper::get_collision(Args&& ...args) -> NonFluidCollision& 88 | { 89 | boundary_conditions.push_back(std::make_unique(args...)); 90 | return *boundary_conditions.back(); 91 | } 92 | 93 | } //namespace lbm 94 | 95 | #include "boundary.hpp" 96 | -------------------------------------------------------------------------------- /include/model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "lbmdefinitions.h" 5 | 6 | namespace lbm 7 | { 8 | 9 | namespace model 10 | { 11 | 12 | //////////////////// D3Q15 //////////////////// 13 | struct d3q15 14 | { 15 | static constexpr size_t D = 3; 16 | static constexpr size_t Q = 15; 17 | static constexpr const char* const name = "D3Q15"; 18 | 19 | // D3Q15 velocities 20 | // static constexpr lattice_velocities velocities 21 | static constexpr double velocities[Q][D] 22 | { 23 | { -1, -1, -1 }, { 1, -1, -1 }, { 0, 0, -1 }, { -1, 1, -1 }, { 1, 1, -1 }, 24 | { 0, -1, 0 }, { -1, 0, 0 }, { 0, 0, 0 }, { 1, 0, 0 }, { 0, 1, 0 }, 25 | { -1, -1, 1 }, { 1, -1, 1 }, { 0, 0, 1 }, { -1, 1, 1 }, { 1, 1, 1 } 26 | }; 27 | 28 | // D3Q15 weights 29 | static constexpr lattice_weights weights 30 | {{ 31 | 1.0/72, 1.0/72, 8.0/72, 1.0/72, 1.0/72, 32 | 8.0/72, 8.0/72, 16.0/72, 8.0/72, 8.0/72, 33 | 1.0/72, 1.0/72, 8.0/72, 1.0/72, 1.0/72 34 | }}; 35 | 36 | static int inv(int q) 37 | { 38 | return Q-1-q; 39 | } 40 | 41 | static size_t velocity_index(int u, int v, int w) 42 | { 43 | return 5*w + 2*v + u + 7 - (w+2)%2 * (u+v)/2; 44 | } 45 | }; 46 | 47 | // Define static arrays 48 | //constexpr lattice_velocities d3q15::velocities; 49 | constexpr double d3q15::velocities[Q][D]; 50 | constexpr lattice_weights d3q15::weights; 51 | 52 | //////////////////// D3Q19 //////////////////// 53 | struct d3q19 54 | { 55 | static constexpr size_t D {3}; 56 | static constexpr size_t Q {19}; 57 | static constexpr const char* const name = "D3Q19"; 58 | // D3Q19 velocities 59 | // static constexpr lattice_velocities velocities 60 | static constexpr double velocities[Q][D] 61 | { 62 | { 0, -1, -1 }, { -1, 0, -1 }, { 0, 0, -1 }, { 1, 0, -1 }, { 0, 1, -1 }, 63 | { -1, -1, 0 }, { 0, -1, 0 }, { 1, -1, 0 }, { -1, 0, 0 }, { 0, 0, 0 }, 64 | { 1, 0, 0 }, { -1, 1, 0 }, { 0, 1, 0 }, { 1, 1, 0 }, { 0, -1, 1 }, 65 | { -1, 0, 1 }, { 0, 0, 1 }, { 1, 0, 1 }, { 0, 1, 1 } 66 | }; 67 | 68 | // D3Q19 weights 69 | static constexpr lattice_weights weights 70 | {{ 71 | 1.0/36, 1.0/36, 2.0/36, 1.0/36, 1.0/36, 72 | 1.0/36, 2.0/36, 1.0/36, 2.0/36, 12.0/36, 73 | 2.0/36, 1.0/36, 2.0/36, 1.0/36, 1.0/36, 74 | 1.0/36, 2.0/36, 1.0/36, 1.0/36 75 | }}; 76 | 77 | static int inv(int q) 78 | { 79 | return Q-1-q; 80 | } 81 | 82 | static size_t velocity_index(int u, int v, int w) 83 | { 84 | return w == 0 ? (6 + (v + 1) * 3 + u) : ((w + 1) * 7 + (v + 1) * 2 + u); 85 | } 86 | }; 87 | 88 | // Define static arrays 89 | //constexpr lattice_velocities d3q19::velocities; 90 | constexpr double d3q19::velocities[Q][D]; 91 | constexpr lattice_weights d3q19::weights; 92 | //constexpr size_t d3q19::RIGHT_PDFS[]; 93 | //constexpr size_t d3q19::LEFT_PDFS[]; 94 | 95 | //////////////////// D3Q27 //////////////////// 96 | struct d3q27 97 | { 98 | static constexpr size_t D = 3; 99 | static constexpr size_t Q = 27; 100 | static constexpr const char* const name = "D3Q27"; 101 | // D3Q27 velocities 102 | // static constexpr lattice_velocities velocities 103 | static constexpr double velocities[Q][D] 104 | { 105 | { -1, -1, -1 }, { 0, -1, -1 }, { 1, -1, -1 }, { -1, 0, -1 }, { 0, 0, -1 }, 106 | { 1, 0, -1 }, { -1, 1, -1 }, { 0, 1, -1 }, { 1, 1, -1 }, { -1, -1, 0 }, 107 | { 0, -1, 0 }, { 1, -1, 0 }, { -1, 0, 0 }, { 0, 0, 0 }, { 1, 0, 0 }, 108 | { -1, 1, 0 }, { 0, 1, 0 }, { 1, 1, 0 }, { -1, -1, 1 }, { 0, -1, 1 }, 109 | { 1, -1, 1 }, { -1, 0, 1 }, { 0, 0, 1 }, { 1, 0, 1 }, { -1, 1, 1 }, 110 | { 0, 1, 1 }, { 1, 1, 1 } 111 | }; 112 | 113 | // D3Q27 weights 114 | static constexpr lattice_weights weights 115 | {{ 116 | 1.0/216, 4.0/216, 1.0/216, 4.0/216, 16.0/216, 117 | 4.0/216, 1.0/216, 4.0/216, 1.0/216, 4.0/216, 118 | 16.0/216, 4.0/216, 16.0/216, 64.0/216, 16.0/216, 119 | 4.0/216, 16.0/216, 4.0/216, 1.0/216, 4.0/216, 120 | 1.0/216, 4.0/216, 16.0/216, 4.0/216, 1.0/216, 121 | 4.0/216, 1.0/216 122 | }}; 123 | 124 | static int inv(int q) 125 | { 126 | return Q-1-q; 127 | } 128 | 129 | // TODO: Implement 130 | static size_t velocity_index(int u, int v, int w) 131 | { 132 | return 9*w + 3*v + u + 13; 133 | } 134 | }; 135 | 136 | // Define static arrays 137 | //constexpr lattice_velocities d3q27::velocities; 138 | constexpr double d3q27::velocities[Q][D]; 139 | constexpr lattice_weights d3q27::weights; 140 | 141 | }//namespace model 142 | }//namespace lbm 143 | -------------------------------------------------------------------------------- /include/io/vtk_legacy.hpp: -------------------------------------------------------------------------------- 1 | #if defined (LEGACY_WRITER) 2 | 3 | namespace lbm 4 | { 5 | namespace io 6 | { 7 | 8 | namespace 9 | { 10 | template 11 | std::string compute_coordinates(const Domain& domain) 12 | { 13 | auto xl = domain.xlength(); 14 | auto yl = domain.ylength(); 15 | auto zl = domain.zlength(); 16 | /* Print lattice points */ 17 | double xs = domain.xspacing(); 18 | double ys = domain.yspacing(); 19 | double zs = domain.zspacing(); 20 | double xo = domain.xorigin(); 21 | double yo = domain.yorigin(); 22 | double zo = domain.zorigin(); 23 | 24 | std::ostringstream str; 25 | for (auto z = 1u; z < zl + 1; ++z) { 26 | for (auto y = 1u; y < yl + 1; ++y) { 27 | for (auto x = 1u; x < xl + 1; ++x) { 28 | str << xs*x+xo-1 << " " << ys*y+yo-1 << " " << zs*z+zo-1 << "\n"; 29 | } 30 | } 31 | } 32 | return str.str(); 33 | } 34 | 35 | }//anonymous namespace 36 | 37 | 38 | #define VTS 1 39 | template 40 | void write_vtk_file(const Domain& domain, const std::string& output_dir, 41 | const std::string& output_filename, uint64_t t) 42 | { 43 | auto xl = domain.xlength(); 44 | auto yl = domain.ylength(); 45 | auto zl = domain.zlength(); 46 | // TODO: The following only holds for non-MPI executions. Check how to distinguish 47 | // Print lattice points. Since these are constant, we only need to compute them once. 48 | static const auto coordinates = compute_coordinates(domain); 49 | /* Save filename as a combination of passed filename and timestep */ 50 | std::stringstream sstr; 51 | sstr << output_dir << "/" << output_filename << "." << t; 52 | #if VTS 53 | sstr << ".vts"; 54 | #else 55 | sstr << ".vtk"; 56 | #endif 57 | std::ofstream file(sstr.str(), std::ios::trunc); 58 | 59 | if (!file) { 60 | std::cerr << "Failed to open file!" << std::endl; 61 | std::exit (EXIT_FAILURE); 62 | } 63 | #if VTS 64 | file << "\n"; 65 | file << "\n"; 66 | file << " \n"; 68 | file << " \n"; 69 | file << " \n"; 70 | /* ====================== Velocity ===================== */ 71 | file << " \n"; 72 | #else 73 | file << "# vtk DataFile Version 2.0\n"; 74 | file << "generated by CFD-lab course output\n"; 75 | file << "ASCII\n"; 76 | file << "\n"; 77 | file << "DATASET STRUCTURED_GRID\n"; 78 | file << "DIMENSIONS " << xl << ' ' << yl << ' ' << zl << "\n"; 79 | file << "\nPOINTS " << (xl)*(yl)*(zl) << " float\n"; 80 | file << coordinates << '\n'; 81 | file << "POINT_DATA " << (xl )*(yl)*(zl ) << " \n"; 82 | file << "\n"; 83 | file << "VECTORS velocity float\n"; 84 | file << "\n"; 85 | #endif 86 | /* Compute (macroscopic) velocities for all cells */ 87 | std::vector density(xl*yl*zl); 88 | auto index = 0u; 89 | #pragma omp for collapse(3) 90 | for (auto z = 1u; z < zl + 1; ++z) { 91 | for (auto y = 1u; y < yl + 1; ++y) { 92 | for (auto x = 1u; x < xl + 1; ++x) { 93 | auto current_cell = domain.cell(x, y, z); 94 | density[index] = current_cell.density(); 95 | auto vel = current_cell.velocity(density[index]); 96 | file << vel[0] << " " << vel[1] << " " << vel[2] << "\n"; 97 | ++index; 98 | } 99 | } 100 | } 101 | #if VTS 102 | file << " \n"; 103 | /* ====================== Density ===================== */ 104 | file << " \n"; 105 | #else 106 | file << "\n"; 107 | file << "SCALARS density float 1\n"; 108 | file << "LOOKUP_TABLE default \n"; 109 | #endif 110 | /* Output density for each cell */ 111 | for (auto i = 0u; i < density.size(); ++i) { 112 | file << density[i] << '\n'; 113 | } 114 | #if VTS 115 | file << " \n"; 116 | file << " \n"; 117 | file << " \n"; 118 | file << " \n"; 119 | /* ====================== Lattice points ===================== */ 120 | file 121 | << " \n"; 122 | file << coordinates; 123 | file << " \n"; 124 | file << " \n"; 125 | file << " \n"; 126 | file << " \n"; 127 | file << "\n"; 128 | #endif 129 | file.flush(); 130 | file.close(); 131 | if (!file) { 132 | std::cerr << "Failed to close file!" << std::endl; 133 | std::exit (EXIT_FAILURE); 134 | } 135 | // std::cout << "All elements written to \"" << sstr.str() << "\"." << std::endl; 136 | } 137 | 138 | }//Namespace io 139 | }//Namespace lbm 140 | 141 | #endif // LEGACY_WRITER 142 | -------------------------------------------------------------------------------- /include/io/vtk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace lbm 7 | { 8 | namespace io 9 | { 10 | #if !defined (LEGACY_WRITER) 11 | 12 | namespace 13 | { 14 | 15 | template 16 | void compute_coordinates(const Domain& domain, 17 | vtkSmartPointer& points) 18 | { 19 | const auto xl = domain.xlength(); 20 | const auto yl = domain.ylength(); 21 | const auto zl = domain.zlength(); 22 | const auto xs = domain.xspacing(); 23 | const auto ys = domain.yspacing(); 24 | const auto zs = domain.zspacing(); 25 | const auto xo = domain.xorigin(); 26 | const auto yo = domain.yorigin(); 27 | const auto zo = domain.zorigin(); 28 | 29 | /* Write lattice coordinates */ 30 | for (auto z = 1u; z < zl + 1; ++z) { 31 | for (auto y = 1u; y < yl + 1; ++y) { 32 | for (auto x = 1u; x < xl + 1; ++x) { 33 | points->InsertNextPoint(xs*x+xo-1, ys*y+yo-1, zs*z+zo-1); 34 | } 35 | } 36 | } 37 | } 38 | 39 | }//namespace 40 | 41 | template 42 | void write_vtk_file(const Domain& domain, const std::string& output_dir, 43 | const std::string& output_filename, uint64_t t) 44 | { 45 | const auto xl = domain.xlength(); 46 | const auto yl = domain.ylength(); 47 | const auto zl = domain.zlength(); 48 | // Compute point coordinates 49 | auto points = vtkSmartPointer::New(); 50 | compute_coordinates(domain, points); 51 | 52 | // Compute velocity and density vectors 53 | auto velocities = vtkSmartPointer::New(); 54 | auto densities = vtkSmartPointer::New(); 55 | 56 | velocities->SetNumberOfComponents(lattice_model::D); 57 | densities->SetNumberOfComponents(1); 58 | 59 | velocities->SetName("Velocity"); 60 | densities->SetName("Density"); 61 | 62 | for (auto z = 1u; z < zl + 1; ++z) { 63 | for (auto y = 1u; y < yl + 1; ++y) { 64 | for (auto x = 1u; x < xl + 1; ++x) { 65 | auto current_cell = domain.cell(x, y, z); 66 | auto density = current_cell.density(); 67 | auto vel = current_cell.velocity(density); 68 | 69 | densities->InsertNextTuple1(density); 70 | velocities->InsertNextTuple3(vel[0], vel[1], vel[2]); 71 | } 72 | } 73 | } 74 | 75 | // Create a grid and write coordinates and velocity/density 76 | auto structuredGrid = vtkSmartPointer::New(); 77 | structuredGrid->SetDimensions(xl, yl, zl); 78 | structuredGrid->SetPoints(points); 79 | structuredGrid->GetPointData()->SetVectors(velocities); 80 | structuredGrid->GetPointData()->SetScalars(densities); 81 | // Save filename as a combination of passed filename and timestep 82 | std::stringstream sstr; 83 | sstr << output_dir << "/" << output_filename << "." << t << ".vts"; 84 | // Write file 85 | auto writer = vtkSmartPointer::New(); 86 | writer->SetFileName(sstr.str().c_str()); 87 | writer->SetInputData(structuredGrid); 88 | writer->Write(); 89 | } 90 | 91 | #endif //!LEGACY_WRITER 92 | 93 | template 94 | auto read_vtk_point_file(const std::string& filename, 95 | FluidCollision& fluid_collision_model) -> Domain_ptr 96 | { 97 | vtkObject::GlobalWarningDisplayOff(); 98 | auto reader = vtkSmartPointer::New(); 99 | 100 | reader->SetFileName(filename.c_str()); 101 | reader->Update(); 102 | 103 | if (!reader->IsFileValid("structured_points")) 104 | throw std::logic_error("VTK file \"" + filename + "\" does not exist or does not " 105 | "seem to be a valid structured grids file!"); 106 | 107 | auto output = reader->GetOutput(); 108 | if (output == nullptr) { 109 | // TODO: Improve exception handling 110 | throw std::logic_error("Could not read file!"); 111 | } 112 | output->Register(reader); 113 | 114 | // Now check for point data 115 | auto dataset = reader->GetOutput(); 116 | auto pd = dataset->GetPointData(); 117 | 118 | Domain_ptr domain; 119 | if (pd != nullptr) { 120 | auto p = dynamic_cast(dataset); 121 | 122 | // Read Dimensions 123 | assert(p->GetDataDimension() == 3); 124 | const auto xl = p->GetDimensions()[0]; 125 | const auto yl = p->GetDimensions()[1]; 126 | const auto zl = p->GetDimensions()[2]; 127 | // Read Origins 128 | double ox, oy, oz; 129 | p->GetOrigin(ox, oy, oz); 130 | // Read Spacing 131 | double sx, sy, sz; 132 | p->GetSpacing(sx, sy, sz); 133 | // Create Domain 134 | domain = make_unique>(xl, yl, zl, fluid_collision_model, ox, oy, oz, sx, sy, sz); 135 | // Get domain points 136 | const auto points = dynamic_cast(pd->GetArray(0)); 137 | static auto solid_collision = solid_collision_model(*domain); 138 | // Read only inner cells since we need to apply boundary conditions to the outer cells 139 | // Of course, we read the cells in a linear manner 140 | int index = 0; 141 | for (int z = 1; z < zl + 1; ++z) { 142 | for (int y = 1; y < yl + 1; ++y) { 143 | for (int x = 1; x < xl + 1; ++x) { 144 | const bool fluid = points->GetValue(index++); 145 | if (!fluid) 146 | domain->cell(x, y, z).set_collision_handler(&solid_collision); 147 | } 148 | } 149 | } 150 | } 151 | else { 152 | dataset->Delete(); 153 | throw std::logic_error("Point data is null!"); 154 | } 155 | dataset->Delete(); 156 | return std::move(domain); 157 | } 158 | 159 | } //namespace output 160 | } //namespace lbm 161 | -------------------------------------------------------------------------------- /include/io/configuration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace lbm 9 | { 10 | namespace io 11 | { 12 | namespace po = boost::program_options; 13 | 14 | class Config 15 | { 16 | std::string _collision_model; 17 | std::string _input_file; 18 | std::string _output_dir { "vtk" }; 19 | std::string _output_filename { "output" }; 20 | std::string _scenario_xml; 21 | uint64_t _timesteps { 0 }; 22 | uint64_t _timesteps_per_plot { 0 }; 23 | double _tau { 1.0 }; 24 | uint32_t _omp_threads { 1 }; 25 | 26 | public: 27 | auto collision_model() const -> decltype(_collision_model) 28 | { 29 | return _collision_model; 30 | } 31 | auto input_file() const -> decltype(_input_file) 32 | { 33 | return _input_file; 34 | } 35 | auto output_dir() const -> decltype(_output_dir) 36 | { 37 | return _output_dir; 38 | } 39 | auto output_filename() const -> decltype(_output_filename) 40 | { 41 | return _output_filename; 42 | } 43 | void set_output_filename(const std::string& filename) 44 | { 45 | _output_filename = filename; 46 | } 47 | auto timesteps() const -> decltype(_timesteps) 48 | { 49 | return _timesteps; 50 | } 51 | auto timesteps_per_plot() const -> decltype(_timesteps_per_plot) 52 | { 53 | return _timesteps_per_plot; 54 | } 55 | auto tau() const -> decltype(_tau) 56 | { 57 | return _tau; 58 | } 59 | auto omp_threads() const -> decltype(_omp_threads) 60 | { 61 | return _omp_threads; 62 | } 63 | auto scenario_xml() const -> decltype(_scenario_xml) 64 | { 65 | return _scenario_xml; 66 | } 67 | 68 | Config(int argc, char** argv) 69 | { 70 | // General options 71 | po::options_description general("General options"); 72 | general.add_options() 73 | ("help,h", "Show help message") 74 | ("input-file,i", po::value(&_input_file), 75 | "Input file containing configuration values"); 76 | 77 | // Configuration options 78 | po::options_description config("Configuration options"); 79 | config.add_options() 80 | ("collision-model,c", po::value(&_collision_model)->default_value("bgk"), 81 | "Collision operator model") 82 | ("tau", po::value(&_tau)->required(), 83 | "Relaxation factor for BGK collision operator. Must be in (0.5, 2.0)") 84 | ("timesteps,t", po::value(&_timesteps)->required(), 85 | "Number of steps to perform") 86 | ("timesteps-per-plot", po::value(&_timesteps_per_plot)->required(), 87 | "Number of timesteps after which an output file is written") 88 | ("scenario-file", po::value(&_scenario_xml)->required(), 89 | "XML file containing scenario to simulate") 90 | ("omp-threads", po::value(&_omp_threads), 91 | "Number of OpenMP threads to use") 92 | ("output-dir", po::value(&_output_dir), 93 | "Output directory for plots") 94 | ;// End of options 95 | // Store command line options of general and config options 96 | po::options_description args; 97 | args.add(general).add(config); 98 | 99 | // Positional options: One Filename 100 | po::positional_options_description positional; 101 | positional.add("input-file", 1); 102 | 103 | // Store options in map 104 | po::variables_map vm; 105 | po::store(po::command_line_parser(argc, argv).options(args) 106 | .allow_unregistered().positional(positional).run(), vm); 107 | 108 | std::cout << "LBM simulation by Krivokapic, Mody, Malcher" << std::endl; 109 | if (vm.count("help") || argc == 1) { 110 | std::cout << "Usage: lbm_isotropy [file] additional-options..." << std::endl; 111 | std::cout << general << std::endl; 112 | std::cout << config << std::endl; 113 | std::exit(1); 114 | } 115 | if (vm.count("input-file")) { 116 | std::cout << "Reading configuration file..." << std::endl; 117 | _input_file = vm["input-file"].as(); 118 | po::store(po::parse_config_file(_input_file.c_str(), config), vm); 119 | po::notify(vm); 120 | } 121 | po::notify(vm); 122 | 123 | assert(_timesteps > 0); 124 | assert(_tau > 0.5 && _tau < 2.0); 125 | assert(_omp_threads > 0); 126 | // TODO: Currently only bgk is supported. Remove this assert to enable support for 127 | // further collision operators (e.g. mrt) 128 | assert(_collision_model == "bgk"); 129 | omp_set_num_threads(_omp_threads); 130 | 131 | namespace fs = boost::filesystem; 132 | fs::path dir(_output_dir); 133 | if (!fs::exists(dir)) { 134 | // Create directory 135 | std::cout << "Output directory \"" + _output_dir + "\" does not exist. Creating." 136 | << std::endl; 137 | fs::create_directory(dir); 138 | } else { 139 | // Clean existing directory 140 | fs::directory_iterator end; 141 | for (fs::directory_iterator file(dir); file != end; ++file) { 142 | fs::remove(file->path()); 143 | } 144 | } 145 | } 146 | }; 147 | 148 | auto operator <<(std::ostream& lhs, const lbm::io::Config& cfg) -> decltype(lhs) 149 | { 150 | lhs << "Configuration:" << '\n' 151 | << "> Configuration file: " << cfg.input_file() << '\n' 152 | << "> Output directory: " << cfg.output_dir() << '\n' 153 | << "> Output filename: " << cfg.output_filename() << '\n' 154 | << "> Number of OMP threads: " << omp_get_max_threads() << '\n' 155 | << "> Collision model: " << cfg.collision_model() << '\n' 156 | << "> Tau: " << cfg.tau() << '\n' 157 | << "> Timesteps: " << cfg.timesteps() << '\n' 158 | << "> Timesteps per plot: " << cfg.timesteps_per_plot() << '\n' 159 | << "> Scenario file: " << cfg.scenario_xml(); 160 | return lhs; 161 | } 162 | 163 | } //namespace io 164 | } //namespace lbm 165 | -------------------------------------------------------------------------------- /include/io/scenario.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "domain.h" 5 | #include "vtk.h" 6 | #include "boundary.h" 7 | #include "configuration.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace lbm 17 | { 18 | namespace io 19 | { 20 | 21 | inline void check_attribute(const pugi::xml_node& node, const std::string& name) 22 | { 23 | if (!node.attribute(name.c_str())) 24 | throw std::logic_error("Missing attribute \"" + name + 25 | "\" for node \"" + node.name() + "\"!"); 26 | } 27 | 28 | template 29 | auto parse_condition(const pugi::xml_node& boundary, Domain& domain) 30 | -> NonFluidCollision& 31 | { 32 | using BF = BoundaryKeeper; 33 | check_attribute(boundary, "condition"); 34 | 35 | const std::string condition = boundary.attribute("condition").value(); 36 | if (condition == "noslip") { 37 | return BF::template get_collision>(domain); 38 | } 39 | else if (condition == "movingwall") { 40 | //wallvelocity[3] 41 | check_attribute(boundary, "vx"); 42 | check_attribute(boundary, "vy"); 43 | check_attribute(boundary, "vz"); 44 | const lbm::double_array wall_velocity = { 45 | boundary.attribute("vx").as_double(), 46 | boundary.attribute("vy").as_double(), 47 | boundary.attribute("vz").as_double() 48 | }; 49 | return BF::template get_collision>(domain, wall_velocity); 50 | } 51 | else if (condition == "freeslip") { 52 | return BF::template get_collision>(domain); 53 | } 54 | else if (condition == "outflow") { 55 | double rho_ref = 1.0; 56 | 57 | //Ref density optional 58 | const auto xml_rho_ref = boundary.attribute("rho-ref"); 59 | if (xml_rho_ref) 60 | rho_ref = xml_rho_ref.as_double(); 61 | return BF::template get_collision>(domain, rho_ref); 62 | } 63 | else if (condition == "inflow") { 64 | //inflow velocity[3] 65 | check_attribute(boundary, "vx"); 66 | check_attribute(boundary, "vy"); 67 | check_attribute(boundary, "vz"); 68 | const lbm::double_array inflow_velocity = { 69 | boundary.attribute("vx").as_double(), 70 | boundary.attribute("vy").as_double(), 71 | boundary.attribute("vz").as_double() 72 | }; 73 | //Ref dens optional 74 | double rho_ref = 1.0; 75 | const auto xml_rho_ref = boundary.attribute("rho-ref"); 76 | if (xml_rho_ref) 77 | rho_ref = xml_rho_ref.as_double(); 78 | return BF::template get_collision>( 79 | domain, inflow_velocity, rho_ref); 80 | } 81 | else if (condition == "pressure") { 82 | //input density 83 | check_attribute(boundary, "rho-in"); 84 | double rho_in = boundary.attribute("rho-in").as_double(); 85 | return BF::template get_collision>(domain, rho_in); 86 | } 87 | throw std::logic_error(condition + " boundary condition not supported!"); 88 | } 89 | 90 | template 91 | void parse_boundary(const pugi::xml_node& boundary, Domain& domain) 92 | { 93 | check_attribute(boundary, "extent"); 94 | const std::string extent = boundary.attribute("extent").value(); 95 | auto& condition = parse_condition(boundary, domain); 96 | const auto xl = domain.xlength(); 97 | const auto yl = domain.ylength(); 98 | const auto zl = domain.zlength(); 99 | if (extent == "z0") { 100 | domain.setBoundaryCondition(condition, 0, xl+1, 0, yl+1, 0, 0); 101 | } 102 | else if (extent == "zmax") { 103 | domain.setBoundaryCondition(condition, 0, xl+1, 0, yl+1, zl+1, zl+1); 104 | } 105 | else if (extent == "x0") { 106 | domain.setBoundaryCondition(condition, 0, 0, 0, yl+1, 0, zl+1); 107 | } 108 | else if (extent == "xmax") { 109 | domain.setBoundaryCondition(condition, xl+1, xl+1, 0, yl+1, 0, zl+1); 110 | } 111 | else if (extent == "y0") { 112 | domain.setBoundaryCondition(condition, 0, xl+1, 0, 0, 0, zl+1); 113 | } 114 | else if (extent == "ymax") { 115 | domain.setBoundaryCondition(condition, 0, xl+1, yl+1, yl+1, 0, zl+1); 116 | } 117 | else { 118 | std::vector ex; 119 | boost::char_separator sep(" "); 120 | boost::tokenizer> tokens(extent, sep); 121 | for (const auto& t : tokens) { 122 | ex.push_back(boost::lexical_cast(t)); 123 | } 124 | if (ex.size() < 6) 125 | throw std::logic_error("Extent \"" + extent + "\" is not complete! Must be six values!"); 126 | domain.setBoundaryCondition(condition, ex[0], ex[1], ex[2], ex[3], ex[4], ex[5]); 127 | } 128 | } 129 | 130 | 131 | template 132 | auto parse_scenario_file(const std::string& filename, Config& cfg, 133 | FluidCollision& collision) -> std::unique_ptr> 134 | { 135 | pugi::xml_document doc; 136 | pugi::xml_parse_result result = doc.load_file(filename.c_str()); 137 | if (!result) 138 | throw std::logic_error("XML file \"" + filename + "\" could not be read properly!"); 139 | 140 | auto scenario = doc.child("scenario"); 141 | if (!scenario) 142 | throw std::logic_error("Scenario node missing!"); 143 | if (!scenario.attribute("name")) 144 | throw std::logic_error("Scenario name is missing!"); 145 | const auto scenario_name = scenario.attribute("name").value(); 146 | 147 | std::cout << "Reading scenario: \"" << scenario_name << "\"..." << std::endl; 148 | 149 | cfg.set_output_filename(scenario_name); 150 | auto xml_domain = scenario.child("domain"); 151 | if (!xml_domain) 152 | throw std::logic_error("Domain node is missing!"); 153 | auto vtk_file = xml_domain.attribute("vtk-file"); 154 | auto xml_xl = xml_domain.attribute("xl"); 155 | auto xml_yl = xml_domain.attribute("yl"); 156 | auto xml_zl = xml_domain.attribute("zl"); 157 | double xl, yl, zl; 158 | 159 | std::unique_ptr> domain; 160 | if (vtk_file) { 161 | domain = lbm::io::read_vtk_point_file>(vtk_file.value(), collision); 163 | xl = domain->xlength(); 164 | yl = domain->ylength(); 165 | zl = domain->zlength(); 166 | } 167 | else if (xml_xl && xml_yl && xml_zl) { 168 | assert(xml_xl.as_uint() > 0 && xml_yl.as_uint() > 0 && xml_zl.as_uint() > 0); 169 | xl = xml_xl.as_uint(); 170 | yl = xml_yl.as_uint(); 171 | zl = xml_zl.as_uint(); 172 | domain = make_unique>(xl, yl, zl, collision); 173 | } 174 | else { 175 | throw std::logic_error( 176 | "Neither vtk-file nor xl/yl/zl attribute provided to domain node!"); 177 | } 178 | // std::cout << "X length: " << xl << std::endl; 179 | // std::cout << "Y length: " << yl << std::endl; 180 | // std::cout << "Z length: " << zl << std::endl; 181 | 182 | // Iterate through boundary conditions 183 | for (auto xml_boundary = xml_domain.child("boundary"); xml_boundary; 184 | xml_boundary = xml_boundary.next_sibling("boundary")) { 185 | parse_boundary(xml_boundary, *domain); 186 | } 187 | return std::move(domain); 188 | } 189 | 190 | 191 | }//namespace io 192 | }//namespace lbm 193 | -------------------------------------------------------------------------------- /include/boundary.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace lbm 4 | { 5 | 6 | ///////////////////////////////////// NoSlipBoundary ///////////////////////////////////////// 7 | 8 | template 9 | NoSlipBoundary::NoSlipBoundary(Domain& domain) : 10 | NonFluidCollision(domain) 11 | { 12 | } 13 | 14 | template 15 | void NoSlipBoundary::collide(Cell& cell, 16 | const uint_array& position) const 17 | { 18 | const auto x = position[0]; 19 | const auto y = position[1]; 20 | const auto z = position[2]; 21 | 22 | for (auto q = 0u; q < lattice_model::Q; ++q) { 23 | const auto& dx = lattice_model::velocities[q][0]; 24 | const auto& dy = lattice_model::velocities[q][1]; 25 | const auto& dz = lattice_model::velocities[q][2]; 26 | if (this->domain.in_bounds(x + dx, y + dy, z + dz) 27 | && this->domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 28 | cell[q] = this->domain.cell(x + dx, y + dy, z + dz)[lattice_model::inv(q)]; 29 | } 30 | } 31 | } 32 | 33 | ///////////////////////////////////// MovingWallBoundary ///////////////////////////////////////// 34 | 35 | template 36 | MovingWallBoundary::MovingWallBoundary( 37 | Domain& domain, const double_array& wall_velocity) 38 | : 39 | NonFluidCollision(domain), wall_velocity(wall_velocity) 40 | { 41 | } 42 | 43 | template 44 | void MovingWallBoundary::collide(Cell& cell, 45 | const uint_array& position) const 46 | { 47 | const auto x = position[0]; 48 | const auto y = position[1]; 49 | const auto z = position[2]; 50 | 51 | for (auto q = 0u; q < lattice_model::Q; ++q) { 52 | const auto& dx = lattice_model::velocities[q][0]; 53 | const auto& dy = lattice_model::velocities[q][1]; 54 | const auto& dz = lattice_model::velocities[q][2]; 55 | if (this->domain.in_bounds(x + dx, y + dy, z + dz) 56 | && this->domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 57 | const auto& neighbor = this->domain.cell(x + dx, y + dy, z + dz); 58 | 59 | const double density = this->compute_density(neighbor); 60 | double c_dot_u = 0; 61 | for (auto d = 0u; d < lattice_model::D; ++d) { 62 | c_dot_u += lattice_model::velocities[q][d] * wall_velocity[d]; 63 | } 64 | const double finv = neighbor[lattice_model::inv(q)]; 65 | cell[q] = finv + 2.0 * lattice_model::weights[q] * density * c_dot_u / (C_S * C_S); 66 | } 67 | } 68 | } 69 | 70 | ///////////////////////////////////// FreeSlipBoundary ///////////////////////////////////////// 71 | 72 | template 73 | FreeSlipBoundary::FreeSlipBoundary(Domain& domain) : 74 | NonFluidCollision(domain) 75 | { 76 | 77 | } 78 | 79 | template 80 | void FreeSlipBoundary::collide(Cell& cell, 81 | const uint_array& position) const 82 | { 83 | const auto x = position[0]; 84 | const auto y = position[1]; 85 | const auto z = position[2]; 86 | 87 | for (auto q = 0u; q < lattice_model::Q; ++q) { 88 | const auto& dx = lattice_model::velocities[q][0]; 89 | const auto& dy = lattice_model::velocities[q][1]; 90 | const auto& dz = lattice_model::velocities[q][2]; 91 | if (this->domain.in_bounds(x + dx, y + dy, z + dz) 92 | && this->domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 93 | if (this->domain.cell(x + dx, y, z).is_fluid()) 94 | cell[q] = 95 | this->domain.cell(x + dx, y, z)[lattice_model::velocity_index(-dx, dy, dz)]; 96 | else if (this->domain.cell(x, y + dy, z).is_fluid()) 97 | cell[q] = 98 | this->domain.cell(x, y + dy, z)[lattice_model::velocity_index(dx, -dy, dz)]; 99 | else if (this->domain.cell(x, y, z + dz).is_fluid()) 100 | cell[q] = 101 | this->domain.cell(x, y, z + dz)[lattice_model::velocity_index(dx, dy, -dz)]; 102 | else if (this->domain.cell(x, y + dy, z + dz).is_fluid()) 103 | cell[q] = this->domain.cell(x, y + dy, z + dz)[lattice_model::velocity_index(dx, 104 | -dy, -dz)]; 105 | else if (this->domain.cell(x + dx, y, z + dz).is_fluid()) 106 | cell[q] = this->domain.cell(x + dx, y, z + dz)[lattice_model::velocity_index(-dx, 107 | dy, -dz)]; 108 | else if (this->domain.cell(x + dx, y + dy, z).is_fluid()) 109 | cell[q] = this->domain.cell(x + dx, y + dy, z)[lattice_model::velocity_index(-dx, 110 | -dy, dz)]; 111 | // else // Check if this condition is correct! 112 | // cell[q] = this->domain.cell(x + dx, y + dy, z + dz)[lattice_model::inv(q)]; 113 | } 114 | } 115 | } 116 | 117 | ///////////////////////////////////// OutflowBoundary ///////////////////////////////////////// 118 | 119 | template 120 | OutflowBoundary::OutflowBoundary(Domain& domain, 121 | double reference_density) : 122 | NonFluidCollision(domain), 123 | reference_density { reference_density } 124 | { 125 | 126 | } 127 | 128 | template 129 | void OutflowBoundary::collide(Cell& cell, 130 | const uint_array& position) const 131 | { 132 | auto x = position[0]; 133 | auto y = position[1]; 134 | auto z = position[2]; 135 | 136 | for (auto q = 0u; q < lattice_model::Q; ++q) { 137 | const auto& dx = lattice_model::velocities[q][0]; 138 | const auto& dy = lattice_model::velocities[q][1]; 139 | const auto& dz = lattice_model::velocities[q][2]; 140 | if (this->domain.in_bounds(x + dx, y + dy, z + dz) 141 | && this->domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 142 | 143 | const auto& neighbor = this->domain.cell(x + dx, y + dy, z + dz); 144 | const auto velocity = neighbor.velocity(reference_density); 145 | const auto feq = neighbor.equilibrium(reference_density, velocity); 146 | 147 | cell[q] = feq[q] + feq[lattice_model::inv(q)] - neighbor[lattice_model::inv(q)]; 148 | } 149 | } 150 | } 151 | 152 | ///////////////////////////////////// InflowBoundary ///////////////////////////////////////// 153 | 154 | template 155 | InflowBoundary::InflowBoundary(Domain& domain, 156 | const double_array& inflow_velocity, double reference_density) : 157 | NonFluidCollision(domain), 158 | reference_density { reference_density }, 159 | inflow_velocity(inflow_velocity) 160 | { 161 | 162 | } 163 | 164 | template 165 | void InflowBoundary::collide(Cell& cell, 166 | const uint_array& position) const 167 | { 168 | const auto x = position[0]; 169 | const auto y = position[1]; 170 | const auto z = position[2]; 171 | 172 | for (auto q = 0u; q < lattice_model::Q; ++q) { 173 | const auto& dx = lattice_model::velocities[q][0]; 174 | const auto& dy = lattice_model::velocities[q][1]; 175 | const auto& dz = lattice_model::velocities[q][2]; 176 | if (this->domain.in_bounds(x + dx, y + dy, z + dz) 177 | && this->domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 178 | cell[q] = this->compute_feq(reference_density, inflow_velocity)[q]; 179 | } 180 | } 181 | } 182 | 183 | ///////////////////////////////////// PressureBoundary ///////////////////////////////////////// 184 | 185 | template 186 | PressureBoundary::PressureBoundary(Domain& domain, 187 | double input_density) : 188 | NonFluidCollision(domain), 189 | input_density { input_density } 190 | { 191 | 192 | } 193 | 194 | template 195 | void PressureBoundary::collide(Cell& cell, 196 | const uint_array& position) const 197 | { 198 | const auto x = position[0]; 199 | const auto y = position[1]; 200 | const auto z = position[2]; 201 | 202 | for (auto q = 0u; q < lattice_model::Q; ++q) { 203 | const auto& dx = lattice_model::velocities[q][0]; 204 | const auto& dy = lattice_model::velocities[q][1]; 205 | const auto& dz = lattice_model::velocities[q][2]; 206 | if (this->domain.in_bounds(x + dx, y + dy, z + dz) 207 | && this->domain.cell(x + dx, y + dy, z + dz).is_fluid()) { 208 | const auto& neighbor = this->domain.cell(x + dx, y + dy, z + dz); 209 | const auto velocity = neighbor.velocity(input_density); 210 | const auto feq = neighbor.equilibrium(input_density, velocity); 211 | cell[q] = feq[q] + feq[lattice_model::inv(q)] - neighbor[lattice_model::inv(q)]; 212 | } 213 | } 214 | } 215 | }//namespace lbm 216 | -------------------------------------------------------------------------------- /include/domain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace lbm 4 | { 5 | 6 | template 7 | inline auto Domain::xlength() const -> decltype(xl) 8 | { 9 | return xl; 10 | } 11 | 12 | template 13 | inline auto Domain::ylength() const -> decltype(yl) 14 | { 15 | return yl; 16 | } 17 | 18 | template 19 | inline auto Domain::zlength() const -> decltype(zl) 20 | { 21 | return zl; 22 | } 23 | 24 | template 25 | inline auto Domain::xorigin() const -> decltype(xo) 26 | { 27 | return xo; 28 | } 29 | 30 | template 31 | inline auto Domain::yorigin() const -> decltype(yo) 32 | { 33 | return yo; 34 | } 35 | 36 | template 37 | inline auto Domain::zorigin() const -> decltype(zo) 38 | { 39 | return zo; 40 | } 41 | 42 | template 43 | inline auto Domain::xspacing() const -> decltype(xs) 44 | { 45 | return xs; 46 | } 47 | 48 | template 49 | inline auto Domain::yspacing() const -> decltype(ys) 50 | { 51 | return ys; 52 | } 53 | 54 | template 55 | inline auto Domain::zspacing() const -> decltype(zs) 56 | { 57 | return zs; 58 | } 59 | 60 | template 61 | inline int Domain::idx(int x, int y, int z) const 62 | { 63 | return x + (xl + 2) * y + (xl + 2) * (yl + 2) * z; 64 | } 65 | 66 | template 67 | inline bool Domain::in_bounds(int x, int y, int z) const 68 | { 69 | return x > 0 && x < (int) xl + 1 && y > 0 && y < (int) yl + 1 && z > 0 && z < (int) zl + 1; 70 | } 71 | 72 | 73 | template 74 | inline auto Domain::cell(int x, int y, int z) const -> const Cell& 75 | { 76 | return collide_field[idx(x, y, z)]; 77 | } 78 | 79 | template 80 | inline auto Domain::cell(int x, int y, int z) -> Cell& 81 | { 82 | return collide_field[idx(x, y, z)]; 83 | } 84 | 85 | 86 | template 87 | inline Domain::Domain(size_t xl, size_t yl, size_t zl, 88 | FluidCollision& _collision, double xorigin, double yorigin, 89 | double zorigin, double xspacing, double yspacing, double zspacing) : 90 | collision { &_collision }, collide_field((xl + 2) * (yl + 2) * (zl + 2), 91 | Cell(&_collision)), stream_field((xl + 2) * (yl + 2) * (zl + 2), 92 | Cell(&_collision)), xl { xl }, yl { yl }, zl { zl }, 93 | xo { xorigin }, yo { yorigin }, zo { zorigin }, 94 | xs { xspacing }, ys { yspacing }, zs { zspacing } 95 | { 96 | collide_field.shrink_to_fit(); 97 | stream_field.shrink_to_fit(); 98 | } 99 | 100 | template 101 | auto Domain::set_nonfluid_cells_nullcollide() -> void 102 | { 103 | static auto null_collision = NullCollision(); 104 | #pragma omp parallel for collapse(3) 105 | for (auto z = 1u; z < zl + 1; ++z) { 106 | for (auto y = 1u; y < yl + 1; ++y) { 107 | for (auto x = 1u; x < xl + 1; ++x) { 108 | if (!cell(x, y, z).has_fluid_vicinity(*this, { x, y, z })) 109 | cell(x, y, z).set_collision_handler(&null_collision); 110 | } 111 | } 112 | } 113 | } 114 | 115 | template 116 | auto Domain::stream() -> void 117 | { 118 | // We don't stream over the ghost layer cells 119 | // Streaming can be parallelized since we only read in parallel but write sequentially 120 | #pragma omp parallel for collapse(3) 121 | for (auto z = 1u; z < zl + 1; ++z) { 122 | for (auto y = 1u; y < yl + 1; ++y) { 123 | for (auto x = 1u; x < xl + 1; ++x) { 124 | if (cell(x, y, z).is_fluid()) { 125 | for (auto q = 0u; q < lattice_model::Q; ++q) { 126 | auto dx = lattice_model::velocities[q][0]; 127 | auto dy = lattice_model::velocities[q][1]; 128 | auto dz = lattice_model::velocities[q][2]; 129 | 130 | // New value for our distribution function (DF) of the index 'i' 131 | // (We set it to DF(i) of the next particle, whose i-th lattice velocity 132 | // points towards considered particle (x,y,z)) 133 | // Position of that next particle is given by (x-dx, y-dy, z-dz) 134 | double finv = cell(x - dx, y - dy, z - dz)[q]; 135 | stream_field[idx(x, y, z)][q] = finv; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | template 144 | auto Domain::collide() -> void 145 | { 146 | // Collide fluid cells 147 | #pragma omp parallel for collapse(3) 148 | for (auto z = 0u; z < zl + 2; ++z) { 149 | for (auto y = 0u; y < yl + 2; ++y) { 150 | for (auto x = 0u; x < xl + 2; ++x) { 151 | if (cell(x, y, z).is_fluid()) 152 | cell(x, y, z).collide({ x, y, z }); 153 | } 154 | } 155 | } 156 | // Collide all other cells 157 | #pragma omp parallel for collapse(3) 158 | for (auto z = 0u; z < zl + 2; ++z) { 159 | for (auto y = 0u; y < yl + 2; ++y) { 160 | for (auto x = 0u; x < xl + 2; ++x) { 161 | if (!cell(x, y, z).is_fluid()) 162 | cell(x, y, z).collide({ x, y, z }); 163 | } 164 | } 165 | } 166 | } 167 | 168 | template 169 | inline auto Domain::swap() -> void 170 | { 171 | std::swap(collide_field, stream_field); 172 | } 173 | 174 | template 175 | auto Domain::setBoundaryCondition( 176 | NonFluidCollision& condition, size_t x0, size_t xE, 177 | size_t y0, size_t yE, size_t z0, size_t zE) -> void 178 | { 179 | // TODO: Remove assertions(?) 180 | assert(xE >= x0 && yE >= y0 && zE >= z0); 181 | assert(xE < xl + 2 && yE < yl + 2 && zE < zl + 2); 182 | // std::cout << "Setting boundary condition for extent " 183 | // << x0 << ' ' << xE << ' ' << y0 << ' ' << yE << ' ' << z0 << ' ' << zE << std::endl; 184 | #pragma omp parallel for collapse(3) 185 | for (auto z = z0; z <= zE; ++z) { 186 | for (auto y = y0; y <= yE; ++y) { 187 | for (auto x = x0; x <= xE; ++x) { 188 | auto index = idx(x, y, z); 189 | collide_field[index].set_collision_handler(&condition); 190 | stream_field[index].set_collision_handler(&condition); 191 | } 192 | } 193 | } 194 | } 195 | 196 | template 197 | auto Domain::create_subdomain( 198 | parallel::ParallelBoundary& parallel_boundary, 199 | size_t xstart, size_t xend, int rank, int number_of_ranks) const -> Domain_ptr 200 | { 201 | assert(xend > xstart); 202 | const size_t new_xl = xend - xstart + 1; 203 | // Create subdomain with reduced xlength and proper shifting of origin 204 | auto subdomain = make_unique>(new_xl, yl, zl, *collision, 205 | xo+rank*new_xl*xs, yo, zo, xs, ys, zs); 206 | 207 | // Copy x-interval of the original domain to the sub-domain 208 | for (auto z = 0u; z < zl + 2; ++z) { 209 | for (auto y = 0u; y < yl + 2; ++y) { 210 | for (auto x = xstart; x < xend; ++x) { 211 | subdomain->cell(x-xstart+1, y, z) = cell(x, y, z); 212 | } 213 | } 214 | } 215 | // Also initialize the stream field with the partial domain cells 216 | subdomain->stream_field = subdomain->collide_field; 217 | // All ranks but the leftmost one have a left parallel boundary 218 | if (rank > 0) { 219 | subdomain->setBoundaryCondition(parallel_boundary, 220 | 0, 0, // X=0 is parallel boundary, 221 | 0, subdomain->ylength()+1, // For all y 222 | 0, subdomain->zlength()+1); // For all z 223 | } 224 | // The leftmost boundary is copied from the original domain ("real" boundary condition) 225 | else { 226 | for (auto z = 0u; z < zl + 2; ++z) { 227 | for (auto y = 0u; y < yl + 2; ++y) { 228 | subdomain->cell(0, y, z) = cell(0, y, z); 229 | } 230 | } 231 | } 232 | // All ranks but the rightmost one have a right boundary 233 | if (rank < number_of_ranks-1) { 234 | subdomain->setBoundaryCondition(parallel_boundary, 235 | subdomain->xlength()+1, subdomain->xlength()+1, 236 | 0, subdomain->ylength()+1, 237 | 0, subdomain->zlength()+1); 238 | } 239 | // The rightmost boundary is copied from the original domain ("real" boundary condition) 240 | else { 241 | for (auto z = 0u; z < zl + 2; ++z) { 242 | for (auto y = 0u; y < yl + 2; ++y) { 243 | subdomain->cell(subdomain->xlength()+1, y, z) = cell(xl+1, y, z); 244 | } 245 | } 246 | } 247 | return std::move(subdomain); 248 | } 249 | 250 | }//namespace lbm 251 | --------------------------------------------------------------------------------