|
| 1 | +#include <CLI/CLI.hpp> |
| 2 | + |
| 3 | +#include <algorithm> |
| 4 | +#include <cmath> |
| 5 | +#include <limits> |
| 6 | +#include <string> |
| 7 | +#include <type_traits> |
| 8 | + |
| 9 | +#include "rxmesh/matrix/cg_solver.h" |
| 10 | +#ifdef USE_CUDSS |
| 11 | +#include "rxmesh/matrix/cudss_cholesky_solver.h" |
| 12 | +#endif |
| 13 | +#include "rxmesh/matrix/pcg_solver.h" |
| 14 | +#include "rxmesh/matrix/sparse_matrix.h" |
| 15 | +#include "rxmesh/reduce_handle.h" |
| 16 | +#include "rxmesh/rxmesh_static.h" |
| 17 | +#include "rxmesh/util/timer.h" |
| 18 | + |
| 19 | +using namespace rxmesh; |
| 20 | + |
| 21 | +struct SolveStats |
| 22 | +{ |
| 23 | + float pre_solve_ms = 0.0f; |
| 24 | + float time_stepping_ms = 0.0f; |
| 25 | + uint64_t total_iterations = 0; |
| 26 | + double final_solver_residual = 0.0; |
| 27 | + bool converged = true; |
| 28 | +}; |
| 29 | + |
| 30 | +template <typename T> |
| 31 | +void assemble_heat_system(const RXMeshStatic& rx, |
| 32 | + const VertexAttribute<T>& coordinates, |
| 33 | + const T dt, |
| 34 | + SparseMatrix<T>& system, |
| 35 | + VertexAttribute<T>& mass) |
| 36 | +{ |
| 37 | + system.reset(T(0), DEVICE); |
| 38 | + mass.reset(T(0), DEVICE); |
| 39 | + |
| 40 | + rx.for_each<Op::TV, 256>([=] __device__(const TetHandle&, |
| 41 | + const VertexIterator& tv) mutable { |
| 42 | + const vec3<T> x[4] = {coordinates.template to_glm<3>(tv[0]), |
| 43 | + coordinates.template to_glm<3>(tv[1]), |
| 44 | + coordinates.template to_glm<3>(tv[2]), |
| 45 | + coordinates.template to_glm<3>(tv[3])}; |
| 46 | + |
| 47 | + T max_edge = T(0); |
| 48 | + for (int i = 0; i < 4; ++i) { |
| 49 | + for (int j = i + 1; j < 4; ++j) { |
| 50 | + max_edge = std::max(max_edge, glm::length(x[j] - x[i])); |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + const vec3<T> e1 = x[1] - x[0]; |
| 55 | + const vec3<T> e2 = x[2] - x[0]; |
| 56 | + const vec3<T> e3 = x[3] - x[0]; |
| 57 | + const T det = glm::dot(e1, glm::cross(e2, e3)); |
| 58 | + |
| 59 | + // const T det_tolerance = T(32) * std::numeric_limits<T>::epsilon() * |
| 60 | + // max_edge * max_edge * max_edge; |
| 61 | + // if (!isfinite(det) || !isfinite(max_edge) || |
| 62 | + // std::abs(det) <= det_tolerance) { |
| 63 | + // return; |
| 64 | + // } |
| 65 | + |
| 66 | + const T volume = std::abs(det) / T(6); |
| 67 | + const vec3<T> g1 = glm::cross(e2, e3) / det; |
| 68 | + const vec3<T> g2 = glm::cross(e3, e1) / det; |
| 69 | + const vec3<T> g3 = glm::cross(e1, e2) / det; |
| 70 | + const vec3<T> gradient[4] = {-(g1 + g2 + g3), g1, g2, g3}; |
| 71 | + |
| 72 | + for (int i = 0; i < 4; ++i) { |
| 73 | + for (int j = 0; j < 4; ++j) { |
| 74 | + ::atomicAdd(&system(tv[i], tv[j]), |
| 75 | + dt * (volume * glm::dot(gradient[i], gradient[j]))); |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + const T vertex_mass = volume / T(4); |
| 80 | + for (int i = 0; i < 4; ++i) { |
| 81 | + ::atomicAdd(&mass(tv[i]), vertex_mass); |
| 82 | + ::atomicAdd(&system(tv[i], tv[i]), vertex_mass); |
| 83 | + } |
| 84 | + }); |
| 85 | +} |
| 86 | + |
| 87 | +template <typename T> |
| 88 | +void build_rhs(const RXMeshStatic& rx, |
| 89 | + const VertexAttribute<T>& mass, |
| 90 | + const DenseMatrix<T>& temperature, |
| 91 | + DenseMatrix<T>& rhs) |
| 92 | +{ |
| 93 | + rx.for_each_vertex(DEVICE, [=] __device__(const VertexHandle vh) mutable { |
| 94 | + rhs(vh) = mass(vh) * temperature(vh); |
| 95 | + }); |
| 96 | +} |
| 97 | + |
| 98 | + |
| 99 | +template <typename T, typename SolverT> |
| 100 | +SolveStats solve_iterative(const RXMeshStatic& rx, |
| 101 | + const VertexAttribute<T>& mass, |
| 102 | + DenseMatrix<T>& rhs, |
| 103 | + DenseMatrix<T>& temperature, |
| 104 | + const int steps, |
| 105 | + const int max_iterations, |
| 106 | + SolverT& solver) |
| 107 | +{ |
| 108 | + SolveStats stats; |
| 109 | + GPUTimer timer; |
| 110 | + timer.start(); |
| 111 | + for (int step = 0; step < steps; ++step) { |
| 112 | + build_rhs(rx, mass, temperature, rhs); |
| 113 | + solver.pre_solve(rhs, temperature); |
| 114 | + solver.solve(rhs, temperature); |
| 115 | + stats.total_iterations += solver.iter_taken(); |
| 116 | + stats.final_solver_residual = solver.final_residual(); |
| 117 | + stats.converged = stats.converged && |
| 118 | + solver.iter_taken() < max_iterations && |
| 119 | + std::isfinite(stats.final_solver_residual); |
| 120 | + } |
| 121 | + timer.stop(); |
| 122 | + stats.time_stepping_ms = timer.elapsed_millis(); |
| 123 | + return stats; |
| 124 | +} |
| 125 | + |
| 126 | +int main(int argc, char** argv) |
| 127 | +{ |
| 128 | + using T = rx_coord_t; |
| 129 | + |
| 130 | + CLI::App app{"Implicit heat diffusion on a tet mesh"}; |
| 131 | + |
| 132 | + std::string mesh_path = STRINGIFY(INPUT_DIR) "car.msh"; |
| 133 | + std::string solver_name = "pcg"; |
| 134 | + uint32_t device_id = 0; |
| 135 | + uint32_t source_vid = 0; |
| 136 | + T t_factor = T(1); |
| 137 | + int steps = 10; |
| 138 | + int cg_max_iter = 10000; |
| 139 | + |
| 140 | + |
| 141 | + app.add_option("-i,--input", mesh_path, "Input tetrahedral MSH file") |
| 142 | + ->default_val(mesh_path); |
| 143 | + app.add_option("-d,--device_id", device_id, "GPU device ID") |
| 144 | + ->default_val(device_id); |
| 145 | + app.add_option("-s,--source", source_vid, "Initial hot vertex") |
| 146 | + ->default_val(source_vid); |
| 147 | + app.add_option("-t,--t-factor", |
| 148 | + t_factor, |
| 149 | + "Time-step multiplier on mean edge length squared") |
| 150 | + ->default_val(t_factor); |
| 151 | + app.add_option("-n,--steps", steps, "Number of implicit time steps") |
| 152 | + ->default_val(steps); |
| 153 | + app.add_option("-l,--solver", solver_name, "Solver: cg, pcg, or cudss") |
| 154 | + ->default_val(solver_name); |
| 155 | + app.add_option( |
| 156 | + "-c,--cg_max_iter", cg_max_iter, "Maximum iterations for CG and PCG") |
| 157 | + ->default_val(cg_max_iter); |
| 158 | + |
| 159 | + try { |
| 160 | + app.parse(argc, argv); |
| 161 | + } catch (const CLI::ParseError& e) { |
| 162 | + return app.exit(e); |
| 163 | + } |
| 164 | + |
| 165 | + if (solver_name != "cg" && solver_name != "pcg" && solver_name != "cudss") { |
| 166 | + RXMESH_ERROR("Unsupported solver '{}'. Use cg, pcg, or cudss", |
| 167 | + solver_name); |
| 168 | + return EXIT_FAILURE; |
| 169 | + } |
| 170 | +#ifndef USE_CUDSS |
| 171 | + if (solver_name == "cudss") { |
| 172 | + RXMESH_ERROR("The cudss solver requires RX_USE_CUDSS=ON"); |
| 173 | + return EXIT_FAILURE; |
| 174 | + } |
| 175 | +#endif |
| 176 | + |
| 177 | + rx_init(device_id); |
| 178 | + |
| 179 | + RXMESH_INFO("input = {}", mesh_path); |
| 180 | + RXMESH_INFO("device_id = {}", device_id); |
| 181 | + RXMESH_INFO("source = {}", source_vid); |
| 182 | + RXMESH_INFO("t_factor = {}", t_factor); |
| 183 | + RXMESH_INFO("steps = {}", steps); |
| 184 | + RXMESH_INFO("solver = {}", solver_name); |
| 185 | + RXMESH_INFO("cg_max_iter = {}", cg_max_iter); |
| 186 | + |
| 187 | + RXMeshStatic rx(mesh_path); |
| 188 | + |
| 189 | + const uint32_t num_vertices = rx.get_num_vertices(); |
| 190 | + const uint32_t num_edges = rx.get_num_edges(); |
| 191 | + const uint32_t num_tets = rx.get_num_tets(); |
| 192 | + |
| 193 | + // allocate attributes and sparse matrices |
| 194 | + auto coordinates = *rx.get_input_vertex_coordinates(); |
| 195 | + auto edge_length = *rx.add_edge_attribute<T>("edge_length", 1); |
| 196 | + auto mass = *rx.add_vertex_attribute<T>("mass", 1); |
| 197 | + DenseMatrix<T> rhs(rx, num_vertices, 1, DEVICE); |
| 198 | + DenseMatrix<T> temperature(rx, num_vertices, 1, LOCATION_ALL); |
| 199 | + SparseMatrix<T> system(rx, Op::VV); |
| 200 | + |
| 201 | + if (rx.get_num_components() > 1) { |
| 202 | + RXMESH_WARN("Input mesh has {} components", rx.get_num_components()); |
| 203 | + } |
| 204 | + |
| 205 | + // pick the handle |
| 206 | + VertexHandle source_handle; |
| 207 | + rx.for_each_vertex(HOST, [&](const VertexHandle vh) { |
| 208 | + if (rx.map_to_global(vh) == source_vid) { |
| 209 | + source_handle = vh; |
| 210 | + } |
| 211 | + }); |
| 212 | + temperature.reset(T(0), LOCATION_ALL); |
| 213 | + temperature(source_handle) = T(100); |
| 214 | + temperature.move(HOST, DEVICE); |
| 215 | + |
| 216 | + // calc mean edge len |
| 217 | + rx.for_each<Op::EV, 256>( |
| 218 | + [=] __device__(const EdgeHandle& eh, const VertexIterator& ev) mutable { |
| 219 | + const vec3<T> a = coordinates.template to_glm<3>(ev[0]); |
| 220 | + const vec3<T> b = coordinates.template to_glm<3>(ev[1]); |
| 221 | + edge_length(eh) = glm::length(a - b); |
| 222 | + }); |
| 223 | + EdgeReduceHandle<T> edge_reducer(edge_length); |
| 224 | + const T edge_length_sum = |
| 225 | + edge_reducer.reduce(edge_length, cub::Sum(), T(0)); |
| 226 | + const T mean_edge_length = edge_length_sum / static_cast<T>(num_edges); |
| 227 | + |
| 228 | + const T dt = t_factor * mean_edge_length * mean_edge_length; |
| 229 | + |
| 230 | + RXMESH_INFO("#vertices = {}, #edges = {}, #tets = {}", |
| 231 | + num_vertices, |
| 232 | + num_edges, |
| 233 | + num_tets); |
| 234 | + RXMESH_INFO("Mean edge length = {}, dt = {}", mean_edge_length, dt); |
| 235 | + |
| 236 | + |
| 237 | + // assemble the system |
| 238 | + GPUTimer assembly_timer; |
| 239 | + assembly_timer.start(); |
| 240 | + assemble_heat_system(rx, coordinates, dt, system, mass); |
| 241 | + assembly_timer.stop(); |
| 242 | + const float assembly_ms = assembly_timer.elapsed_millis(); |
| 243 | + |
| 244 | + // solve |
| 245 | + const T iterative_abs_tolerance = std::numeric_limits<T>::min(); |
| 246 | + const T iterative_rel_tolerance = |
| 247 | + std::is_same_v<T, double> ? T(1e-24) : T(1e-12); |
| 248 | + |
| 249 | + SolveStats solve_stats; |
| 250 | + if (solver_name == "pcg") { |
| 251 | + PCGSolver<T> solver(system, |
| 252 | + 1, |
| 253 | + cg_max_iter, |
| 254 | + iterative_abs_tolerance, |
| 255 | + iterative_rel_tolerance); |
| 256 | + solve_stats = solve_iterative( |
| 257 | + rx, mass, rhs, temperature, steps, cg_max_iter, solver); |
| 258 | + } else if (solver_name == "cg") { |
| 259 | + CGSolver<T> solver(system, |
| 260 | + 1, |
| 261 | + cg_max_iter, |
| 262 | + iterative_abs_tolerance, |
| 263 | + iterative_rel_tolerance); |
| 264 | + solve_stats = solve_iterative( |
| 265 | + rx, mass, rhs, temperature, steps, cg_max_iter, solver); |
| 266 | +#ifdef USE_CUDSS |
| 267 | + } else if (solver_name == "cudss") { |
| 268 | + cuDSSCholeskySolver<SparseMatrix<T>> solver(&system); |
| 269 | + build_rhs(rx, mass, temperature, rhs); |
| 270 | + |
| 271 | + GPUTimer pre_solve_timer; |
| 272 | + pre_solve_timer.start(); |
| 273 | + solver.pre_solve(rx, rhs, temperature); |
| 274 | + pre_solve_timer.stop(); |
| 275 | + solve_stats.pre_solve_ms = pre_solve_timer.elapsed_millis(); |
| 276 | + |
| 277 | + GPUTimer time_stepping_timer; |
| 278 | + time_stepping_timer.start(); |
| 279 | + for (int step = 0; step < steps; ++step) { |
| 280 | + if (step != 0) { |
| 281 | + build_rhs(rx, mass, temperature, rhs); |
| 282 | + } |
| 283 | + solver.solve(rhs, temperature); |
| 284 | + } |
| 285 | + time_stepping_timer.stop(); |
| 286 | + solve_stats.time_stepping_ms = time_stepping_timer.elapsed_millis(); |
| 287 | +#endif |
| 288 | + } |
| 289 | + |
| 290 | + if (!solve_stats.converged) { |
| 291 | + RXMESH_WARN("An iterative heat solve did not converge"); |
| 292 | + } |
| 293 | + |
| 294 | + // calc residual |
| 295 | + DenseMatrix<T> residual(rx, num_vertices, 1, DEVICE); |
| 296 | + system.multiply(temperature, residual); |
| 297 | + residual.axpy(rhs, T(-1)); |
| 298 | + const double rhs_norm = static_cast<double>(rhs.norm2()); |
| 299 | + const double residual_norm = static_cast<double>(residual.norm2()); |
| 300 | + const double relative_residual = residual_norm / rhs_norm; |
| 301 | + |
| 302 | + RXMESH_INFO("Assembly took {} ms", assembly_ms); |
| 303 | + if (solver_name == "cudss") { |
| 304 | + RXMESH_INFO("cuDSS analysis and factorization took {} ms", |
| 305 | + solve_stats.pre_solve_ms); |
| 306 | + } else { |
| 307 | + RXMESH_INFO("Solver took {} iterations and final solver residual = {}", |
| 308 | + solve_stats.total_iterations, |
| 309 | + solve_stats.final_solver_residual); |
| 310 | + } |
| 311 | + RXMESH_INFO("{} heat steps took {} ms ({} ms/step)", |
| 312 | + steps, |
| 313 | + solve_stats.time_stepping_ms, |
| 314 | + solve_stats.time_stepping_ms / static_cast<float>(steps)); |
| 315 | + |
| 316 | + |
| 317 | + RXMESH_INFO("Final ordinary relative residual = {}", relative_residual); |
| 318 | + |
| 319 | + |
| 320 | +#if USE_POLYSCOPE |
| 321 | + temperature.move(DEVICE, HOST); |
| 322 | + auto final_temperature = *rx.add_vertex_attribute<T>("temperature", 1); |
| 323 | + final_temperature.from_matrix(&temperature); |
| 324 | + rx.get_polyscope_volume_mesh() |
| 325 | + ->addVertexScalarQuantity("temperature", final_temperature) |
| 326 | + ->setEnabled(true); |
| 327 | + polyscope::show(); |
| 328 | + |
| 329 | +#endif |
| 330 | + |
| 331 | + residual.release(); |
| 332 | + rhs.release(); |
| 333 | + temperature.release(); |
| 334 | + system.release(); |
| 335 | + |
| 336 | + return 0; |
| 337 | +} |
0 commit comments