Skip to content

Commit 9f075ea

Browse files
committed
Add added-mass determinism unit test and solver sweep
Introduce a self-contained unit test (test_added_mass_determinism) that verifies cross-coupled added-mass matrix assembly through ChLoadHydrodynamics and checks determinism across 12 independent trials with heap perturbation. Includes an informational solver sweep that reports assembly correctness and dynamics consistency for all available Chrono solver types (SPARSE_QR, SPARSE_LU, MINRES, GMRES, BICGSTAB, PSOR, BARZILAIBORWEIN, APGD). Also adds unit test CMake infrastructure, developer documentation with reference solver compatibility table, and simplifies the Python DLL install logic in the root CMakeLists.
1 parent 978727f commit 9f075ea

5 files changed

Lines changed: 466 additions & 22 deletions

File tree

CMakeLists.txt

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -443,11 +443,6 @@ target_include_directories(HydroChronoGUI
443443
"$<INSTALL_INTERFACE:include>"
444444
)
445445

446-
if(MSVC)
447-
target_compile_options(HydroChronoGUI PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/wd4996>) # deprecated function or class member
448-
target_compile_options(HydroChronoGUI PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/wd4458>) # declaration hides class member
449-
endif()
450-
451446
set_target_properties(HydroChronoGUI PROPERTIES POSITION_INDEPENDENT_CODE ON)
452447
target_link_libraries(HydroChronoGUI
453448
PUBLIC
@@ -548,6 +543,7 @@ if(HYDROCHRONO_ENABLE_TESTS)
548543
endif()
549544

550545
add_subdirectory(tests/regression)
546+
add_subdirectory(tests/unit)
551547
endif()
552548

553549
# ===============================================================================
@@ -697,23 +693,16 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
697693
endif()
698694
endif()
699695

700-
# Python DLLs, if Chrono::Parsers depends on it
701-
if(CHRONO_PARSERS_PYTHON)
702-
find_package(Python3 QUIET COMPONENTS Interpreter Development)
703-
if(Python3_Interpreter_FOUND AND Python3_Development_FOUND)
704-
#message(STATUS "Found Python and dependencies")
705-
#message(STATUS " Python3_Interpreter_FOUND: ${Python3_Interpreter_FOUND}")
706-
#message(STATUS " Python3_Development_FOUND: ${Python3_Development_FOUND}")
707-
get_target_property(tgt_DLL Python3::Python IMPORTED_LOCATION_RELEASE)
708-
get_target_property(tgt_DLL_d Python3::Python IMPORTED_LOCATION_DEBUG)
709-
if(EXISTS ${tgt_DLL})
710-
message(STATUS " Python DLL ${tgt_DLL}")
711-
install(FILES ${tgt_DLL} DESTINATION bin COMPONENT runtime)
712-
endif()
713-
if(EXISTS ${tgt_DLL_d})
714-
install(FILES ${tgt_DLL_d} DESTINATION bin COMPONENT runtime)
715-
endif()
716-
endif()
696+
# Python DLLs, if Chrono::Parsers depends on it.
697+
# Derive the DLL path from the already-found interpreter rather than calling
698+
# find_package(Python3 ... Development), which can stall on Windows/conda.
699+
if(CHRONO_PARSERS_PYTHON AND Python3_EXECUTABLE)
700+
get_filename_component(_py_bin_dir "${Python3_EXECUTABLE}" DIRECTORY)
701+
file(GLOB _py_dlls "${_py_bin_dir}/python3*.dll")
702+
foreach(_py_dll ${_py_dlls})
703+
message(STATUS " Python DLL ${_py_dll}")
704+
install(FILES "${_py_dll}" DESTINATION bin COMPONENT runtime)
705+
endforeach()
717706
endif()
718707

719708
endif()

docs/_main_pages/developer_docs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ title: Developer Documentation
66
Welcome to the Developer Documentation section of HydroChrono. Use these guides to build, understand, and contribute to the project.
77

88
- [Build & Setup]({{ site.baseurl }}/developer_docs/build_instructions) - install dependencies, configure, build, and run tests
9+
- [Unit Tests]({{ site.baseurl }}/developer_docs/unit_tests) - C++ unit tests for internal correctness (added-mass assembly, determinism)
910
- [Contribution Guidelines]({{ site.baseurl }}/developer_docs/contribution_guidelines) - coding conventions, best practices, MUST/SHOULD summary, PR checklist
1011
- [CMake Build Basics]({{ site.baseurl }}/developer_docs/cmake_build_basics) - generators, single vs multi-config, build types
1112
- [CMake Build Structure]({{ site.baseurl }}/developer_docs/cmake_build_structure) - how `CMakeLists.txt` is organized, targets, install/packaging
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
layout: page
3+
title: Unit Tests
4+
parent_section: Developer Documentation
5+
---
6+
7+
# Unit Tests
8+
9+
Unit tests verify internal correctness of HydroChrono and its integration with Project Chrono at the C++ level. They are self-contained (no external data files required) and typically run in under a second.
10+
11+
## Running unit tests
12+
13+
```powershell
14+
ctest -C Release -L unit --test-dir build
15+
```
16+
17+
Useful flags:
18+
19+
| Flag | Description |
20+
|------|-------------|
21+
| `-V` | Verbose — show full test output (matrix dumps, signatures) |
22+
| `--output-on-failure` | Only show output when a test fails |
23+
| `-R <pattern>` | Run only tests matching a name pattern |
24+
25+
Example with verbose output:
26+
```powershell
27+
ctest -C Release -L unit --test-dir build -V
28+
```
29+
30+
## Test: added-mass assembly (`test_added_mass_determinism`)
31+
32+
Verifies that Chrono's `ChLoadHydrodynamics` correctly assembles a cross-coupled added-mass matrix for a multi-body system.
33+
34+
### What it does
35+
36+
1. **Constructs** a fully dense, symmetric positive-definite 18×18 added-mass matrix for 3 bodies, with non-trivial off-diagonal coupling between all body pairs and all 6 DOFs
37+
2. **Passes** the per-body 6×18 row blocks to `ChLoadHydrodynamics` (the same path HydroChrono uses at runtime)
38+
3. **Extracts** the assembled system mass matrix from Chrono, subtracts body inertia, and asserts it matches the original input
39+
4. **Runs 12 independent trials** with fresh system instances and heap perturbation between them, asserting bit-identical velocity results across all trials
40+
5. **Confirms** the added mass is actively influencing the dynamics (non-trivial velocity change after one timestep)
41+
6. **Sweeps across Chrono solver types** and reports a summary table showing which solvers correctly preserve the full added-mass matrix and produce consistent dynamics
42+
43+
### What it catches
44+
45+
- Incorrect scatter of added-mass blocks into the system matrix (e.g., wrong row/column placement)
46+
- Ordering sensitivity from internal container changes (e.g., `unordered_map` vs `vector`)
47+
- Non-deterministic behaviour across independent system instances
48+
- Silent corruption of off-diagonal cross-coupling terms
49+
- Solver-specific issues with added-mass handling (e.g., solvers that cannot handle stiffness/damping matrices, or iterative solvers with convergence differences)
50+
51+
### Solver sweep
52+
53+
The test runs the same added-mass assembly and single-step dynamics with every available Chrono solver type and prints a summary table:
54+
55+
| Column | Meaning |
56+
|--------|---------|
57+
| Assembly | Whether the extracted added-mass matrix matches the input |
58+
| NaN/Inf | Whether velocities contain invalid values |
59+
| Dynamics | Whether post-step velocities match the SPARSE_QR reference |
60+
61+
The solver sweep is **informational only** — it does not affect the test PASS/FAIL status. Some solvers may not support this problem type (e.g., PSOR rejects systems with stiffness/damping matrices) or may converge to slightly different results (e.g., APGD). The table makes these differences visible without false positives.
62+
63+
### Reference results
64+
65+
The table below summarises the results observed when the test was last run. Run the test with `-V` to regenerate it against your current Chrono build.
66+
67+
| Solver | Assembly | NaN/Inf | Dynamics | Notes |
68+
|--------|----------|---------|----------|-------|
69+
| SPARSE_QR | MATCH | clean | MATCH | Reference solver used by HydroChrono |
70+
| SPARSE_LU | MATCH | clean | MATCH | |
71+
| MINRES | MATCH | clean | MATCH | |
72+
| GMRES | MATCH | clean | MATCH | |
73+
| BICGSTAB | MATCH | clean | MATCH | |
74+
| PSOR | ERROR ||| Cannot handle stiffness/damping matrices |
75+
| BARZILAIBORWEIN | MATCH | clean | MATCH | |
76+
| APGD | MATCH | clean | diff ~6.7e-3 | Assembly correct; iterative convergence differs slightly |
77+
78+
**Key takeaways:**
79+
80+
- **Assembly is solver-independent.** Every solver that can run produces an added-mass matrix that matches the input exactly, including all off-diagonal cross-coupling terms. Choosing a different solver does not lose structural information from the mass matrix.
81+
- **PSOR is incompatible.** `ChLoadHydrodynamics` contributes stiffness/damping matrices that PSOR cannot handle. This is a Chrono limitation, not an assembly issue. HydroChrono should never be configured to use PSOR.
82+
- **APGD assembles correctly but converges differently.** The ~6.7e-3 velocity difference is a convergence characteristic of the iterative APGD solver, not a mass-matrix problem. If tighter agreement is needed, use a direct solver.
83+
- **All other solvers (direct and iterative) produce bit-identical dynamics.**
84+
85+
### Verbose output
86+
87+
When run with `-V`, the test prints the input blocks, the assembled matrix extracted from Chrono, eigenvalues, post-step velocity signatures, and the solver sweep summary table — making it straightforward to visually inspect what went into the solver and what came out.

tests/unit/CMakeLists.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# ==============================================================================
2+
# Unit tests — standalone executables, no external data dependencies
3+
# ==============================================================================
4+
5+
# Helper: build a self-contained unit test and register with CTest
6+
function(build_unit_test TEST_NAME)
7+
set(TEST_SRC ${TEST_NAME}.cpp)
8+
set(TEST_EXE ${TEST_NAME})
9+
10+
add_executable(${TEST_EXE} ${TEST_SRC})
11+
target_link_libraries(${TEST_EXE} PRIVATE HydroChrono)
12+
target_compile_features(${TEST_EXE} PRIVATE ${HC_CXX_STANDARD})
13+
14+
add_test(
15+
NAME ${TEST_EXE}
16+
WORKING_DIRECTORY "$<TARGET_FILE_DIR:${TEST_EXE}>"
17+
COMMAND $<TARGET_FILE:${TEST_EXE}>
18+
)
19+
20+
set_tests_properties(${TEST_EXE}
21+
PROPERTIES LABELS "unit"
22+
)
23+
24+
# DLL search paths on Windows (reuse parent-scope variable if set)
25+
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows" AND DEFINED TEST_ENVIRONMENT)
26+
set_tests_properties(${TEST_EXE}
27+
PROPERTIES ENVIRONMENT "${TEST_ENVIRONMENT}"
28+
)
29+
endif()
30+
endfunction()
31+
32+
# --- Register unit tests ---
33+
message(STATUS "Add HydroChrono unit tests")
34+
build_unit_test(test_added_mass_determinism)

0 commit comments

Comments
 (0)