### Install OpenCL Runtime on Linux (Intel GPUs) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Installs the OpenCL Runtime for Intel GPUs on Ubuntu-based Linux systems. This requires installing specific packages and ensuring the user is part of the 'render' group. A reboot is recommended after installation. ```bash sudo apt update && sudo apt upgrade -y sudo apt install -y g++ git make ocl-icd-libopencl1 ocl-icd-opencl-dev intel-opencl-icd sudo usermod -a -G render $(whoami) sudo shutdown -r now ``` -------------------------------- ### Install OpenCL Runtime on Linux (Nvidia GPUs) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Installs Nvidia GPU drivers and OpenCL runtime on Ubuntu-based Linux systems. This involves updating the system, installing essential development tools and driver packages, and then rebooting. ```bash sudo apt update && sudo apt upgrade -y sudo apt install -y g++ git make ocl-icd-libopencl1 ocl-icd-opencl-dev nvidia-driver-580 sudo shutdown -r now ``` -------------------------------- ### Install OpenCL Runtime on Linux (AMD GPUs) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Installs AMD GPU drivers and OpenCL runtime on Ubuntu-based Linux systems. This includes updating the system, installing necessary packages, downloading and installing AMD drivers with OpenCL support, and configuring user permissions. A reboot is required after installation. ```bash sudo apt update && sudo apt upgrade -y sudo apt install -y g++ git make ocl-icd-libopencl1 ocl-icd-opencl-dev mkdir -p ~/amdgpu wget -P ~/amdgpu https://repo.radeon.com/amdgpu-install/6.4.2.1/ubuntu/noble/amdgpu-install_6.4.60402-1_all.deb sudo apt install -y ~/amdgpu/amdgpu-install*.deb sudo amdgpu-install -y --usecase=graphics,rocm,opencl --opencl=rocr sudo usermod -a -G render,video $(whoami) rm -r ~/amdgpu sudo shutdown -r now ``` -------------------------------- ### Install OpenCL Runtime on Linux (CPU - Intel) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Installs the Intel CPU Runtime for OpenCL on Ubuntu-based Linux systems using two options. Option 1 involves downloading and compiling specific versions of the oneAPI DPC++ Compiler and oneTBB. Option 2 involves installing PoCL. ```bash export OCLV="oclcpuexp-2025.20.6.0.04_224945_rel" export TBBV="oneapi-tbb-2022.2.0" sudo apt update && sudo apt upgrade -y sudo apt install -y g++ git make ocl-icd-libopencl1 ocl-icd-opencl-dev sudo mkdir -p ~/cpurt /opt/intel/${OCLV} /etc/OpenCL/vendors /etc/ld.so.conf.d sudo wget -P ~/cpurt https://github.com/intel/llvm/releases/download/2025-WW27/${OCLV}.tar.gz sudo wget -P ~/cpurt https://github.com/uxlfoundation/oneTBB/releases/download/v2022.2.0/${TBBV}-lin.tgz sudo tar -zxvf ~/cpurt/${OCLV}.tar.gz -C /opt/intel/${OCLV} sudo tar -zxvf ~/cpurt/${TBBV}-lin.tgz -C /opt/intel echo /opt/intel/${OCLV}/x64/libintelocl.so | sudo tee /etc/OpenCL/vendors/intel_expcpu.icd echo /opt/intel/${OCLV}/x64 | sudo tee /etc/ld.so.conf.d/libintelopenclexp.conf sudo ln -sf /opt/intel/${TBBV}/lib/intel64/gcc4.8/libtbb.so /opt/intel/${OCLV}/x64 sudo ln -sf /opt/intel/${TBBV}/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/${OCLV}/x64 sudo ln -sf /opt/intel/${TBBV}/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/${OCLV}/x64 sudo ln -sf /opt/intel/${TBBV}/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/${OCLV}/x64 sudo ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf sudo rm -r ~/cpurt ``` ```bash sudo apt update && sudo apt upgrade -y sudo apt install -y g++ git make ocl-icd-libopencl1 ocl-icd-opencl-dev pocl-opencl-icd ``` -------------------------------- ### Install Termux for Android (ARM GPUs) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Installs Termux and necessary development tools on Android devices for ARM GPU support. This involves downloading the Termux APK and then running package installation commands within the Termux environment. ```bash apt update && apt upgrade -y apt install -y clang git make ``` -------------------------------- ### Configure FluidX3D Extensions in defines.hpp Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md This C++ code demonstrates how to configure FluidX3D by commenting out or uncommenting preprocessor directives in the `defines.hpp` file. This is used to enable specific features or benchmarks required for different setups. ```c++ // #define BENCHMARK ``` -------------------------------- ### Download FluidX3D using Git Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Clones the FluidX3D project repository from GitHub. This command downloads the entire project source code into a new directory named 'FluidX3D' in the current location. ```bash git clone https://github.com/ProjectPhysX/FluidX3D.git && cd FluidX3D ``` -------------------------------- ### Add Ahmed body setup example (C++) Source: https://github.com/projectphysx/fluidx3d/blob/master/README.md Includes an Ahmed body setup as a practical example demonstrating how to compute body forces and drag coefficients. This serves as a benchmark and a learning resource for simulating aerodynamic forces. ```cpp #include "setup.cpp" #include "LBM.hpp" #include "Mesh.hpp" // ... within setup.cpp or examples directory ... void setup_ahmed_body() { // Load Ahmed body geometry Mesh ahmed_body = load_stl("ahmed_body.stl"); lbm.add_object(ahmed_body); // Enable force calculations lbm.enable_force_calculations(); // Run simulation and compute drag coefficient // ... simulation loop ... float drag_coefficient = lbm.get_drag_coefficient(); std::cout << "Drag Coefficient: " << drag_coefficient << std::endl; } ``` -------------------------------- ### Initialize LBM Simulation Box (C++) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Initializes the LBM simulation box with specified grid resolution and kinematic viscosity. For multi-GPU setups, additional parameters define domain decomposition. ```c++ LBM lbm(Nx, Ny, Nz, nu, ...); // For multi-GPU: LBM lbm(Nx, Ny, Nz, Dx, Dy, Dz, nu, ...); ``` -------------------------------- ### Basic Video Rendering Loop in C++ Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md This C++ code snippet demonstrates a basic loop for rendering video frames within the main simulation setup. It sets visualization modes, runs the simulation for a specified number of time steps, and captures frames from two camera positions. Ensure 'src/defines.hpp' is configured for graphics rendering. ```cpp lbm.graphics.visualization_modes = VIS_FLAG_LATTICE|VIS_Q_CRITERION; // set visualization modes, see all available visualization mode macros (VIZ_...) in defines.hpp const uint lbm_T = 10000u; // number of LBM time steps to simulate lbm.run(0u, lbm_T); // initialize simulation while(lbm.get_t()scale(const float scale); // manually scale meshes mesh_2->scale(const float scale); mesh_1->translate(const float3& translation); // manually reposition meshes mesh_2->translate(const float3& translation); lbm.voxelize_mesh_on_device(mesh_1); // voxelize meshes on GPU lbm.voxelize_mesh_on_device(mesh_2); ``` -------------------------------- ### Initialize Fluid Cells with SURFACE Extension (C++) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md This snippet shows how to initialize fluid cells using the SURFACE extension. It demonstrates setting the cell flag to TYPE_F and optionally setting the fill level 'phi'. The interface layer is automatically initialized after the first run. ```cpp lbm.flags[n] = TYPE_F; // Optionally set fill level: lbm.phi[n] = 1.0f; // or between 0 and 1 for interface cells ``` -------------------------------- ### Load and Voxelize Simple .stl Files (C++) Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Loads a simple binary .stl file and voxelizes it onto the GPU simulation grid with automatic repositioning and rescaling. Requires the mesh to be watertight and triangles oriented outwards. The mesh is centered around the simulation box center, with optional offsets. ```c++ lbm.voxelize_stl(get_exe_path()+"../stl/mesh.stl", center, rotation, size); ``` -------------------------------- ### Set Moving Solid Boundaries in FluidX3D Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Sets moving solid boundaries ('TYPE_S') with a specified non-zero velocity. Requires enabling the 'MOVING_BOUNDARIES' extension. These act as moving objects or inflow/outflow with fixed flow rate, reflecting shockwaves. ```c++ if() { lbm.flags[n] = TYPE_S; lbm.u.x[n] = <...>; // non-zero velocity lbm.u.y[n] = <...>; // non-zero velocity lbm.u.z[n] = <...>; // non-zero velocity } ``` -------------------------------- ### Smooth Camera Path Animation in C++ Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md This C++ code implements a smooth camera path animation using Catmull-Rom splines. It defines keyframe camera positions and rotations and interpolates between them as the simulation progresses. This allows for dynamic camera movement through the rendered scene. Dependencies include standard C++ libraries and FluidX3D's graphics module. ```cpp while(lbm.get_t()<=lbm_T) { // main simulation loop if(lbm.graphics.next_frame(lbm_T, 30.0f)) { const float t = (float)lbm.get_t()/(float)lbm_T; vector camera_positions = { float3(-0.282220f*(float)Nx, 0.529221f*(float)Ny, 0.304399f*(float)Nz), float3( 0.806921f*(float)Nx, 0.239912f*(float)Ny, 0.436880f*(float)Nz), float3( 1.129724f*(float)Nx, -0.130721f*(float)Ny, 0.352759f*(float)Nz), float3( 0.595601f*(float)Nx, -0.504690f*(float)Ny, 0.203096f*(float)Nz), float3(-0.056776f*(float)Nx, -0.591919f*(float)Ny, -0.416467f*(float)Nz) }; vector camera_rx = { 116.0f, 25.4f, -10.6f, -45.6f, -94.6f }; vector camera_ry = { 26.0f, 33.3f, 20.3f, 25.3f, -16.7f }; const float camera_fov = 90.0f; lbm.graphics.set_camera_free(catmull_rom(camera_positions, t), catmull_rom(camera_rx, t), catmull_rom(camera_ry, t), camera_fov); lbm.graphics.write_frame(get_exe_path()+"export/"); } lbm.run(1u, lbm_T); } ``` -------------------------------- ### Export Volumetric Data to VTK Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Exports volumetric data from the simulation to binary .vtk files. This process involves transferring data from GPU to CPU RAM before writing to disk. File names automatically include the simulation time step. Data can be automatically converted to SI units if unit conversion is enabled. Warning: these files can be very large. Viewing is recommended with ParaView. ```c lbm.rho.write_device_to_vtk(); // density lbm.u.write_device_to_vtk(); // velocity lbm.flags.write_device_to_vtk(); // flags lbm.F.write_device_to_vtk(); // force, only for FORCE_FIELD extension lbm.phi.write_device_to_vtk(); // fill fraction, only for SURFACE extension lbm.T.write_device_to_vtk(); // temperature, only for TEMPERATURE extension ``` -------------------------------- ### Initialize Grid Cells in FluidX3D Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Iterates through all grid cells in parallel to set initial or boundary conditions. It accesses cell properties like density, velocity, and flags using linearized indices. Dependencies include the LBM object and its methods for grid dimensions and coordinate mapping. No specific input is required, but cell properties are modified. ```c++ const uint Nx=lbm.get_Nx(), Ny=lbm.get_Ny(), Nz=lbm.get_Nz(); parallel_for(lbm.get_N(), [&](ulong n) { uint x=0u, y=0u, z=0u; lbm.coordinates(n, x, y, z); // ... set lbm.rho[n], lbm.u.x[n], etc. }); ``` -------------------------------- ### Initialize LBM with Volume Force in FluidX3D Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Initializes the LBM class with specified grid dimensions and volume forces (fx, fy, fz). This is used to drive flow with periodic boundary conditions when strict mass conservation is required. The magnitude of force per volume should not exceed 0.001. ```c++ LBM lbm(Nx, Ny, Nz, nu, fx, fy, fz); ``` -------------------------------- ### Read Entire Force Field from GPU Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Copies the entire force field data (lbm.F) from GPU VRAM to CPU RAM. This operation can be slow due to the potentially large amount of data. Once in CPU RAM, individual force components at specific grid cells can be accessed. ```c lbm.F.read_from_device(); // copy entire force field from GPU VRAM to CPU RAM (slow) lbm_force_x_n = lbm.F.x[lbm.index(x, y, z)]; // access force at one particular grid cell with integer coordinates x, y, z ``` -------------------------------- ### Add documentation and clean up setup.cpp (C++) Source: https://github.com/projectphysx/fluidx3d/blob/master/README.md Enhances the project by adding more comprehensive documentation and cleaning up the `setup.cpp` file for improved beginner-friendliness. Required extensions are now commented in `defines.hpp`, making it easier for new users to understand and configure setups. ```cpp // In DOCUMENTATION.md or similar documentation file: /** * @page GettingStarted Getting Started with FluidX3D * * This guide provides a step-by-step approach to setting up and running simulations. * Key concepts and parameters are explained in detail. * ... more documentation content ... */ // In setup.cpp: #include "defines.hpp" // Example of cleaned up setup with commented requirements void setup_basic_simulation() { // Requires: #define ENABLE_FLUID // Requires: #define ENABLE_GRAPHICS lbm.initialize_grid(...); // ... rest of setup ... } ``` -------------------------------- ### Convert LBM Force to SI Units Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Converts force values from LBM units to SI units. This conversion is necessary for interpreting simulation results in a physically meaningful context and requires the prior setup of unit conversion parameters. ```c const float si_force_x = units.si_F(lbm_force.x); ``` -------------------------------- ### Convert LBM Velocity to SI Units Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Converts velocity values from LBM (Lattice Boltzmann Method) units to SI (International System of Units). This conversion requires prior setup of unit conversion parameters using `units.set_m_kg_s(...)`. ```c const float si_velocity_x = units.si_u(lbm_velocity_x); ``` -------------------------------- ### Run FluidX3D LBM Simulation Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md This code snippet shows how to initiate and control the execution of an LBM simulation using the `lbm.run()` function. It can run for an infinite number of time steps by calling `lbm.run()` without arguments, or for a specific duration with `lbm.run(time_steps)`. A special case, `lbm.run(0u)`, is used to initialize the simulation on the GPU without computing any time steps, essential before complex rendering or data export loops. ```cpp // Run for infinite time steps lbm.run() // Run for a specific number of time steps lbm.run(time_steps) // Initialize simulation on GPU without computing time steps lbm.run(0u) ``` -------------------------------- ### Initialize Single GPU LBM Simulation (C++) Source: https://context7.com/projectphysx/fluidx3d/llms.txt Sets up and runs a lattice Boltzmann simulation on a single GPU. It configures the grid resolution and kinematic viscosity, initializes boundary conditions for the simulation box, and then executes the simulation for a specified number of time steps. Dependencies include the 'setup.hpp' header. ```cpp #include "setup.hpp" void main_setup() { // Create 256x256x256 grid with kinematic viscosity nu=1/6 LBM lbm(256u, 256u, 256u, 1.0f/6.0f); // Initialize simulation box with boundary conditions const uint Nx=lbm.get_Nx(), Ny=lbm.get_Ny(), Nz=lbm.get_Nz(); parallel_for(lbm.get_N(), [&](ulong n) { uint x=0u, y=0u, z=0u; lbm.coordinates(n, x, y, z); // Set solid boundaries on all box sides if(x==0u || x==Nx-1u || y==0u || y==Ny-1u || z==0u || z==Nz-1u) { lbm.flags[n] = TYPE_S; } }); // Run simulation for 10000 time steps lbm.run(10000u); } ``` -------------------------------- ### Unit Conversion System Setup (C++) Source: https://context7.com/projectphysx/fluidx3d/llms.txt Implements a unit conversion system to translate between SI units and Lattice Boltzmann (LBM) units, ensuring physical accuracy in simulations. This example demonstrates setting physical parameters in SI units, converting them to LBM units using the `units.set_m_kg_s` function, creating an LBM simulation with the converted parameters, and then converting simulation results back to SI units for interpretation. It requires the 'setup.hpp' header. ```cpp #include "setup.hpp" void main_setup() { // SI physical parameters const float si_length = 1.0f; // 1 meter characteristic length const float si_velocity = 10.0f; // 10 m/s flow velocity const float si_density = 1.225f; // 1.225 kg/m³ (air density) const float si_nu = 1.5e-5f; // 1.5×10⁻⁵ m²/s (air viscosity) // LBM parameters (dimensionless, optimized for accuracy) const float lbm_length = 256.0f; // grid cells const float lbm_velocity = 0.08f; // LBM velocity units const float lbm_density = 1.0f; // LBM density units // Configure unit conversion system units.set_m_kg_s(lbm_length, lbm_velocity, lbm_density, si_length, si_velocity, si_density); // Convert SI viscosity to LBM units const float lbm_nu = units.nu(si_nu); const uint Nx = (uint)lbm_length; // Create simulation with converted parameters LBM lbm(Nx, Nx, Nx, lbm_nu); // Run simulation and convert results back to SI lbm.run(10000u); lbm.u.read_from_device(); // Access velocity at specific point const float lbm_vx = lbm.u.x[lbm.index(Nx/2, Ny/2, Nz/2)]; const float si_vx = units.si_u(lbm_vx); // Convert to m/s print_info("Velocity at center: " + to_string(si_vx) + " m/s"); } ``` -------------------------------- ### Compute Object Force and Torque Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Calculates the total force and torque acting on an object within the simulation. This computation is GPU-accelerated and sums contributions from all cells marked with the specified flag combination. The results are copied to CPU RAM. ```c const float3 lbm_force = lbm.object_force(TYPE_S|TYPE_X); // force on object const float3 lbm_torque = lbm.object_torque(lbm_com, TYPE_S|TYPE_X); // torque on object around lbm_com rotation point ``` -------------------------------- ### Initialize Particle Positions Randomly Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md This snippet illustrates how to initialize the positions of particles within the simulation box. It iterates through all created particles and assigns them random coordinates using the `random_symmetric` function. The particle positions are relative to the center of the simulation box. ```c++ uint seed = 42u; for(ulong n=0ull; nlength(); n++) { lbm.particles->x[n] = random_symmetric(seed, 0.5f*lbm.size().x); // this will palce the particles randomly anywhere in the simulation box lbm.particles->y[n] = random_symmetric(seed, 0.5f*lbm.size().y); lbm.particles->z[n] = random_symmetric(seed, 0.5f*lbm.size().z); } ``` -------------------------------- ### Set Equilibrium Boundaries in FluidX3D Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Sets equilibrium boundaries ('TYPE_E') for grid cells, acting as inflow/outflow boundaries. Requires enabling the 'EQUILIBRIUM_BOUNDARIES' extension. Cells with this flag will enforce specified density and/or velocity values, absorbing shockwaves. ```c++ if() { lbm.flags[n] = TYPE_E; lbm.rho[n] = <...>; // TYPE_E cells will retain this fixed density lbm.u.x[n] = <...>; // TYPE_E cells will retain this fixed velocity lbm.u.y[n] = <...>; // TYPE_E cells will retain this fixed velocity lbm.u.z[n] = <...>; // TYPE_E cells will retain this fixed velocity } ``` -------------------------------- ### Initialize Multi-GPU LBM Simulation (C++) Source: https://context7.com/projectphysx/fluidx3d/llms.txt Configures and runs a lattice Boltzmann simulation distributed across multiple GPUs to increase the simulation scale by pooling VRAM. It calculates an optimal resolution based on available VRAM and aspect ratio, sets up domain decomposition, and initializes a spherical obstacle within the simulation domain. The simulation is set to run indefinitely. Dependencies include 'setup.hpp'. ```cpp #include "setup.hpp" void main_setup() { // Calculate optimal resolution for 2000 MB VRAM with 2:1:1 aspect ratio const uint3 lbm_N = resolution(float3(2.0f, 1.0f, 1.0f), 2000u); // Create 2x2x1 domain decomposition (4 GPUs) // Total grid: 2*lbm_N.x × 2*lbm_N.y × 1*lbm_N.z LBM lbm(2u*lbm_N.x, 2u*lbm_N.y, 1u*lbm_N.z, // total resolution 2u, 2u, 1u, // domain count (Dx, Dy, Dz) 1.0f/6.0f); // kinematic viscosity // Initialize geometry across all domains const uint Nx=lbm.get_Nx(), Ny=lbm.get_Ny(), Nz=lbm.get_Nz(); parallel_for(lbm.get_N(), [&](ulong n) { uint x=0u, y=0u, z=0u; lbm.coordinates(n, x, y, z); // Create sphere obstacle at simulation center if(sphere(x, y, z, lbm.center(), 64.0f)) { lbm.flags[n] = TYPE_S; } }); // Run infinite simulation lbm.run(); } ``` -------------------------------- ### Read Velocity Data to CPU RAM Source: https://github.com/projectphysx/fluidx3d/blob/master/DOCUMENTATION.md Reads velocity data from the GPU to CPU RAM, allowing direct access to individual velocity components at specific grid coordinates. This is a prerequisite for custom data manipulation or analysis before further processing or export. ```c lbm.u.read_from_device(); const float lbm_velocity_x = lbm.u.x[lbm.index(x, y, z)]; ``` -------------------------------- ### Simulate Free Surface with Surface Tension (C++) Source: https://context7.com/projectphysx/fluidx3d/llms.txt Models water surfaces with surface tension using the SURFACE extension. This example simulates a raindrop impacting a water surface. It initializes an LBM grid, defines boundary conditions for the solid bottom and water body, and introduces a falling raindrop. Surface tension is specified during LBM initialization. Dependencies include 'setup.hpp' and the SURFACE extension. ```cpp #include "setup.hpp" void main_setup() { // Raindrop impact on water surface // Enable SURFACE in defines.hpp const uint Nx=512u, Ny=256u, Nz=512u; const float sigma = 0.005f; // surface tension coefficient LBM lbm(Nx, Ny, Nz, 1.0f/6.0f, 0.0f, -0.00015f, 0.0f, sigma); const uint Nxl=lbm.get_Nx(), Nyl=lbm.get_Ny(), Nzl=lbm.get_Nz(); parallel_for(lbm.get_N(), [&](ulong n) { uint x=0u, y=0u, z=0u; lbm.coordinates(n, x, y, z); // Solid bottom if(y == 0u) { lbm.flags[n] = TYPE_S; } // Water body (bottom half) else if(y <= Nyl/2u) { lbm.flags[n] = TYPE_F; lbm.phi[n] = 1.0f; // fully filled } // Raindrop sphere falling from above else if(sphere(x, y, z, float3(Nxl/2.0f, 3.0f*Nyl/4.0f, Nzl/2.0f), 16.0f)) { lbm.flags[n] = TYPE_F; lbm.phi[n] = 1.0f; lbm.u.y[n] = -0.15f; // initial downward velocity } }); lbm.graphics.visualization_modes = VIS_PHI_RAYTRACE; lbm.run(); } ```