|
10 | 10 | #include <hydroc/chloadaddedmass.h> |
11 | 11 | #include <hydroc/h5fileinfo.h> |
12 | 12 | #include <hydroc/wave_types.h> |
| 13 | +#include <hydroc/logging.h> |
13 | 14 |
|
14 | 15 | #include <chrono/physics/ChLoad.h> |
15 | 16 | #include <unsupported/Eigen/Splines> |
|
25 | 26 | #include <stdexcept> |
26 | 27 | #include <vector> |
27 | 28 | #include <chrono> |
| 29 | +#include <filesystem> |
28 | 30 |
|
29 | 31 | #ifdef _OPENMP |
30 | 32 | #include <omp.h> |
@@ -376,13 +378,171 @@ inline bool AdvanceToBracket(const std::vector<double>& time_history, |
376 | 378 | } |
377 | 379 | } // namespace |
378 | 380 |
|
| 381 | +// Preprocess the radiation kernel K(t) per body for TaperedDirect mode. |
| 382 | +void TestHydro::EnsureProcessedRIRF() { |
| 383 | + if (rirf_processed_ready_) { |
| 384 | + return; |
| 385 | + } |
| 386 | + |
| 387 | + const int steps = file_info_.GetRIRFDims(2); |
| 388 | + const int cols = kDofPerBody * num_bodies_; |
| 389 | + const int rows = kDofPerBody; |
| 390 | + |
| 391 | + rirf_processed_.clear(); |
| 392 | + rirf_processed_.resize(num_bodies_); |
| 393 | + |
| 394 | + // SG smoothing coefficients for 5-point quadratic: [-3, 12, 17, 12, -3] / 35 |
| 395 | + const double sg5[5] = { -3.0 / 35.0, 12.0 / 35.0, 17.0 / 35.0, 12.0 / 35.0, -3.0 / 35.0 }; |
| 396 | + |
| 397 | + for (int b = 0; b < num_bodies_; ++b) { |
| 398 | + Eigen::Tensor<double, 3> processed(rows, cols, steps); |
| 399 | + |
| 400 | + // Calculate effective steps for this body (same for all channels) |
| 401 | + int effective_steps = steps; |
| 402 | + if (tapered_opts_.rirf_end_time > 0.0) { |
| 403 | + // Calculate the step index corresponding to the end time |
| 404 | + double dt = rirf_time_vector[1] - rirf_time_vector[0]; // assume uniform time step |
| 405 | + int end_step = static_cast<int>(std::floor(tapered_opts_.rirf_end_time / dt)); |
| 406 | + effective_steps = std::min(end_step, steps); |
| 407 | + } |
| 408 | + |
| 409 | + // Diagnostic aggregation across channels |
| 410 | + int max_tc_index = 0; |
| 411 | + int max_taper_len = 0; |
| 412 | + int max_effective_len = steps; |
| 413 | + |
| 414 | + for (int row_dof = 0; row_dof < rows; ++row_dof) { |
| 415 | + for (int col = 0; col < cols; ++col) { |
| 416 | + // Load raw (rho-scaled) kernel time series |
| 417 | + std::vector<double> k_raw(steps); |
| 418 | + for (int s = 0; s < steps; ++s) { |
| 419 | + k_raw[s] = file_info_.GetRIRFVal(b, row_dof, col, s); |
| 420 | + } |
| 421 | + |
| 422 | + // Apply RIRF truncation if specified |
| 423 | + if (tapered_opts_.rirf_end_time > 0.0) { |
| 424 | + // Truncate the raw kernel |
| 425 | + k_raw.resize(effective_steps); |
| 426 | + } |
| 427 | + |
| 428 | + // Light smoothing |
| 429 | + std::vector<double> k_smooth(effective_steps); |
| 430 | + if (tapered_opts_.smoothing == "moving_average") { |
| 431 | + const int w = std::max(3, tapered_opts_.window_length); |
| 432 | + const int half = w / 2; |
| 433 | + for (int s = 0; s < effective_steps; ++s) { |
| 434 | + int a = std::max(0, s - half); |
| 435 | + int b = std::min(effective_steps - 1, s + half); |
| 436 | + double sum = 0.0; int cnt = 0; |
| 437 | + for (int i = a; i <= b; ++i) { sum += k_raw[i]; ++cnt; } |
| 438 | + k_smooth[s] = (cnt > 0) ? (sum / cnt) : k_raw[s]; |
| 439 | + } |
| 440 | + } else { |
| 441 | + // default: SG quadratic with window 5 |
| 442 | + if (effective_steps >= 5) { |
| 443 | + // copy edges as-is for simplicity |
| 444 | + k_smooth[0] = k_raw[0]; |
| 445 | + k_smooth[1] = k_raw[1]; |
| 446 | + for (int s = 2; s <= effective_steps - 3; ++s) { |
| 447 | + k_smooth[s] = sg5[0] * k_raw[s - 2] + sg5[1] * k_raw[s - 1] + sg5[2] * k_raw[s] + sg5[3] * k_raw[s + 1] + sg5[4] * k_raw[s + 2]; |
| 448 | + } |
| 449 | + k_smooth[effective_steps - 2] = k_raw[effective_steps - 2]; |
| 450 | + k_smooth[effective_steps - 1] = k_raw[effective_steps - 1]; |
| 451 | + } else { |
| 452 | + k_smooth = k_raw; // too short to smooth |
| 453 | + } |
| 454 | + } |
| 455 | + |
| 456 | + // Simple taper control: start and end percentages |
| 457 | + int tc_index = static_cast<int>(std::floor(tapered_opts_.taper_start_percent * static_cast<double>(effective_steps))); |
| 458 | + int tc_end = static_cast<int>(std::floor(tapered_opts_.taper_end_percent * static_cast<double>(effective_steps))); |
| 459 | + tc_index = std::max(0, std::min(tc_index, effective_steps)); |
| 460 | + tc_end = std::max(tc_index, std::min(tc_end, effective_steps)); |
| 461 | + |
| 462 | + // Apply half-cosine taper from start to end percentage |
| 463 | + int taper_len = tc_end - tc_index; |
| 464 | + int effective_len = tc_end; |
| 465 | + |
| 466 | + const double pi_const = 3.14159265358979323846; |
| 467 | + for (int s = 0; s < effective_steps; ++s) { |
| 468 | + double val = k_smooth[s]; |
| 469 | + if (s < tc_index) { |
| 470 | + // keep original value |
| 471 | + } else if (s < tc_end && taper_len > 0) { |
| 472 | + double t = (static_cast<double>(s - tc_index)) / static_cast<double>(taper_len); |
| 473 | + // Half-cosine taper: goes from 1.0 to taper_final_amplitude |
| 474 | + // taper_final_amplitude = 0.0 means complete zero, 1.0 means no change |
| 475 | + double w = tapered_opts_.taper_final_amplitude + (1.0 - tapered_opts_.taper_final_amplitude) * 0.5 * (1.0 + std::cos(pi_const * t)); |
| 476 | + val *= w; |
| 477 | + } else { |
| 478 | + val = 0.0; |
| 479 | + } |
| 480 | + processed(row_dof, col, s) = val; |
| 481 | + } |
| 482 | + |
| 483 | + // Zero out any remaining steps beyond effective_steps |
| 484 | + for (int s = effective_steps; s < steps; ++s) { |
| 485 | + processed(row_dof, col, s) = 0.0; |
| 486 | + } |
| 487 | + |
| 488 | + if (tc_index > max_tc_index) max_tc_index = tc_index; |
| 489 | + if (taper_len > max_taper_len) max_taper_len = taper_len; |
| 490 | + if (effective_len > max_effective_len) max_effective_len = effective_len; |
| 491 | + } |
| 492 | + } |
| 493 | + |
| 494 | + rirf_processed_[b] = std::move(processed); |
| 495 | + |
| 496 | + LOG_DEBUG("TaperedDirect kernel (body " << b |
| 497 | + << ") Start: " << tapered_opts_.taper_start_percent |
| 498 | + << ", End: " << tapered_opts_.taper_end_percent |
| 499 | + << ", Final Amp: " << tapered_opts_.taper_final_amplitude |
| 500 | + << ", RIRF End Time: " << (tapered_opts_.rirf_end_time > 0.0 ? std::to_string(tapered_opts_.rirf_end_time) + "s" : "full") |
| 501 | + << ", Max Tc index: " << max_tc_index |
| 502 | + << ", Max taper length: " << max_taper_len |
| 503 | + << ", Max effective length: " << max_effective_len |
| 504 | + << "/" << steps); |
| 505 | + |
| 506 | + if (tapered_opts_.export_plot_csv) { |
| 507 | + // Write small CSV summaries for inspection: times, representative channel before/after |
| 508 | + try { |
| 509 | + const std::string base = std::string("rirf_body") + std::to_string(b) + std::string("_summary.csv"); |
| 510 | + std::filesystem::path out_dir = diagnostics_output_dir_.empty() ? std::filesystem::current_path() : std::filesystem::path(diagnostics_output_dir_); |
| 511 | + std::filesystem::path out_path = out_dir / base; |
| 512 | + std::ofstream ofs(out_path.string()); |
| 513 | + ofs << "step,time,k_before,k_after\n"; |
| 514 | + // pick row=0,col=0 as representative |
| 515 | + for (int s = 0; s < effective_steps; ++s) { |
| 516 | + double t = (s < rirf_time_vector.size()) ? rirf_time_vector[s] : static_cast<double>(s); |
| 517 | + double before = file_info_.GetRIRFVal(b, 0, 0, s); |
| 518 | + double after = rirf_processed_[b](0, 0, s); |
| 519 | + ofs << s << "," << t << "," << before << "," << after << "\n"; |
| 520 | + } |
| 521 | + // Only log the directory once (body 0) |
| 522 | + if (b == 0) { |
| 523 | + LOG_INFO("RIRF CSVs written in " << out_dir.string()); |
| 524 | + } |
| 525 | + } catch (...) { |
| 526 | + // ignore export errors |
| 527 | + } |
| 528 | + } |
| 529 | + } |
| 530 | + |
| 531 | + rirf_processed_ready_ = true; |
| 532 | +} |
| 533 | + |
379 | 534 | std::vector<double> TestHydro::ComputeForceRadiationDampingConv() { |
380 | 535 | auto __t0 = std::chrono::steady_clock::now(); |
381 | 536 | const int rirf_steps = file_info_.GetRIRFDims(2); |
382 | 537 | const int total_dofs = kDofPerBody * num_bodies_; |
383 | 538 |
|
384 | 539 | assert(total_dofs > 0 && rirf_steps > 0); |
385 | 540 |
|
| 541 | + // If using TaperedDirect, ensure processed kernel is ready before any parallel region |
| 542 | + if (convolution_mode_ == RadiationConvolutionMode::TaperedDirect) { |
| 543 | + EnsureProcessedRIRF(); |
| 544 | + } |
| 545 | + |
386 | 546 | // Current time and minimum history time window required |
387 | 547 | const double simulation_time = bodies_[0]->GetChTime(); |
388 | 548 | const int rirf_last_index = static_cast<int>(rirf_time_vector.size()) - 1; |
@@ -537,6 +697,13 @@ double TestHydro::GetRIRFval(int row, int col, int st) { |
537 | 697 | int col_dof = col % kDofPerBody; |
538 | 698 | int row_dof = row % kDofPerBody; |
539 | 699 |
|
| 700 | + if (convolution_mode_ == RadiationConvolutionMode::TaperedDirect) { |
| 701 | + EnsureProcessedRIRF(); |
| 702 | + // processed tensor is scaled by rho already |
| 703 | + const auto& tensor = rirf_processed_[body_index]; |
| 704 | + return tensor(row_dof, col, st); |
| 705 | + } |
| 706 | + |
540 | 707 | return file_info_.GetRIRFVal(body_index, row_dof, col, st); |
541 | 708 | } |
542 | 709 |
|
|
0 commit comments