diff --git a/.github/workflows/header_checks.yml b/.github/workflows/header_checks.yml index 1fbd51db82c..1f3b3b91a30 100644 --- a/.github/workflows/header_checks.yml +++ b/.github/workflows/header_checks.yml @@ -39,13 +39,14 @@ jobs: Get-Command mingw32-make | Select-Object -ExpandProperty Definition shell: powershell - - name: Build Math libs - shell: powershell - run: mingw32-make -f make/standalone math-libs -j2 + - name: Install Ninja + run: python -m pip install ninja - name: Run header tests shell: powershell - run: make -j2 test-headers + run: | + cmake -S . -B build -G Ninja -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DSTAN_BUILD_PROB_TESTS=OFF + cmake --build build --parallel 2 --target test-headers opencl: name: OpenCL @@ -57,7 +58,8 @@ jobs: - name: Run header tests run: | echo "STAN_OPENCL=true" > make/local - make -j2 test-headers + cmake -S . -B build -DSTAN_OPENCL=ON -DSTAN_BUILD_PROB_TESTS=OFF + cmake --build build --parallel 2 --target test-headers no_range_checks: name: NoRange runs-on: ubuntu-latest diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ccba1e931b3..8f34d0c9cd2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,25 +48,24 @@ jobs: with: r-version: 'release' - - name: Build Math libs + - name: Configure test tools shell: pwsh run: | - echo "$(pwd)/lib/tbb" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 + python -m pip install ninja Add-Content make\local "CPPFLAGS=-w -Wno-psabi -Wno-misleading-indentation`n" Add-Content make\local "STAN_THREADS=true`n" - make -f make/standalone math-libs -j2 - name: Run ${{ matrix.config.label }} unit tests shell: pwsh run: | - python runTests.py -j4 ${{ matrix.config.tests }} + python runTests.py --cmake-arg=-GNinja --cmake-arg=-DCMAKE_C_COMPILER=gcc --cmake-arg=-DCMAKE_CXX_COMPILER=g++ -j4 ${{ matrix.config.tests }} - name: Upload gtest_output xml uses: actions/upload-artifact@v7 if: failure() with: name: gtest_outputs_xml - path: '**/*_test.xml' + path: 'build/test-results/*.xml' mix-fun: name: mix fun and prob tests ${{ matrix.group }} (${{ matrix.os }}) @@ -91,13 +90,12 @@ jobs: r-version: 'release' - - name: Build Math libs + - name: Configure test tools shell: pwsh run: | - echo "$(pwd)/lib/tbb" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 + python -m pip install ninja Add-Content make\local "CPPFLAGS=-w -Wno-psabi -Wno-misleading-indentation`n" Add-Content make\local "STAN_THREADS=true`n" - make -f make/standalone math-libs -j2 - name: Run mix/fun unit tests shell: pwsh @@ -114,11 +112,11 @@ jobs: $MixFunProbTests[(4 * $FifthNumberTests)..($NumberTests - 1)] ` ) - python runTests.py -j2 $MixFunProbTestsArray[(${{ matrix.group }} - 1)] + python runTests.py --cmake-arg=-GNinja --cmake-arg=-DCMAKE_C_COMPILER=gcc --cmake-arg=-DCMAKE_CXX_COMPILER=g++ -j2 $MixFunProbTestsArray[(${{ matrix.group }} - 1)] - name: Upload gtest_output xml uses: actions/upload-artifact@v7 if: failure() with: name: gtest_outputs_xml - path: '**/*_test.xml' + path: 'build/test-results/*.xml' diff --git a/.gitignore b/.gitignore index c36591f03d8..7ba777a5e88 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,9 @@ lib/tbb/** # gdb debug history .gdb_history + +# CMake build trees +/build/ +/build-*/ +/.build/ +/.build-*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000000..e3c1b93dff0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,192 @@ +cmake_minimum_required(VERSION 3.16.3) +include(cmake/MakeLocal.cmake) +# ReleaseTesting optimizes like Release but keeps assertions on (no NDEBUG), +# matching make's test builds. runTests.py uses it by default. +set(CMAKE_C_FLAGS_RELEASETESTING_INIT -O1) +set(CMAKE_CXX_FLAGS_RELEASETESTING_INIT -O1) +project(stanmath LANGUAGES C CXX) +stan_apply_make_local_flags() + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "Use an out-of-source build: cmake -S . -B build") +endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) +stan_cache_default(CMAKE_POSITION_INDEPENDENT_CODE ON BOOL + "Compile position-independent code and link executables as PIE") +# Link executables as PIE to match their position-independent objects. +include(CheckPIESupported) +check_pie_supported() + +option(STAN_BUILD_DOCS "Build the Stan Math library documentation" OFF) +option(STAN_TEST_HEADERS "Create targets that check standalone headers" ON) +option(STAN_TEST_PCH "Reuse precompiled headers when building tests" ON) +option(STAN_BUILD_PROB_TESTS "Generate and build distribution tests" ON) +option(STAN_BUILD_EXPRESSION_TESTS "Build pre-generated expression tests" OFF) +option(STAN_TEST_ROW_VECTORS "Include row vectors in distribution tests" OFF) +option(STAN_PROB_TEST_ALL "Test all distribution autodiff modes" OFF) +option(STAN_NO_RANGE_CHECKS "Disable range checks within the Stan library" OFF) +option(STAN_MPI "Enable MPI support" OFF) +option(STAN_OPENCL "Enable OpenCL support" OFF) +option(STAN_THREADS "Enable multi-threading support" OFF) +option(STAN_SANITIZE_ADDRESS "Compile C++ sources and link with AddressSanitizer" OFF) +stan_cache_default(STAN_OPENCL_DEVICE_ID "0" STRING "OpenCL device ID") +stan_cache_default(STAN_OPENCL_PLATFORM_ID "0" STRING "OpenCL platform ID") +stan_cache_default(STAN_INTEGRATED_OPENCL "0" STRING "Enable integrated OpenCL features") +set(STAN_TEST_NAME_FILTERS "" CACHE STRING "Required substrings in selected test source paths") +set(STAN_SELECTED_TEST_REGEX "" CACHE STRING + "Tests whose source paths match this regex are built by stan_selected_tests") +include(CTest) +stan_cache_default(STAN_TEST_OPTIMIZATION "1" STRING "Optimization level for tests and solver libraries") +if(DEFINED CACHE{STAN_TEST_OPTIMIZATION}) + set_property(CACHE STAN_TEST_OPTIMIZATION PROPERTY STRINGS 0 1 2 3) +endif() +if(NOT STAN_TEST_OPTIMIZATION MATCHES "^[0-3]$") + message(FATAL_ERROR "STAN_TEST_OPTIMIZATION must be 0, 1, 2, or 3") +endif() +# Keep solver libraries at the same optimization level as the C++ tests. +if(MSVC) + if(STAN_TEST_OPTIMIZATION STREQUAL "0") + set(STAN_TEST_OPTIMIZATION_FLAGS /Od) + elseif(STAN_TEST_OPTIMIZATION STREQUAL "1") + set(STAN_TEST_OPTIMIZATION_FLAGS /O1) + else() + set(STAN_TEST_OPTIMIZATION_FLAGS /O2) + endif() +else() + set(STAN_TEST_OPTIMIZATION_FLAGS -O${STAN_TEST_OPTIMIZATION}) +endif() +# Offer ReleaseTesting to multi-config generators such as Visual Studio. +get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(multi_config AND NOT "ReleaseTesting" IN_LIST CMAKE_CONFIGURATION_TYPES) + list(APPEND CMAKE_CONFIGURATION_TYPES ReleaseTesting) + set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING + "Available build configurations" FORCE) +endif() +# Like Make's CXXFLAGS, reach every C++ compile and link, but not the +# vendored TBB build, which takes its own flags. +if(STAN_SANITIZE_ADDRESS) + if(MSVC) + add_compile_options(/fsanitize=address) + else() + add_compile_options($<$:-fsanitize=address>) + add_link_options(-fsanitize=address) + endif() +endif() + +add_library(stanmath INTERFACE) +add_library(stanmath::stanmath ALIAS stanmath) +target_compile_features(stanmath INTERFACE cxx_std_17) +target_include_directories(stanmath INTERFACE "${PROJECT_SOURCE_DIR}") +target_compile_definitions(stanmath INTERFACE BOOST_DISABLE_ASSERTS TBB_INTERFACE_NEW _REENTRANT) +foreach(feature STAN_NO_RANGE_CHECKS STAN_THREADS STAN_MPI) + if(${feature}) + target_compile_definitions(stanmath INTERFACE ${feature}) + endif() +endforeach() +add_subdirectory(cmake) + +if(STAN_OPENCL) + # Configuration and object-only header checks need headers, not a loader. + # Respect explicit FindOpenCL overrides while defaulting to vendored headers. + if(NOT DEFINED OpenCL_INCLUDE_DIR) + set(OpenCL_INCLUDE_DIR "${STAN_OPENCL_ROOT}") + endif() + find_package(OpenCL QUIET) + target_include_directories(stanmath SYSTEM INTERFACE "${OpenCL_INCLUDE_DIR}") + if(TARGET OpenCL::OpenCL) + target_link_libraries(stanmath INTERFACE OpenCL::OpenCL) + elseif(APPLE) + target_link_libraries(stanmath INTERFACE "-framework OpenCL") + else() + # Keep the ordinary platform link requirement even without a found loader. + target_link_libraries(stanmath INTERFACE OpenCL) + endif() + target_compile_definitions(stanmath INTERFACE + STAN_OPENCL OPENCL_DEVICE_ID=${STAN_OPENCL_DEVICE_ID} + OPENCL_PLATFORM_ID=${STAN_OPENCL_PLATFORM_ID} + CL_HPP_TARGET_OPENCL_VERSION=120 CL_HPP_MINIMUM_OPENCL_VERSION=120 + CL_HPP_ENABLE_EXCEPTIONS INTEGRATED_OPENCL=${STAN_INTEGRATED_OPENCL}) +endif() +if(STAN_MPI) + find_package(MPI REQUIRED COMPONENTS CXX) + if(STAN_USE_SYSTEM_BOOST) + find_package(Boost REQUIRED COMPONENTS mpi serialization) + else() + # Build the MPI libraries against the same headers used by Stan; mixing + # vendored headers with an older system Boost can change serialization ABI. + file(GLOB serialization_sources CONFIGURE_DEPENDS + "${STAN_BOOST_ROOT}/libs/serialization/src/*.cpp") + add_library(stan_boost_serialization STATIC ${serialization_sources}) + add_library(Boost::serialization ALIAS stan_boost_serialization) + target_include_directories(stan_boost_serialization SYSTEM PUBLIC "${STAN_BOOST_ROOT}") + file(GLOB mpi_sources CONFIGURE_DEPENDS "${STAN_BOOST_ROOT}/libs/mpi/src/*.cpp") + add_library(stan_boost_mpi STATIC ${mpi_sources}) + add_library(Boost::mpi ALIAS stan_boost_mpi) + target_compile_definitions(stan_boost_mpi PRIVATE BOOST_MPI_SOURCE=1) + target_compile_definitions(stan_boost_mpi INTERFACE BOOST_MPI_NO_LIB=1) + target_link_libraries(stan_boost_mpi PUBLIC Boost::serialization MPI::MPI_CXX) + endif() + target_link_libraries(stanmath INTERFACE Boost::mpi Boost::serialization MPI::MPI_CXX) + add_library(stan_mpi OBJECT stan/math/prim/functor/mpi_cluster_inst.cpp) + target_link_libraries(stan_mpi PRIVATE stanmath) +endif() + +message(STATUS "Stan Math: compiler=${CMAKE_CXX_COMPILER} " + "(${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}), " + "build type=${CMAKE_BUILD_TYPE}, test optimization=${STAN_TEST_OPTIMIZATION}") +message(STATUS "Stan Math: CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}, " + "CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}") +message(STATUS "Stan Math: threads=${STAN_THREADS}, OpenCL=${STAN_OPENCL}, MPI=${STAN_MPI}, " + "AddressSanitizer=${STAN_SANITIZE_ADDRESS}") +set(STAN_EFFECTIVE_MPI "${STAN_MPI}" CACHE INTERNAL "MPI setting used by test executables") +include(cmake/StanTest.cmake) +if(BUILD_TESTING) + add_subdirectory(test) +endif() +if(STAN_BUILD_DOCS) + find_package(Doxygen REQUIRED) + add_custom_target(doxygen + COMMAND ${CMAKE_COMMAND} -E make_directory doc/api + COMMAND ${DOXYGEN_EXECUTABLE} doxygen/doxygen.cfg + COMMAND ${CMAKE_COMMAND} -E copy doxygen/pretty_stuff/eigen_navtree_hacks.js + doc/api/html/eigen_navtree_hacks.js + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + COMMENT "Generating API documentation with Doxygen" + VERBATIM) +endif() + +find_package(Python3 COMPONENTS Interpreter) +if(Python3_FOUND) + # The same checks as make/cpplint and Make's test-math-dependencies. + set(cpplint_command "${Python3_EXECUTABLE}" lib/cpplint_2.0.2/cpplint.py + --output=vs7 --counting=detailed --root=stan --extensions=hpp,cpp + "--filter=-runtime/indentation_namespace,-build/c++11,-readability/namespace,-legal/copyright,-whitespace/indent,-build/header_guard,-build/include_order,-build/include_what_you_use,-runtime/string,-build/namespaces") + # cpplint's --recursive needs Python 3.9, so pass the files as make/cpplint does. + set(cpplint_commands "") + foreach(directory stan test/unit) + file(GLOB_RECURSE lint_sources CONFIGURE_DEPENDS RELATIVE "${PROJECT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/${directory}/*.hpp" "${PROJECT_SOURCE_DIR}/${directory}/*.cpp") + list(APPEND cpplint_commands COMMAND ${cpplint_command} ${lint_sources}) + endforeach() + add_custom_target(cpplint ${cpplint_commands} + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + VERBATIM) + add_custom_target(test-math-dependencies + COMMAND "${Python3_EXECUTABLE}" runChecks.py + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + VERBATIM) +endif() + +set(STAN_COMPILE_TEST_SOURCE "" CACHE FILEPATH "Source file for compile-only compatibility checks") +if(STAN_COMPILE_TEST_SOURCE) + # Only GoogleTest's headers are needed; its targets exist only with tests. + add_library(stan_compile_test_options INTERFACE) + target_link_libraries(stan_compile_test_options INTERFACE stan_test_flags) + target_include_directories(stan_compile_test_options SYSTEM INTERFACE + "${STAN_BENCHMARK_ROOT}/googletest/googletest/include") + stan_add_pch(stan_compile_test_pch stan_compile_test_options ) + add_library(stan_compile_test OBJECT EXCLUDE_FROM_ALL "${STAN_COMPILE_TEST_SOURCE}") + stan_configure_test(stan_compile_test stan_compile_test_options stan_compile_test_pch) +endif() diff --git a/Jenkinsfile b/Jenkinsfile index b194b911beb..1e832d2d877 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,20 +15,43 @@ properties([ def image = 'stanorg/ci:v1' def runRemainingStages = false def CLANG_CXX = 'clang++-7' +def CLANG_CC = 'clang-7' def GCC = 'g++' def MPICXX = 'mpicxx.openmpi' def mainBranch = env.BRANCH_NAME == 'develop' || env.BRANCH_NAME == 'master' def noOptimize = !(params.optimizeUnitTests || mainBranch) -def jumboFlags = params.disableJumbo ? '' : ' --jumbo --debug' -def runTests(String local, String args) { - writeFile(file: "make/local", text: local) - sh "cat make/local" - sh "make print-compiler-flags" - sh "python3 runTests.py -j\$PARALLEL $args" - junit 'test/**/*.xml' - sh "find test -name *.xml -delete" +// Quote complete arguments, including compiler flags containing spaces. +def shellQuote(String value) { + return "'" + value.replace("'", "'\"'\"'") + "'" +} + +// Every configure of `build` starts from a fresh cache, so it uses exactly +// these settings. stanorg/ci:v1 has CMake 3.16, which lacks `cmake --fresh` +// (3.24), so the cache and top-level CMakeFiles are deleted instead, which is +// what --fresh does. Objects whose flags are unchanged are not rebuilt. +def cmakeSettings(List settings) { + return ['-DSTAN_USE_MAKE_LOCAL=OFF'] + settings +} + +def configureCmake(List settings) { + sh 'cmake -E remove -f build/CMakeCache.txt' + sh 'cmake -E remove_directory build/CMakeFiles' + sh 'cmake -S . -B build ' + cmakeSettings(settings).collect { shellQuote(it.toString()) }.join(' ') +} + +// runTests.py --fresh does the same cache reset before it configures. +def runnerSettings(List settings) { + return (['--build-dir', 'build', '--fresh'] + cmakeSettings(settings).collect { + "--cmake-arg=${it}" + }).collect { shellQuote(it.toString()) }.join(' ') +} + +def runTests(List settings, String args) { + sh 'cmake -E remove_directory build/test-results' + sh "python3 runTests.py -j\$PARALLEL ${runnerSettings(settings)} $args" + junit(testResults: 'build/test-results/*.xml', allowEmptyResults: true) } catchError { @@ -42,7 +65,7 @@ catchError { runPod(image: image, cpus: 2) { stage('Verify changes') { runRemainingStages = params.run_all || filesChanged( - 'stan', 'make', 'lib', 'test', 'runTests.py', 'runChecks.py', 'makefile', 'Jenkinsfile', '.clang-format') + 'stan', 'make', 'lib', 'test', 'cmake', 'CMakeLists.txt', 'runTests.py', 'runChecks.py', 'makefile', 'Jenkinsfile', '.clang-format') } stage("Clang-format") { @@ -85,11 +108,12 @@ up the autoformatter locally. (Check console output at ${env.BUILD_URL}) } stage('Linting & Doc checks') { - writeFile(file: "make/local", text: "CXX=$CLANG_CXX\nBOOST_PARALLEL_JOBS=\$PARALLEL") + configureCmake(["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DBUILD_TESTING=OFF', '-DSTAN_BUILD_DOCS=ON']) parallel( - CppLint: { sh "make cpplint" }, - Dependencies: { sh "make test-math-dependencies" }, - Documentation: { sh "make doxygen" }, + CppLint: { sh "cmake --build build --target cpplint" }, + Dependencies: { sh "cmake --build build --target test-math-dependencies" }, + Documentation: { sh "cmake --build build --target doxygen" }, ) } } @@ -99,16 +123,18 @@ up the autoformatter locally. (Check console output at ${env.BUILD_URL}) parallel headers: { runPod(image: image) { stage('Headers check') { - writeFile(file: 'make/local', text: "CXX=$CLANG_CXX -Werror") - sh 'make -j$PARALLEL test-headers' + configureCmake(["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_TEST_HEADERS=ON', + '-DSTAN_BUILD_PROB_TESTS=OFF']) + sh 'cmake --build build --parallel $PARALLEL --target test-headers' } } }, unit: { if (env.CHANGE_TARGET) { runPod(image: image) { stage('Run changed unit tests') { - writeFile(file: 'make/local', text: "O=3\nCXXFLAGS+=-march=native -mtune=native") - sh './runTests.py -j$PARALLEL --changed --debug' + runTests(['-DSTAN_TEST_OPTIMIZATION=3', '-DCMAKE_CXX_FLAGS=-march=native -mtune=native'], + '--changed --debug') } } } @@ -120,49 +146,53 @@ up the autoformatter locally. (Check console output at ${env.BUILD_URL}) revfwd: { runPod(image: image) { stage('Rev/Fwd Unit Tests') { - def local = 'CXXFLAGS+= -fsanitize=address\n' - if (noOptimize) - local += 'O=0\n' - runTests(local, "test/unit/math/rev") - runTests(local, "test/unit/math/fwd") + def settings = ['-DSTAN_SANITIZE_ADDRESS=ON', + "-DSTAN_TEST_OPTIMIZATION=${noOptimize ? '0' : '3'}"] + runTests(settings, "test/unit/math/rev") + runTests(settings, "test/unit/math/fwd") } } }, mix: { runPod(image: image, cpus: 8, memory: '192Gi') { stage('Mix Unit Tests') { - def local = 'CXXFLAGS+= -fsanitize=address\n' - if (noOptimize) - local += 'O=1\n' - runTests(local, "test/unit/math/mix" + jumboFlags) + def settings = ['-DSTAN_SANITIZE_ADDRESS=ON', + "-DSTAN_TEST_OPTIMIZATION=${noOptimize ? '1' : '3'}"] + // A single mix executable exceeds the 2 GiB x86-64 small code + // model limit under ASan, so link each subfolder separately. + runTests(settings, "test/unit/math/mix/*_test.cpp") + runTests(settings, "test/unit/math/mix/constraint") + runTests(settings, "test/unit/math/mix/core") + runTests(settings, "test/unit/math/mix/fun") + runTests(settings, "test/unit/math/mix/functor") + runTests(settings, "test/unit/math/mix/meta") + runTests(settings, "test/unit/math/mix/prob") } } }, prim: { runPod(image: image) { stage('Prim Unit Tests') { - def local = 'CXXFLAGS+= -fsanitize=address\n' - if (noOptimize) - local += 'O=0\n' - runTests(local, "test/unit/*_test.cpp") - runTests(local, "test/unit/math/*_test.cpp") - runTests(local, "test/unit/math/prim" + jumboFlags) - runTests(local, "test/unit/math/memory") + def settings = ['-DSTAN_SANITIZE_ADDRESS=ON', + "-DSTAN_TEST_OPTIMIZATION=${noOptimize ? '0' : '3'}"] + runTests(settings, "test/unit/*_test.cpp") + runTests(settings, "test/unit/math/*_test.cpp") + runTests(settings, "test/unit/math/prim") + runTests(settings, "test/unit/math/memory") } } }, laplace: { runPod(image: image, memory: '32Gi') { stage('Laplace Unit Tests') { - def local = 'CXXFLAGS+= -march=native -mtune=native\nO=3\n' + def settings = ['-DCMAKE_CXX_FLAGS=-march=native -mtune=native', '-DSTAN_TEST_OPTIMIZATION=3'] - runTests(local, "test/unit/math/laplace/*_test.cpp") + runTests(settings, "test/unit/math/laplace/*_test.cpp") if (mainBranch) { // only run ASAN on a selected test to avoid extremely long CI times - sh 'make clean' - local += 'CXXFLAGS+= -fsanitize=address' - runTests(local, "test/unit/math/laplace/laplace_marginal_lpdf_moto_test.cpp") + runTests(settings + ['-DSTAN_SANITIZE_ADDRESS=ON'], + "test/unit/math/laplace/laplace_marginal_lpdf_moto_test.cpp") } } } @@ -170,58 +200,57 @@ up the autoformatter locally. (Check console output at ${env.BUILD_URL}) gpu: { runPod(image: image, memory: '32Gi', gpus: 1) { stage('OpenCL GPU tests') { - def local = """CXX=$CLANG_CXX -Werror -STAN_OPENCL=true -OPENCL_PLATFORM_ID_GPU=0 -OPENCL_DEVICE_ID=0 -LDFLAGS_OPENCL=-L/usr/local/cuda/targets/x86_64-linux/lib -""" - if (noOptimize) - local += 'O=1\n' - runTests(local, "test/unit/math/opencl") // TODO(bward): try to enable - runTests(local, "test/unit/multiple_translation_units_test.cpp") + def settings = ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_OPENCL=ON', + '-DSTAN_OPENCL_PLATFORM_ID=0', '-DSTAN_OPENCL_DEVICE_ID=0', + '-DOpenCL_LIBRARY=/usr/local/cuda/targets/x86_64-linux/lib/libOpenCL.so', + "-DSTAN_TEST_OPTIMIZATION=${noOptimize ? '1' : '3'}"] + runTests(settings, "test/unit/math/opencl") // TODO(bward): try to enable + runTests(settings, "test/unit/multiple_translation_units_test.cpp") } } }, mpi: { runPod(image: image) { stage('MPI Tests') { - def local = "CXX=$MPICXX\nCXX_TYPE=gcc\nSTAN_MPI=true\n" - runTests(local, "test/unit/math/prim/functor") - runTests(local, "test/unit/math/rev/functor") + def settings = ["-DCMAKE_CXX_COMPILER=${MPICXX}", '-DSTAN_MPI=ON', '-DSTAN_TEST_OPTIMIZATION=3'] + runTests(settings, "test/unit/math/prim/functor") + runTests(settings, "test/unit/math/rev/functor") } } }, expr: { runPod(image: image, memory: '64Gi') { stage('Expressions test') { - def local = "CXX=$CLANG_CXX -Werror\nO=0\n" - writeFile(file: 'make/local', text: local) + def settings = ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_TEST_OPTIMIZATION=0'] sh "python ./test/code_generator_test.py" sh "python ./test/signature_parser_test.py" sh "python ./test/statement_types_test.py" sh "python ./test/varmat_compatibility_summary_test.py" - sh "python ./test/varmat_compatibility_test.py" - withEnv(['PATH+TBB=./lib/tbb']) { + // These scripts configure their own CMake builds; CC, CXX, and + // CXXFLAGS set those builds' compilers and CMAKE_CXX_FLAGS. + withEnv(["CC=${CLANG_CC}", "CXX=${CLANG_CXX}", 'CXXFLAGS=-Werror']) { + sh "python ./test/varmat_compatibility_test.py" sh "python ./test/expressions/test_expression_testing_framework.py" - runTests(local, 'test/expressions') - sh "make clean-all" - runTests(local + "STAN_THREADS=true\n", 'test/expressions --only-functions reduce_sum map_rect') } + runTests(settings, 'test/expressions') + runTests(settings + ['-DSTAN_THREADS=ON'], 'test/expressions --only-functions reduce_sum map_rect') } } }, thread: { runPod(image: image, cpus: 8, memory: '128Gi') { stage('Threading tests') { - def local = "CXX=$CLANG_CXX -Werror\nSTAN_THREADS=true\n" + def settings = ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_THREADS=ON', '-DSTAN_TEST_OPTIMIZATION=3'] withEnv(['STAN_NUM_THREADS=4', 'PARALLEL=8']) { // runTests currently only runs 1 test at a time post-build if (mainBranch) { - runTests(local, "test/unit") + runTests(settings, "test/unit") } else { - runTests(local, "test/unit -f thread") - runTests(local, "test/unit -f map_rect") - runTests(local, "test/unit -f reduce_sum") + runTests(settings, "test/unit -f thread") + runTests(settings, "test/unit -f map_rect") + runTests(settings, "test/unit -f reduce_sum") } } } @@ -232,22 +261,25 @@ LDFLAGS_OPENCL=-L/usr/local/cuda/targets/x86_64-linux/lib def parallel_tests = 10 runPod(image: image, cpus: cores_per_test*parallel_tests, memory: '128Gi') { stage ('Distribution tests') { - def local = "CXX=$CLANG_CXX\nO=0\nN_TESTS=100\n" + def settings = ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DSTAN_TEST_OPTIMIZATION=0', '-DSTAN_PROB_TEST_BATCH_SIZE=100'] if (params.withRowVector || mainBranch) { - local += "CXXFLAGS+= -DSTAN_TEST_ROW_VECTORS -DSTAN_PROB_TEST_ALL\n" + settings += ['-DSTAN_TEST_ROW_VECTORS=ON', '-DSTAN_PROB_TEST_ALL=ON'] } - writeFile(file: 'make/local', text: local) + // getDependencies.py lists each fixture's includes with the + // configured build's compiler flags. + sh "python3 runTests.py --configure-only ${runnerSettings(settings)}" - def cmd = 'python3 test/prob/getDependencies.py' + def cmd = 'python3 test/prob/getDependencies.py --build-dir build' if (params.runAllDistributions || mainBranch) { cmd += ' --pretend-all' } sh """ $cmd > changed-tests if [ -s changed-tests ] ; then - ./runTests.py -j${cores_per_test*parallel_tests} --make-only `cat changed-tests` + ./runTests.py -j${cores_per_test*parallel_tests} ${runnerSettings(settings)} --make-only `cat changed-tests` unset PARALLEL - parallel --halt now,fail=1 -r -j$parallel_tests ./runTests.py --test-only -j$cores_per_test {} < changed-tests + parallel --halt now,fail=1 -r -j$parallel_tests ./runTests.py --build-dir build --test-only -j$cores_per_test {} < changed-tests fi """ } @@ -271,7 +303,8 @@ LDFLAGS_OPENCL=-L/usr/local/cuda/targets/x86_64-linux/lib runPod(image: image, cpus: 2) { stage('Upload doxygen') { def branch = 'gh-pages' - sh 'make doxygen' + configureCmake(['-DBUILD_TESTING=OFF', '-DSTAN_BUILD_DOCS=ON']) + sh 'cmake --build build --target doxygen' dir('gh-pages') { def scm = scmGit( branches: [[name: "refs/heads/$branch"]], diff --git a/README.md b/README.md index 600f5712ad0..63a37c45acf 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,509 @@ -
- -The Stan Math Library is a C++, reverse-mode automatic -differentiation library designed to be usable, extensive and -extensible, efficient, scalable, stable, portable, and redistributable -in order to facilitate the construction and utilization of algorithms -that utilize derivatives. - - -Documentation, Installation, and Examples --------------- - -All of Stan math's documentation is hosted on our website below. Please do not -reference articles in the wiki as they are outdated and not maintained. - -[mc-stan.org/math](https://mc-stan.org/math/) - - -Licensing ---------- -The Stan Math Library is licensed under the [new BSD -license](https://github.com/stan-dev/math/blob/develop/LICENSE%2Emd). - -The Stan Math Library depends on the Intel TBB library which is -licensed under the Apache 2.0 license. This dependency implies an -additional restriction as compared to the new BSD license alone. The -Apache 2.0 license is incompatible with GPL-2 licensed code if -distributed as a unitary binary. You may refer to the Licensing page on the [Stan wiki](https://github.com/stan-dev/stan/wiki/Stan-Licensing). +
+ +The **Stan Math Library** is a C++ library for reverse-mode automatic +differentiation. Use Stan Math to build algorithms requiring derivatives. + + +Documentation, Installation, and Examples +-------------- + +Read the Stan Math documentation at mc-stan.org. +The wiki articles are outdated. Stan Math developers do not maintain wiki articles. + +[mc-stan.org/math](https://mc-stan.org/math/) + + +Licensing +--------- +The Stan Math Library is licensed under the [new BSD +license](https://github.com/stan-dev/math/blob/develop/LICENSE%2Emd). + +Stan Math depends on Intel TBB. TBB uses the Apache 2.0 license. +The Apache 2.0 license adds restrictions to the BSD license terms. +The Apache 2.0 license is incompatible with GPL-2 licensed code in a combined binary. See the [Licensing page](https://github.com/stan-dev/stan/wiki/Stan-Licensing) for details. + +Building tests with CMake +------------------------- + +Stan Math has recently used to using cmake as its primary build system. +CMake creates build rules for a selected build tool while CTest is CMake's test runner. + +`runTests.py` is the main way to compile the tests for Stan Math and it now uses cmake on the backend. +`runTests.py` configures CMake, builds the selected executables, and runs the tests. + +```sh +# Builds a single executable for the whole `fun` directory +./runTests.py test/unit/math/prim/fun +# Builds a single executable for the `sum_test.cpp` file +./runTests.py test/unit/math/prim/fun/sum_test.cpp +``` + +Some useful options for `runTests.py` include: + +- `--fresh` resets cached CMake settings before configuration. + +CMake saves persistent build settings in a cache. +The default cache file is `build/CMakeCache.txt`. +Cached settings can override later changes to `make/local`. +`--fresh` resets the CMake configuration before building. +`--fresh` removes `CMakeCache.txt` and the top-level `CMakeFiles` directory, which is what CMake's own `--fresh` option does. +It works with any supported CMake version, while the native option needs CMake 3.24 or newer. +CMake then configures the build with the current arguments and `make/local` settings. +Objects whose compile flags did not change are not rebuilt. +Use `--fresh` when cached settings no longer match the intended build. +Pass required custom settings again because `--fresh` removes cached overrides. +`--test-only` runs an existing executable without configuration or compilation. +`--fresh` cannot be combined with `--test-only`. + +```sh +# Reset cached settings, then build and run the sum tests. +./runTests.py --fresh test/unit/math/prim/fun/sum_test.cpp +``` + +- `--build-dir PATH` selects the build directory. The default is `build`. +The build directory contains generated build rules, compiled files, and test results. + +```sh +# Store the build in `.build`. +./runTests.py --build-dir .build test/unit/math/prim/fun/sum_test.cpp +``` + +- `--config NAME` selects the build configuration such as `Debug`, `Release`, or `ReleaseTesting`. Setting a configuration changes the default behavior of the build system such as compiler flags and debug information. +The default is `ReleaseTesting`. +`ReleaseTesting` compiles with `-O1` and does not define `NDEBUG`, so assertions stay on as in the make build. +`Release` defines `NDEBUG`, which disables assertions. +`STAN_TEST_OPTIMIZATION` still sets the optimization level of the tests in every configuration. + +```sh +# Use the `Release` configuration. +./runTests.py --config Release test/unit/math/prim/fun/sum_test.cpp +``` + +- `-j N` sets the number of parallel build jobs. The default is 1. + +```sh +# Build with four jobs. +./runTests.py -j4 test/unit/math/prim/fun +``` + +- `-f TEXT` selects source files whose paths contain `TEXT`. +A directory executable includes only source files matching the filter. +Repeat `-f` to require all filters to match. + +```sh +# Build and run primitive function tests with `sum` in the source paths. +./runTests.py -f sum test/unit/math/prim/fun +``` + +- `--make-only` builds the tests without running the test executables. + +```sh +# Build the executable for `sum_test.cpp`. +./runTests.py --make-only test/unit/math/prim/fun/sum_test.cpp +``` + +Selected files and distribution fixtures build through one CMake target, `stan_selected_tests`, so all of them compile in parallel. +Each selected file is still its own executable. + +- A distribution fixture such as `test/prob/normal/normal_test.hpp` selects the tests generated from that fixture. + +```sh +# Build and run the tests generated from the normal distribution fixture. +./runTests.py -j8 test/prob/normal/normal_test.hpp +``` + +- `--configure-only` configures `` without building or running tests. +`test/prob/getDependencies.py` reads that configuration's compile commands to select the distribution fixtures whose included Stan headers changed: + +```sh +./runTests.py --configure-only +./runTests.py -j8 $(python3 test/prob/getDependencies.py --build-dir build) +``` + +- `--test-only` runs an existing test executable. +`--test-only` skips configuration and compilation. +`--test-only` does not rebuild tests after source changes. + +```sh +# Build the primitive function test executable in `.build` +./runTests.py --make-only --build-dir .build test/unit/math/prim/fun +# run the executable. +./runTests.py --test-only --build-dir .build test/unit/math/prim/fun +``` + +- `--changed` selects tests for committed changes on your branch. +`--changed` compares your branch with `origin/develop`. +`--changed` selects changed test files and tests for changed Stan headers. +Each selected file builds and runs as a separate executable: + +```sh +./runTests.py --changed +``` + +- `--run-all` continues to the next executable if a test executable fails. +`runTests.py --run-all` still returns a failure status if any tests fail. +Build failures stop `runTests.py`. + +```sh +# Run the `abs` tests even if the `sum` tests fail. +./runTests.py --run-all test/unit/math/prim/fun/sum_test.cpp test/unit/math/prim/fun/abs_test.cpp +``` + +- `--test-jobs N` runs up to `N` test executables at once. The default is 1. +Each executable prints its output when it finishes, in the order of the selected paths. +With more than one job, every executable runs even if one fails. +A trailing `/` in a pattern selects only directories. + +```sh +# Build one executable per distribution directory and run four at a time. +./runTests.py -j8 --test-jobs 4 'test/prob/*/' +``` + +`` means the directory selected by `--build-dir`. +XML results are written under `/test-results` by default. +`make/local` is an optional file for local compiler and build settings. +The CMake test setup reads `make/local`. +All CMake generators use the imported settings, including Ninja and Unix Makefiles. +The Windows and ARM GitHub jobs use CMake. + +`--cmake-arg=ARGUMENT` passes one argument to CMake configuration. +Repeat `--cmake-arg` to pass multiple arguments. +The CMake argument `-DNAME=VALUE` sets a named cache setting. + +```sh +# Build reverse-mode core tests with threading enabled. +./runTests.py --cmake-arg=-DSTAN_THREADS=ON test/unit/math/rev/core +``` + +`-DSTAN_SANITIZE_ADDRESS=ON` compiles C++ sources and links with AddressSanitizer. +The vendored TBB build does not use it. + +```sh +# Build mixed-mode tests at -O1 with AddressSanitizer, without make/local. +./runTests.py --cmake-arg=-DSTAN_USE_MAKE_LOCAL=OFF --cmake-arg=-DSTAN_SANITIZE_ADDRESS=ON \ + --cmake-arg=-DSTAN_TEST_OPTIMIZATION=1 test/unit/math/mix +``` + +The `cpplint` and `test-math-dependencies` CMake targets run the same checks as `make cpplint` and `make test-math-dependencies`: + +```sh +cmake -S . -B build -DBUILD_TESTING=OFF +cmake --build build --target cpplint +``` + +The CMake argument `-G NAME` selects a generator. +The combined form `-GNAME` selects the generator with one argument. + +```sh +# Build with Ninja and the settings from `make/local`. +./runTests.py --build-dir build-ninja --cmake-arg=-GNinja test/unit/math/prim/fun/sum_test.cpp +``` + +CMake imports the following Make settings: + +| Make setting | CMake setting | +| --- | --- | +| `CC`, `CXX` | C and C++ compilers | +| `O` | Optimization level from 0 to 3, mapped to `STAN_TEST_OPTIMIZATION` | +| `STAN_THREADS` | Enable threading support | +| `STAN_OPENCL` | Enable OpenCL GPU support | +| `STAN_MPI` | Enable MPI support for communication between processes | +| `STAN_NO_RANGE_CHECKS` | Disable range checks | +| `STAN_TEST_ROW_VECTORS` | Include row vectors in distribution tests | +| `STAN_PROB_TEST_ALL` | Enable all distribution test modes | +| `OPENCL_DEVICE_ID` | Select an OpenCL device with `STAN_OPENCL_DEVICE_ID` | +| `OPENCL_PLATFORM_ID` | Select an OpenCL platform with `STAN_OPENCL_PLATFORM_ID` | +| `INTEGRATED_OPENCL` | Enable integrated GPU settings with `STAN_INTEGRATED_OPENCL` | +| `BOOST`, `EIGEN`, `TBB`, `SUNDIALS`, `OPENCL`, `BENCHMARK` | Dependency source directories | +| `CXXFLAGS`, `CXXFLAGS_OPTIM`, `CXXFLAGS_WARNINGS` | Additional C++ compiler flags | +| `CFLAGS` | Additional C compiler flags | +| `CPPFLAGS`, `CPPFLAGS_OPTIM` | Additional C and C++ preprocessor flags | +| `LDFLAGS`, `LDFLAGS_OPTIM` | Additional linker flags | +| `LDLIBS` | Additional link libraries | + +CMake options passed with `-D` take priority over imported defaults. +Existing CMake cache values also take priority over imported defaults. +Use a new build directory after changing cached settings or compilers. +Use `--fresh` to discard cached settings in the selected build directory. +Flags from `make/local` are added to the CMake compiler and linker flags. +Changes to `make/local` and included Make files trigger CMake configuration. +Make settings outside the table are not imported. + +`CXX = clang++` selects the Clang C++ compiler. +`O = 0` disables compiler optimization. +`STAN_THREADS = true` enables threading support. +`CXXFLAGS += -g` adds compiler debug information. +Example: Set compiler settings in `make/local`. + +```make +CXX = clang++ +O = 0 +STAN_THREADS = true +CXXFLAGS += -g +``` + +Use `STAN_USE_MAKE_LOCAL=OFF` to ignore `make/local`: + +```sh +./runTests.py --cmake-arg=-DSTAN_USE_MAKE_LOCAL=OFF test/unit/math/prim/fun/sum_test.cpp +``` + +### Common CMake commands + +Run the commands from the repository root. +CMake configuration creates build rules and saves settings in `{BUILD_DIRECTORY}/CMakeCache.txt`. +Configuration does not build the test executables. +`cmake --build` compiles and links the test executables. +CTest runs test executables already built by CMake. +`runTests.py` configures, builds, and runs the selected tests automatically. + +#### Configure a build + +`-S PATH` selects the source directory. Use `.` for the repository root. +`-B PATH` selects the build directory. CMake creates the directory if needed. +`-DNAME=VALUE` sets a named cache setting. +`CMAKE_BUILD_TYPE` selects the configuration for Ninja and Unix Makefiles. +`Release` enables release compiler settings. +`ReleaseTesting` is the configuration `runTests.py` uses: `-O1` without `NDEBUG`. +Direct CMake commands also read `make/local`. +Example: Configure a release build in `build`. + +```sh +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +``` + +The native CMake option `--fresh` resets the configuration cache. +`--fresh` requires CMake 3.24 or newer. +With older CMake, delete the cache file and the top-level `CMakeFiles` directory instead. +Example: Reset cached settings and configure a release build. + +```sh +cmake --fresh -S . -B build -DCMAKE_BUILD_TYPE=Release +# CMake older than 3.24 +cmake -E remove -f build/CMakeCache.txt +cmake -E remove_directory build/CMakeFiles +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +``` + +#### List settings and descriptions + +`-N` reads the cache without configuring the project. +`-L` lists cache variables. +`H` in `-LH` or `-LAH` prints the description above each value. +`A` in `-LAH` includes advanced variables. +Advanced variables contain settings changed less often during normal builds. +Cache listings omit temporary variables and uncached `make/local` defaults. +Print the current cache values and option descriptions: + +```sh +cmake -N -LH build +``` + +Add `A` to include advanced cache settings: + +```sh +cmake -N -LAH build +``` + +To inspect project defaults, configure an empty build directory. +Disable `make/local` imports to exclude local overrides. +The `build-defaults` directory must not contain an existing CMake cache: + +```sh +cmake -S . -B build-defaults -DSTAN_USE_MAKE_LOCAL=OFF -LAH +``` + +Project option definitions contain the default values and descriptions. +Read [CMakeLists.txt](CMakeLists.txt) for the main options. +Read [cmake/CMakeLists.txt](cmake/CMakeLists.txt) for dependency options. +Read [cmake/MakeLocal.cmake](cmake/MakeLocal.cmake) for local settings options. +Read [test/prob/CMakeLists.txt](test/prob/CMakeLists.txt) for distribution options. + +`--help-variable-list` prints CMake's built-in variable names. +`--help-variable NAME` prints the definition of a built-in variable. +CMake's built-in help does not define Stan-specific options. +Use the cache descriptions or the project CMake files for Stan-specific options. +Example: List built-in variables and read the definition of `CMAKE_BUILD_TYPE`. + +```sh +cmake --help-variable-list +cmake --help-variable CMAKE_BUILD_TYPE +``` + +#### Change settings or select a generator + +Use `-DNAME=VALUE` to change a cached setting. +Example: Enable threading in `build`. + +```sh +cmake -S . -B build -DSTAN_THREADS=ON +``` + +Use `-U` to remove a named cache override. +Configuration then uses the `make/local` value or the project default. +Example: Remove the cached threading setting. + +```sh +cmake -S . -B build -U STAN_THREADS +``` + +Run `cmake --help` to list the available generators. +Use `-G` to select Ninja or Unix Makefiles. +Use a separate build directory when changing generators or compilers: + +```sh +cmake --help +cmake -S . -B build-ninja -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake -S . -B build-make -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release +``` + +#### List targets and build tests + +A build target names an executable, library, or build task. +`--build PATH` builds targets from the selected build directory. +`--target NAME` selects a build target. +The `help` target lists available targets for Ninja and Unix Makefiles. +`test_unit_math_prim_fun_sum_test` builds the executable for `sum_test.cpp`. +`--parallel N` sets the number of parallel build jobs. +`--verbose` prints compiler and linker commands. +List build targets for Ninja or Unix Makefiles: + +```sh +cmake --build build --target help +``` + +Build one test executable with four parallel jobs: + +```sh +cmake --build build --parallel 4 --target test_unit_math_prim_fun_sum_test +``` + +Add `--verbose` to print compiler and linker commands: + +```sh +cmake --build build --parallel 4 --verbose --target test_unit_math_prim_fun_sum_test +``` + +#### List and run tests + +CTest does not build missing executables. +Build the selected test targets before running CTest. +The CTest option `--test-dir PATH` selects the build directory containing test registrations. +A regular expression is a pattern used to match text. +The CTest option `-N` lists registered tests without running the tests. +`-R PATTERN` selects test names matching a regular expression. +`^` matches the start of a name. `$` matches the end of a name. +`--output-on-failure` prints output from failing tests. +The CTest option `--parallel N` sets the number of parallel test jobs. +List registered tests without running the test executables: + +```sh +ctest --test-dir build -N +``` + +Run one registered test and print output on failure: + +```sh +ctest --test-dir build -R '^test_unit_math_prim_fun_sum_test$' --output-on-failure +``` + +Run all registered tests with four parallel jobs: + +```sh +ctest --test-dir build --parallel 4 --output-on-failure +``` + +See the [CMake command reference](https://cmake.org/cmake/help/latest/manual/cmake.1.html) +and [CTest command reference](https://cmake.org/cmake/help/latest/manual/ctest.1.html) +for more commands. + +Each test source has a separate executable and CTest entry. +Targets including `unit_math_rev_core_subtests` and `prob_math_bernoulli_subtests` +build tests in the selected directory and the selected directory's subdirectories. +Use `-R` to select matching CTest entries: + +```sh +ctest --test-dir build -R '^test_unit_math_rev_core_' --output-on-failure +``` + +The optional `*_all_tests` targets combine a directory's tests into one executable. +The default build and CTest exclude `*_all_tests` targets to prevent duplicate test runs. +Each test source compiles into one object file. +Individual test executables and directory executables reuse the compiled object files. +Switching between individual and directory executables does not recompile the test source. +The working directory for tests is the repository root. + +CMake creates targets for all enabled tests. +Select a directory target to build only the required test executables. +The `unit_math_prim_core_subtests` target builds primitive core test executables. +The CTest label `test/unit/math/prim/core/` selects primitive core tests. +The CTest option `-L PATTERN` selects labels matching a regular expression. + +```sh +cmake --build build --parallel 4 --target unit_math_prim_core_subtests +ctest --test-dir build -L '^test/unit/math/prim/core/' --output-on-failure +``` + +Precompiled headers (PCH) are enabled by default with `STAN_TEST_PCH=ON`. +Upstream Clang 9 and 10 automatically compile without PCH because of a compiler +defect fixed in Clang 11. Configuration reports the requested and effective +settings; the same setting applies to var-matrix compatibility checks. +Automatic differentiation (AD) computes derivatives through the Stan Math type system. +Unit tests reuse a PCH for the automatic differentiation layer under test. +Sources including `test_ad.hpp` use the AD helper PCH instead. +The PCH choice for each source also applies to directory executables. +Sources changing Stan or Eigen configuration macros skip PCH. +Sources without configuration macro changes continue to use PCH. +Umbrella header checks and tests with multiple translation units compile without PCH. + +Distribution tests precompile distribution fixture headers. +The distribution test PCH excludes generated typedefs. +PCH builds and test builds use identical compiler flags and definitions. +GCC reports an invalid PCH as an error. +Use `-DSTAN_TEST_PCH=OFF` to compile tests without PCH. +Standalone header targets never use PCH. +Examples include `test-headers` and `stan_math_prim_fun_sum-test`. + +Tests and the included SUNDIALS library use `STAN_TEST_OPTIMIZATION`. +`STAN_TEST_OPTIMIZATION` defaults to `1`. Optimization level `1` matches Make builds with `O=1`. + +The build generates distribution test sources under `/test/prob`. +The distribution test generator is `test/prob/generate_tests.cpp`. +`STAN_PROB_TEST_BATCH_SIZE` defaults to 100 cases per source. +The build generates distribution test sources once per fixture before compilation. +The build regenerates distribution test sources when a fixture or the generator changes. + +`STAN_THREADS`, `STAN_OPENCL`, `STAN_MPI`, and `STAN_NO_RANGE_CHECKS` work as before. +The build excludes OpenCL tests when `STAN_OPENCL=OFF`. +The build excludes MPI tests when `STAN_MPI=OFF`. +Use `STAN_USE_SYSTEM_{BOOST,EIGEN,TBB,SUNDIALS}` to select installed dependencies. +Use `STAN_{BOOST,EIGEN,TBB,SUNDIALS,OPENCL,BENCHMARK}_ROOT` to set dependency paths. +The included TBB source supports GCC and Clang builds on Linux and macOS. +MinGW and Rtools builds on Windows use the included TBB source. +MSVC builds require an installed TBB. +MPI builds use the included Boost sources. +Set `STAN_USE_SYSTEM_BOOST=ON` to use installed Boost libraries. +Set `BUILD_TESTING=OFF` to use the `stanmath` interface target without configuring tests. + +Jenkins uses CMake and CTest. Each worker reuses the `build` directory. +Jenkins passes `--fresh` when a configuration cache already exists. +Test reports are saved under `test-results` with a directory for each test stage. +`PARALLEL` sets the number of build jobs. +The pipeline includes ASan, GPU, MPI, threading, and expression tests. +`STAN_TEST_ROW_VECTORS` and `STAN_PROB_TEST_ALL` enable the additional +distribution cases requested by Jenkins. +`runTests.py generate-expressions PATH` creates expression test sources in +`PATH/expression-sources`. +`STAN_BUILD_EXPRESSION_TESTS=ON` enables generated expression tests. +`runTests.py` also generates expression sources when you select `test/expressions`. +The runner option `-e N` sets the number of expression source files. +The runner option `--only-functions` selects functions for expression tests. +Example: Generate expression test sources in `build-expressions/expression-sources`. + +```sh +python3 runTests.py generate-expressions build-expressions +``` + +Expression framework self-tests use the CMake runner. +Var-matrix compatibility checks compile through CMake. +The Makefiles remain available for upstream packages. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 00000000000..9bc0435e8c1 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,183 @@ +# Share dependency versions with Make; cache paths may be overridden. +file(STRINGS "${PROJECT_SOURCE_DIR}/make/libraries" library_locations + REGEX "^(BOOST|EIGEN|OPENCL|TBB|SUNDIALS|BENCHMARK) ") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${PROJECT_SOURCE_DIR}/make/libraries") +foreach(line IN LISTS library_locations) + if(line MATCHES "^([A-Z]+) .*lib/([^ ]+)") + set(dependency "${CMAKE_MATCH_1}") + set(vendored_root "${PROJECT_SOURCE_DIR}/lib/${CMAKE_MATCH_2}") + stan_cache_default(STAN_${dependency}_ROOT "" PATH "Override ${dependency} source directory (empty: vendored)") + if(NOT STAN_${dependency}_ROOT) + set(STAN_${dependency}_ROOT "${vendored_root}") + endif() + set(STAN_${dependency}_ROOT "${STAN_${dependency}_ROOT}" PARENT_SCOPE) + endif() +endforeach() +option(STAN_USE_SYSTEM_TBB "Use an installed TBB library" OFF) +option(STAN_USE_SYSTEM_EIGEN "Use an installed Eigen library" OFF) +option(STAN_USE_SYSTEM_BOOST "Use installed Boost headers" OFF) +option(STAN_USE_SYSTEM_SUNDIALS "Use installed SUNDIALS libraries" OFF) +find_package(Threads REQUIRED) +target_link_libraries(stanmath INTERFACE Threads::Threads) + +set(stan_arm64 FALSE) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$") + set(stan_arm64 TRUE) +endif() +# Match make/compiler_flags for ARM64, including the solver C sources. +if(stan_arm64 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MSVC) + set(stan_arm64_fp_options -ffp-contract=off) + target_compile_options(stanmath INTERFACE ${stan_arm64_fp_options}) +endif() + +if(STAN_USE_SYSTEM_EIGEN) + find_package(Eigen3 REQUIRED) + target_link_libraries(stanmath INTERFACE Eigen3::Eigen) +else() + target_include_directories(stanmath SYSTEM INTERFACE "${STAN_EIGEN_ROOT}") +endif() +if(STAN_USE_SYSTEM_BOOST) + find_package(Boost REQUIRED) + target_link_libraries(stanmath INTERFACE Boost::boost) +else() + target_include_directories(stanmath SYSTEM INTERFACE "${STAN_BOOST_ROOT}") +endif() + +if(WIN32) + target_compile_definitions(stanmath INTERFACE _USE_MATH_DEFINES) + # Match Make's windows.h macro probe, without requiring an executable link. + function(stan_detect_ucrt) + include(CheckCXXSourceCompiles) + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + check_cxx_source_compiles("#include + #ifndef _UCRT + #error Not UCRT + #endif + int main() { return 0; }" STAN_WINDOWS_UCRT) + endfunction() + stan_detect_ucrt() + if(STAN_WINDOWS_UCRT) + target_compile_definitions(stanmath INTERFACE _UCRT) + endif() +endif() + +if(STAN_USE_SYSTEM_TBB) + find_package(TBB REQUIRED COMPONENTS tbb) +else() + # Vendored TBB predates oneTBB's CMake build. One nested Make job keeps + # dependency compilation within the outer build's parallelism limit. + include(ExternalProject) + find_program(STAN_TBB_MAKE NAMES gmake make mingw32-make) + if(NOT STAN_TBB_MAKE) + message(FATAL_ERROR "Building vendored TBB requires GNU Make") + endif() + set(tbb_extra_args "") + set(tbb_extra_cxx_flags "") + if(STAN_WINDOWS_UCRT) + set(tbb_extra_cxx_flags "-D_UCRT") + endif() + if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(tbb_compiler gcc) + if(stan_arm64) + list(APPEND tbb_extra_args WINARM64=true) + string(APPEND tbb_extra_cxx_flags " -DTBB_USE_GCC_BUILTINS") + set(tbb_interface_definitions TBB_USE_GCC_BUILTINS) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(tbb_compiler clang) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(tbb_compiler gcc) + else() + message(FATAL_ERROR "Use STAN_USE_SYSTEM_TBB with this compiler") + endif() + if(APPLE) + set(tbb_library "${CMAKE_CURRENT_BINARY_DIR}/tbb/libtbb.dylib") + elseif(WIN32) + set(tbb_library "${CMAKE_CURRENT_BINARY_DIR}/tbb/tbb.dll") + else() + set(tbb_library "${CMAKE_CURRENT_BINARY_DIR}/tbb/libtbb.so.2") + endif() + ExternalProject_Add(stan_tbb_build + SOURCE_DIR "${STAN_TBB_ROOT}" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/tbb" + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND ${STAN_TBB_MAKE} -j1 -r -f "${STAN_TBB_ROOT}/build/Makefile.tbb" + "tbb_root=${STAN_TBB_ROOT}" compiler=${tbb_compiler} cfg=release stdver=c++17 + "CPLUS=${CMAKE_CXX_COMPILER}" "CONLY=${CMAKE_C_COMPILER}" + "CXXFLAGS=-Wno-deprecated-declarations -Wno-deprecated-copy ${STAN_MAKE_LOCAL_CPP_FLAGS} ${STAN_MAKE_LOCAL_CXX_FLAGS} ${tbb_extra_cxx_flags}" + ${tbb_extra_args} + BUILD_BYPRODUCTS "${tbb_library}" + BUILD_ALWAYS TRUE + INSTALL_COMMAND "") + add_library(TBB::tbb SHARED IMPORTED GLOBAL) + set_target_properties(TBB::tbb PROPERTIES + IMPORTED_LOCATION "${tbb_library}" + INTERFACE_INCLUDE_DIRECTORIES "${STAN_TBB_ROOT}/include" + INTERFACE_COMPILE_DEFINITIONS "${tbb_interface_definitions}") + if(WIN32) + set_property(TARGET TBB::tbb PROPERTY IMPORTED_IMPLIB "${tbb_library}") + endif() + add_dependencies(TBB::tbb stan_tbb_build) +endif() +target_link_libraries(stanmath INTERFACE TBB::tbb) + +if(STAN_USE_SYSTEM_SUNDIALS) + find_package(SUNDIALS REQUIRED COMPONENTS cvodes idas kinsol arkode nvecserial) + target_link_libraries(stanmath INTERFACE + SUNDIALS::cvodes SUNDIALS::idas SUNDIALS::kinsol SUNDIALS::arkode + SUNDIALS::nvecserial) +else() + # The pruned vendored distribution has no upstream CMakeLists.txt. + # Match make/libraries, including Stan's printf override and common modules. + file(GLOB sundials_sources CONFIGURE_DEPENDS + "${STAN_SUNDIALS_ROOT}/src/cvodes/*.c" + "${STAN_SUNDIALS_ROOT}/src/idas/*.c" + "${STAN_SUNDIALS_ROOT}/src/kinsol/*.c" + "${STAN_SUNDIALS_ROOT}/src/arkode/*.c" + "${STAN_SUNDIALS_ROOT}/src/sundials/*.c" + "${STAN_SUNDIALS_ROOT}/src/sunmatrix/band/*.c" + "${STAN_SUNDIALS_ROOT}/src/sunmatrix/dense/*.c" + "${STAN_SUNDIALS_ROOT}/src/sunlinsol/band/*.c" + "${STAN_SUNDIALS_ROOT}/src/sunlinsol/dense/*.c" + "${STAN_SUNDIALS_ROOT}/src/sunnonlinsol/newton/*.c" + "${STAN_SUNDIALS_ROOT}/src/sunnonlinsol/fixedpoint/*.c" + "${STAN_SUNDIALS_ROOT}/src/nvector/serial/nvector_serial.c") + list(FILTER sundials_sources EXCLUDE REGEX "/f[^/]*\\.c$|/sundials_profiler\\.c$") + add_library(stan_sundials STATIC ${sundials_sources}) + target_compile_options(stan_sundials PRIVATE ${stan_arm64_fp_options}) + if(BUILD_TESTING) + target_compile_options(stan_sundials PRIVATE ${STAN_TEST_OPTIMIZATION_FLAGS}) + endif() + target_include_directories(stan_sundials SYSTEM PUBLIC "${STAN_SUNDIALS_ROOT}/include") + target_include_directories(stan_sundials PRIVATE "${STAN_SUNDIALS_ROOT}/src/sundials") + target_compile_definitions(stan_sundials PRIVATE NO_FPRINTF_OUTPUT) + if(MSVC) + target_compile_options(stan_sundials PRIVATE + "/FI${STAN_SUNDIALS_ROOT}/include/stan_sundials_printf_override.hpp") + else() + target_compile_options(stan_sundials PRIVATE + -include "${STAN_SUNDIALS_ROOT}/include/stan_sundials_printf_override.hpp") + target_link_libraries(stan_sundials PUBLIC m) + endif() + target_link_libraries(stanmath INTERFACE stan_sundials) +endif() +if(BUILD_TESTING) + set(BUILD_GMOCK OFF CACHE BOOL "Build Google Mock") + set(INSTALL_GTEST OFF CACHE BOOL "Install Google Test") + function(stan_add_googletest) + # CMake 4 removed policies older than 3.5. Limit the compatibility setting + # to this old dependency, preserving a higher caller-provided minimum. + if(NOT DEFINED CMAKE_POLICY_VERSION_MINIMUM + OR CMAKE_POLICY_VERSION_MINIMUM VERSION_LESS "3.5") + set(CMAKE_POLICY_VERSION_MINIMUM 3.5) + endif() + add_subdirectory("${STAN_BENCHMARK_ROOT}/googletest" + "${CMAKE_CURRENT_BINARY_DIR}/googletest" EXCLUDE_FROM_ALL) + endfunction() + stan_add_googletest() + if(WIN32) + target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) + endif() +endif() diff --git a/cmake/MakeLocal.cmake b/cmake/MakeLocal.cmake new file mode 100644 index 00000000000..3190411533f --- /dev/null +++ b/cmake/MakeLocal.cmake @@ -0,0 +1,47 @@ +option(STAN_USE_MAKE_LOCAL "Read supported settings from make/local" ON) +set(STAN_MAKE_LOCAL "${CMAKE_SOURCE_DIR}/make/local" CACHE FILEPATH "Local Make settings file") + +macro(stan_cache_default name value type description) + if(NOT DEFINED ${name}) + set(${name} "${value}" CACHE ${type} "${description}") + endif() +endmacro() + +if(STAN_USE_MAKE_LOCAL) + file(GLOB local_settings CONFIGURE_DEPENDS "${STAN_MAKE_LOCAL}") + if(local_settings) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + find_program(STAN_SETTINGS_MAKE NAMES gmake make mingw32-make) + if(NOT STAN_SETTINGS_MAKE) + message(FATAL_ERROR "Reading make/local requires GNU Make. Use STAN_USE_MAKE_LOCAL=OFF to ignore make/local.") + endif() + execute_process( + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_LIST_DIR}/make_local.py" + --make "${STAN_SETTINGS_MAKE}" --source "${CMAKE_SOURCE_DIR}" + --local "${STAN_MAKE_LOCAL}" --output "${CMAKE_BINARY_DIR}/make-local.cmake" + RESULT_VARIABLE settings_status + ERROR_VARIABLE settings_error) + if(NOT settings_status EQUAL 0) + message(FATAL_ERROR "${settings_error}") + endif() + include("${CMAKE_BINARY_DIR}/make-local.cmake") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${CMAKE_CURRENT_LIST_DIR}/make_local.py") + message(STATUS "Loaded Make settings from ${STAN_MAKE_LOCAL}") + endif() +endif() + +function(stan_apply_make_local_flags) + foreach(language C CXX) + separate_arguments(flags NATIVE_COMMAND "${STAN_MAKE_LOCAL_${language}_FLAGS}") + foreach(flag IN LISTS flags) + add_compile_options("$<$:${flag}>") + endforeach() + endforeach() + separate_arguments(flags NATIVE_COMMAND "${STAN_MAKE_LOCAL_CPP_FLAGS}") + add_compile_options(${flags}) + separate_arguments(flags NATIVE_COMMAND "${STAN_MAKE_LOCAL_LINK_FLAGS}") + add_link_options(${flags}) + separate_arguments(libraries NATIVE_COMMAND "${STAN_MAKE_LOCAL_LIBRARIES_FLAGS}") + link_libraries(${libraries}) +endfunction() diff --git a/cmake/StanTest.cmake b/cmake/StanTest.cmake new file mode 100644 index 00000000000..1571c8af52f --- /dev/null +++ b/cmake/StanTest.cmake @@ -0,0 +1,43 @@ +# Test flags and precompiled headers shared by the test suite and the +# compile-only checks. A single set of flags and dependencies keeps PCH +# producers and consumers compatible. Do not enable IPO or add +# target-specific optimization flags. +set(STAN_EFFECTIVE_TEST_PCH ${STAN_TEST_PCH}) +if(STAN_TEST_PCH AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "9" + AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11") + # Clang 9/10 cannot deserialize Eigen's __builtin_bit_cast expressions. + # Fixed in LLVM 95d7ccb70b9cbd53f1f137c0b2411852c42c122b (Clang 11). + set(STAN_EFFECTIVE_TEST_PCH OFF) + message(STATUS "Test PCH disabled: Clang 9/10 PCH bit_cast defect (fixed in Clang 11)") +endif() + +add_library(stan_test_flags INTERFACE) +target_link_libraries(stan_test_flags INTERFACE stanmath) +if(MSVC) + target_compile_options(stan_test_flags INTERFACE ${STAN_TEST_OPTIMIZATION_FLAGS} /W3) +else() + target_compile_options(stan_test_flags INTERFACE + ${STAN_TEST_OPTIMIZATION_FLAGS} -Wall -Wno-deprecated-declarations -Wno-sign-compare) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(stan_test_flags INTERFACE + -Werror=invalid-pch -Wno-misleading-indentation) + endif() +endif() + +# Producers and consumers of a PCH must link the same options target. +function(stan_add_pch target options) + if(STAN_EFFECTIVE_TEST_PCH) + add_library(${target} OBJECT EXCLUDE_FROM_ALL "${PROJECT_SOURCE_DIR}/cmake/empty.cpp") + target_link_libraries(${target} PRIVATE ${options}) + target_precompile_headers(${target} PRIVATE ${ARGN}) + endif() +endfunction() + +# A PCH target exists only when precompiled headers are in effect. +function(stan_configure_test target options pch) + target_link_libraries(${target} PRIVATE ${options}) + if(TARGET "${pch}") + target_precompile_headers(${target} REUSE_FROM ${pch}) + endif() +endfunction() diff --git a/cmake/empty.cpp b/cmake/empty.cpp new file mode 100644 index 00000000000..1b2de39da12 --- /dev/null +++ b/cmake/empty.cpp @@ -0,0 +1 @@ +// Empty translation unit for shared PCH providers and standalone header checks. diff --git a/cmake/make_local.py b/cmake/make_local.py new file mode 100644 index 00000000000..2af6bde2b54 --- /dev/null +++ b/cmake/make_local.py @@ -0,0 +1,120 @@ +"""Evaluate supported make/local settings and export CMake defaults.""" + +import argparse +import os +from pathlib import Path +import re +import shlex +import subprocess + + +SETTINGS = { + "CC": "CMAKE_C_COMPILER", + "CXX": "CMAKE_CXX_COMPILER", + "O": "STAN_TEST_OPTIMIZATION", + "OPENCL_DEVICE_ID": "STAN_OPENCL_DEVICE_ID", + "OPENCL_PLATFORM_ID": "STAN_OPENCL_PLATFORM_ID", + **{name: name for name in ( + "STAN_THREADS", "STAN_OPENCL", "STAN_MPI", "STAN_NO_RANGE_CHECKS", + "STAN_TEST_ROW_VECTORS", "STAN_PROB_TEST_ALL")}, + **{name: "STAN_" + name + "_ROOT" for name in ( + "BOOST", "EIGEN", "TBB", "SUNDIALS", "OPENCL", "BENCHMARK")}, + "INTEGRATED_OPENCL": "STAN_INTEGRATED_OPENCL", +} +FLAGS = { + "CXX": ("CXXFLAGS", "CXXFLAGS_OPTIM", "CXXFLAGS_WARNINGS"), + "C": ("CFLAGS",), + "CPP": ("CPPFLAGS", "CPPFLAGS_OPTIM"), + "LINK": ("LDFLAGS", "LDFLAGS_OPTIM"), + "LIBRARIES": ("LDLIBS",), +} +FEATURES = {name for name in SETTINGS if name.startswith("STAN_")} + + +def bracket(value): + delimiter = "=" + while "]" + delimiter + "]" in value: + delimiter += "=" + return "[" + delimiter + "[" + value + "]" + delimiter + "]" + + +def read_local(make, source, local): + names = list(SETTINGS) + [name for group in FLAGS.values() for name in group] + escaped_local = str(local).replace("\\", "/").replace(" ", "\\ ").replace("#", "\\#").replace("$", "$$") + makefile = "include " + escaped_local + "\n" + for name in names: + makefile += (f"$(info __STAN_CMAKE_BEGIN_{name}__)\n" + f"$(info $(if $(filter file override,$(origin {name})),$({name})))\n" + f"$(info __STAN_CMAKE_END_{name}__)\n") + makefile += ("$(info __STAN_CMAKE_BEGIN_FILES__)\n$(info $(MAKEFILE_LIST))\n" + "$(info __STAN_CMAKE_END_FILES__)\n" + ".PHONY: stan-cmake-settings\nstan-cmake-settings:\n") + environment = dict(os.environ) + for name in ("MAKEFLAGS", "MFLAGS", "GNUMAKEFLAGS", "MAKELEVEL"): + environment.pop(name, None) + result = subprocess.run([make, "--no-print-directory", "-s", "-f", "-", + "stan-cmake-settings"], cwd=source, env=environment, + input=makefile, text=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if result.returncode: + raise ValueError(f"Cannot evaluate {local}:\n{result.stderr}") + values = {} + for name in names + ["FILES"]: + match = re.search(f"__STAN_CMAKE_BEGIN_{name}__\n(.*?)\n__STAN_CMAKE_END_{name}__", + result.stdout, re.DOTALL) + if not match: + raise ValueError(f"Missing GNU Make output for {name} in {local}") + values[name] = match.group(1).strip() + return values + + +def cmake_settings(values, source): + output = [] + for name, target in SETTINGS.items(): + value = values[name] + if name in FEATURES or name == "INTEGRATED_OPENCL": + value = ("ON" if value else "OFF") if name in FEATURES else ("1" if value else "0") + elif not value: + continue + elif target.endswith("_ROOT"): + path = Path(value) + value = str((source / path).resolve()) + elif name in ("CC", "CXX"): + value = ";".join(shlex.split(value)) + output.append(f"if(NOT DEFINED {target})\n set({target} {bracket(value)})\nendif()\n") + for group, names in FLAGS.items(): + value = " ".join(values[name] for name in names if values[name]) + output.append(f"set(STAN_MAKE_LOCAL_{group}_FLAGS {bracket(value)})\n") + files = [] + words = values["FILES"].split() + index = 0 + while index < len(words): + for end in range(index + 1, len(words) + 1): + path = source / " ".join(words[index:end]) + if path.is_file(): + files.append(str(path.resolve())) + index = end - 1 + break + index += 1 + output.append("set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS\n") + output.extend(" " + bracket(path) + "\n" for path in files) + output.append(")\n") + return "".join(output) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--make", required=True) + parser.add_argument("--source", type=Path, required=True) + parser.add_argument("--local", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + try: + values = read_local(args.make, args.source, args.local) + args.output.write_text(cmake_settings(values, args.source)) + except ValueError as error: + parser.exit(1, str(error) + "\n") + + +if __name__ == "__main__": + main() diff --git a/cmake/stanc.cmake b/cmake/stanc.cmake new file mode 100644 index 00000000000..09583e4fda8 --- /dev/null +++ b/cmake/stanc.cmake @@ -0,0 +1,26 @@ +if(NOT EXISTS "${STAN_STANC_FILE}") + if(CMAKE_HOST_WIN32) + set(platform windows) + elseif(CMAKE_HOST_APPLE) + set(platform mac) + else() + set(platform linux) + endif() + find_program(curl_program NAMES curl) + if(NOT curl_program) + message(FATAL_ERROR "Downloading the expression test compiler requires curl") + endif() + execute_process(COMMAND "${curl_program}" -L --fail --retry 5 --retry-delay 10 + "https://github.com/stan-dev/stanc3/releases/download/nightly/${platform}-stanc" + -o "${STAN_STANC_FILE}" RESULT_VARIABLE status) + if(NOT status EQUAL 0) + file(REMOVE "${STAN_STANC_FILE}") + message(FATAL_ERROR "Downloading the expression test compiler failed") + endif() + if(NOT CMAKE_HOST_WIN32) + execute_process(COMMAND chmod +x "${STAN_STANC_FILE}" RESULT_VARIABLE status) + if(NOT status EQUAL 0) + message(FATAL_ERROR "Setting expression test compiler permissions failed") + endif() + endif() +endif() diff --git a/runTests.py b/runTests.py index ddf65fe83c1..c0bd7935c9c 100755 --- a/runTests.py +++ b/runTests.py @@ -1,76 +1,19 @@ #!/usr/bin/env python3 +"""Build and run Stan Math tests with CMake.""" -""" -Replacement for runtest target in Makefile. - -Call script with '-h' as an option to see a helpful message. -""" - -from __future__ import print_function - +from concurrent.futures import ThreadPoolExecutor import glob +import json import os -import os.path -import platform +from pathlib import Path import re +import shlex +import shutil import subprocess import sys -import time from argparse import ArgumentParser, RawTextHelpFormatter -winsfx = ".exe" -testsfx = "_test.cpp" - -allowed_paths_with_jumbo = [ - "test/unit/math/prim/", - "test/unit/math/prim/constraint", - "test/unit/math/", - "test/unit/math/rev/", - "test/unit/math/rev/constraint", - "test/unit/math/fwd/", - "test/unit/math/fwd/constraint", - "test/unit/math/mix/", - "test/unit/math/mix/fun/", - "test/unit/math/opencl/", - "test/unit/", -] - -jumbo_folders = [ - "test/unit/math/prim/constraint", - "test/unit/math/prim/core", - "test/unit/math/prim/err", - "test/unit/math/prim/fun", - "test/unit/math/prim/functor", - "test/unit/math/prim/meta", - "test/unit/math/prim/prob", - "test/unit/math/rev/constraint", - "test/unit/math/rev/core", - "test/unit/math/rev/err", - "test/unit/math/rev/fun", - "test/unit/math/rev/functor", - "test/unit/math/rev/meta", - "test/unit/math/rev/prob", - "test/unit/math/fwd/constraint", - "test/unit/math/fwd/core", - "test/unit/math/fwd/fun", - "test/unit/math/fwd/functor", - "test/unit/math/fwd/meta", - "test/unit/math/fwd/prob", - "test/unit/math/mix/core", - "test/unit/math/mix/fun", - "test/unit/math/mix/functor", - "test/unit/math/mix/meta", - "test/unit/math/mix/prob", - "test/unit/math/opencl/device_functions", - "test/unit/math/opencl/kernel_generator", - "test/unit/math/opencl/prim", - "test/unit/math/opencl/prim/constraint", - "test/unit/math/opencl/rev", - "test/unit/math/opencl/rev/constraint", -] - - -def processCLIArgs(): +def processCLIArgs(arguments): """ Define and process the command line interface to the runTests.py script. """ @@ -83,9 +26,29 @@ def processCLIArgs(): formatter_class=RawTextHelpFormatter, ) - # Now define all the rules of the command line args and opts + parser.set_defaults(command=None) + if arguments and arguments[0] in ("changed-unit-filter", "generate-expressions", "build-tests"): + commands = parser.add_subparsers(dest="command", required=True) + commands.add_parser("changed-unit-filter") + expressions = commands.add_parser("generate-expressions") + expressions.add_argument("build_dir") + expressions.add_argument("--functions", nargs="*", default=[]) + expressions.add_argument("--batches", type=int, default=6) + build = commands.add_parser("build-tests") + build.add_argument("build_dir") + build.add_argument("--source-regex", required=True) + build.add_argument("--jobs", type=int, default=1) + inputs = parser.parse_args(arguments) + if getattr(inputs, "jobs", 1) < 1 or getattr(inputs, "batches", 1) < 1: + parser.error("job and batch counts must be positive") + return inputs + + parser.add_argument( + "-j", metavar="N", type=int, default=1, help="parallel build jobs (default: 1)" + ) parser.add_argument( - "-j", metavar="N", type=int, default=1, help="number of cores for make to use" + "--test-jobs", metavar="N", type=int, default=1, + help="test executables run at once (default: 1)" ) parser.add_argument( @@ -123,6 +86,12 @@ def processCLIArgs(): action="store_true", help="Don't make tests, just try to run them (must already be made).", ) + parser.add_argument( + "--configure-only", + dest="configure_only", + action="store_true", + help="Only configure the CMake build directory.", + ) parser.add_argument( "--run-all", dest="run_all", @@ -140,323 +109,313 @@ def processCLIArgs(): "--jumbo-test", dest="do_jumbo", action="store_true", - help="Build/run jumbo tests.", + help="accepted for compatibility, directory tests already use one executable", ) tests_help_msg = "The path(s) to the test case(s) to run.\n" tests_help_msg += "Example: 'test/unit', 'test/prob', and/or\n" - tests_help_msg += " 'test/unit/math/prim/fun/abs_test.cpp'" + tests_help_msg += " 'test/unit/math/prim/fun/abs_test.cpp', and/or\n" + tests_help_msg += " 'test/prob/normal/normal_test.hpp' (a distribution fixture)" parser.add_argument("tests", nargs="*", type=str, help=tests_help_msg) + parser.add_argument("--build-dir", default="build", help="CMake build directory") + parser.add_argument("--fresh", action="store_true", + help="reset the CMake cache before configuring, like cmake --fresh") + parser.add_argument("--cmake-arg", action="append", default=[], + help="extra configure argument, e.g. --cmake-arg=-DSTAN_THREADS=ON") + parser.add_argument("--config", default="ReleaseTesting", + help="CMake build configuration (ReleaseTesting: -O1 without NDEBUG)") # And parse the command line against those rules - return parser.parse_args() - - -def stopErr(msg, returncode): - """Report an error message to stderr and exit with a given code.""" - sys.stderr.write("%s\n" % msg) - sys.stderr.write("exit now (%s)\n" % time.strftime("%x %X %Z")) - sys.exit(returncode) - - -def isWin(): - return platform.system().lower().startswith( - "windows" - ) or os.name.lower().startswith("windows") - - -batchSize = 20 if isWin() else 200 -jumboSize = 5 if isWin() else 12 -maxChangedTests = 20 - - -def mungeName(name): - """Set up the makefile target name""" - if name.endswith(testsfx): - name = name.replace(testsfx, "_test") - - if isWin(): - name += winsfx - name = name.replace("\\", "/") - return name - - -def doCommand(command, exit_on_failure=True): - """Run command as a shell command and report/exit on errors.""" - print("------------------------------------------------------------") - print("%s" % command) - p1 = subprocess.Popen(command, shell=True) - p1.wait() - if exit_on_failure and (not (p1.returncode is None) and not (p1.returncode == 0)): - stopErr("%s failed" % command, p1.returncode) - - -def generateTests(j): - """Generate all tests and pass along the j parameter to make.""" - doCommand("make -j%d generate-tests -s" % (j or 1)) - - -def divide_chunks(l, n): - # looping till length l - for i in range(0, len(l), n): - yield l[i : i + n] - -include_pattern = re.compile(r'^\s*#include\s+<.*>$') - -def generateJumboTests(paths, debug=False): - jumbo_files_to_create = [] - jumbo_files = [] - for p in paths: - if not p.endswith(testsfx) and not p.endswith("/"): - p = p + "/" - if p in allowed_paths_with_jumbo: - jumbo_files_to_create.extend([x for x in jumbo_folders if x.startswith(p)]) - else: - stopErr("The --jumbo flag is only allowed with top level folders.", 10) - for jf in jumbo_files_to_create: - tests_in_subfolder = sorted(os.path.join(jf, x) for x in os.listdir(jf) if x.endswith(testsfx)) - for i, tests in enumerate(divide_chunks(tests_in_subfolder, jumboSize)): - - includes = [] - jumbo_contents = [] - for t in tests: - with open(t, "r") as test_file: - contents_raw = test_file.read() - - test_contents = [] - for l in contents_raw.splitlines(): - if include_pattern.fullmatch(l.strip()): - includes.append(l.strip()) - continue - test_contents.append(l) - - jumbo_contents.append('\n'.join(test_contents)) - - jumbo_file_path = jf + "_" + str(i) + testsfx - jumbo_files.append(jumbo_file_path) - if debug: - print("Generating jumbo test file '{}' with tests: {}".format( jumbo_file_path, ', '.join(tests))) - with open(jumbo_file_path, "w") as jumbo_file: - jumbo_file.write('\n'.join(list(dict.fromkeys(includes)))) - jumbo_file.write('\n') - jumbo_file.write('\n'.join(jumbo_contents)) - return jumbo_files - - -def cleanupJumboTests(paths): - for f in paths: - if os.path.exists(f): - os.remove(f) - - -def makeTest(name, j): - """Run the make command for a given single test.""" - doCommand("make -j%d %s" % (j or 1, name)) - - -def commandExists(command): - p = subprocess.Popen( - command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - p.wait() - return p.returncode != 127 - - -def runTest(name, run_all=False, mpi=False, j=1): - executable = mungeName(name).replace("/", os.sep) - xml = mungeName(name).replace(winsfx, "") - command = '%s --gtest_output="xml:%s.xml"' % (executable, xml) - if mpi: - if not commandExists("mpirun"): - stopErr( - "Error: need to have mpi (and mpirun) installed to run mpi tests", - -1, - ) - if "mpi_" in name: - j = j > 2 and j or 2 - else: - j = 1 - command = "mpirun -np {} {}".format(j, command) - doCommand(command, not run_all) - - -def test_files_in_folder(folder): - """Returns a list of test files (*_test.cpp) in the folder and all - its subfolders recursively. The folder can be written with - wildcards as with the Unix find command. - """ - files = [] - for f in glob.glob(folder): - if os.path.isdir(f): - files.extend(test_files_in_folder(f + os.sep + "**")) - else: - if f.endswith(testsfx): - files.append(f) - return files - - -def findTests(base_path, filter_names, do_jumbo=False): - tests = [] - for path in base_path: - if (not os.path.isdir(path)) and path.endswith("_test"): - tests.append(path + ".cpp") - else: - tests.extend(test_files_in_folder(path)) - tests = map(mungeName, tests) - tests = [ - test - for test in tests - if all(filter_name in test for filter_name in filter_names) - ] - if do_jumbo: - filtered_jumbo_tests = [] - for t in tests: - add = True - for k in jumbo_folders: - k = k + "/" - if t.startswith(k): - add = False - break - if add: - filtered_jumbo_tests.append(t) - tests = filtered_jumbo_tests - return tests - - -def findChangedTests(debug): - import subprocess - - changed_files = subprocess.run( - ["git", "diff", "--name-only", "--diff-filter=d", "origin/develop...HEAD"], - text=True, capture_output=True - ).stdout.splitlines() - if debug: - print("Changed files:", changed_files) - - # changes in prim should also test other non-prim signatures - test_deps = {"prim/": ["prim/", "rev/", "mix/", "fwd/"], "rev/": ["rev/", "mix/"], "fwd/": ["fwd/", "mix/"]} - - changed_tests = set() - for f in changed_files: - if 'stan/' in f and f.endswith('.hpp') and 'opencl' not in f: # changed fn - maybe_test = f.replace('stan/', 'test/unit/').replace('.hpp', testsfx) - for (path, replacements) in test_deps.items(): - if path in maybe_test: - for rep in replacements: - maybe_test = maybe_test.replace(path, rep) - if os.path.exists(maybe_test): - changed_tests.add(maybe_test) - - if f.endswith(testsfx) and 'opencl' not in f: - changed_tests.add(f) - - if len(changed_tests) > maxChangedTests: - stopErr("Number of changed tests excluded maximum, not running priority tests", 0) - - return list(map(mungeName, changed_tests)) - - -def batched(tests): - return [tests[i : i + batchSize] for i in range(0, len(tests), batchSize)] - - -def handleExpressionTests(tests, only_functions, n_test_files): - expression_tests = False - for n, i in list(enumerate(tests))[::-1]: - if "test/expressions" in i or "test\\expressions" in i: - del tests[n] - expression_tests = True - if expression_tests: - HERE = os.path.dirname(os.path.realpath(__file__)) - sys.path.append(os.path.join(HERE, "test")) - sys.path.append(os.path.join(HERE, "test/expressions")) - import generate_expression_tests - - generate_expression_tests.main(only_functions, n_test_files) - for i in range(n_test_files): - tests.append("test/expressions/tests%d_test.cpp" % i) - elif only_functions: - stopErr( - "--only-functions can only be specified if running expression tests (test/expressions)", - -1, - ) - - -def checkToolchainPathWindows(): - if isWin(): - p1 = subprocess.Popen( - "where.exe make", - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ) - out, err = p1.communicate() - if re.search(r" |\(|\)", out): - stopErr( - "The RTools toolchain is installed in a path with spaces or bracket. Please reinstall to a valid path.", - -1, - ) - - -def main(): - inputs = processCLIArgs() - checkToolchainPathWindows() - - try: - with open("make/local") as f: - stan_mpi = "STAN_MPI" in f.read() - except IOError: - stan_mpi = False - - jumboFiles = [] - - if inputs.changed: - tests = findChangedTests(inputs.debug) + inputs = parser.parse_args(arguments) + if inputs.j < 1 or inputs.test_jobs < 1: + parser.error("-j and --test-jobs must be positive") + if inputs.test_only and (inputs.make_only or inputs.configure_only): + parser.error("--test-only cannot be combined with --make-only or --configure-only") + if inputs.fresh and inputs.test_only: + parser.error("--fresh cannot be combined with --test-only") + return inputs + + +def findChangedTests(files, existing_tests, limit=20): + """Select priority tests from supplied changes and existing test paths.""" + dependencies = {"prim": ("prim", "rev", "mix", "fwd"), + "rev": ("rev", "mix"), "fwd": ("fwd", "mix")} + selected = set() + for path in files: + if "opencl" in path: + continue + if path.endswith("_test.cpp"): + selected.add(path) + elif path.startswith("stan/") and path.endswith(".hpp"): + candidate = path.replace("stan/", "test/unit/", 1)[:-4] + "_test.cpp" + layer = candidate.split("/")[3] + for replacement in dependencies.get(layer, (layer,)): + test = candidate.replace("/" + layer + "/", "/" + replacement + "/", 1) + if test in existing_tests: + selected.add(test) + return sorted(selected) if len(selected) <= limit else [] + + +def is_fixture(path): + return re.fullmatch(r"test/prob/[^/]+/[^/]+_test\.hpp", path) is not None + + +def cmake_escape(text): + return re.sub(r"([.+*?^$()\[\]{}|\\])", r"\\\1", text) + + +def selection_regex(paths): + """Match the source paths of the selected files and fixtures' generated tests.""" + patterns = [cmake_escape(path[:-len("_test.hpp")]) + "_[0-9]+_generated_.*" if is_fixture(path) + else cmake_escape(path) for path in sorted(paths) + if is_fixture(path) or path.endswith(".cpp")] + return "^(" + "|".join(patterns) + ")$" if patterns else "" + + +def fixture_tests(inventory, fixtures): + """Expand fixtures to the generated sources CMake registered for them.""" + prefixes = tuple(fixture[:-len("_test.hpp")] + "_" for fixture in fixtures) + sources = set() + for test in inventory["tests"]: + for entry in test.get("properties", []): + if entry["name"] != "LABELS": + continue + for label in entry["value"]: + for prefix in prefixes: + if label.startswith(prefix) and re.match( + r"[0-9]+_generated_.*_test\.cpp$", label[len(prefix):]): + sources.add(label) + return sorted(sources) + + +def select_paths(matches, directories): + """Validate expanded paths and remove selections covered by a directory.""" + paths = set() + for argument, expanded in matches.items(): + if not expanded: + raise ValueError("No matching tests: " + argument) + for path in expanded: + if not any(path == base or path.startswith(base + "/") + for base in ("test/unit", "test/prob", "test/expressions")): + raise ValueError("Unsupported test path: " + path) + if path not in directories and not path.endswith("_test.cpp") and not is_fixture(path): + raise ValueError("Expected a test directory, *_test.cpp, or " + "test/prob/*/*_test.hpp: " + path) + paths.add(path) + return [path for path in sorted(paths) + if not any(path.startswith(parent + "/") for parent in directories)] + + +def target_for(path): + if path.endswith(".cpp"): + target = path[:-4].replace("/", "_") else: - if not inputs.test_only: - # pass 0: generate all auto-generated tests - if any("test/prob" in arg for arg in inputs.tests): - generateTests(inputs.j) - - if inputs.do_jumbo: - jumboFiles = generateJumboTests(inputs.tests) - if inputs.e == -1: - if inputs.j == 1: - num_expr_test_files = 1 - else: - num_expr_test_files = inputs.j * 4 - else: - num_expr_test_files = inputs.e - handleExpressionTests(inputs.tests, inputs.only_functions, num_expr_test_files) - - tests = findTests(inputs.tests, inputs.f, inputs.do_jumbo) - - if not tests: - if inputs.changed: - stopErr("No changed tests were found!", 0) - stopErr( - "No matching tests found. Check the path passed on the command line", -1 - ) - + prefix = path.replace("test/unit/math", "unit_math", 1) + prefix = prefix.replace("test/unit", "unit", 1) + prefix = prefix.replace("test/prob", "prob_math", 1) + prefix = prefix.replace("test/expressions", "expressions", 1) + target = prefix.replace("/", "_") + "_all_tests" + folder = "test/unit" if path.startswith("test/unit") else ( + "test/prob" if path.startswith("test/prob") else "test") + return target, folder + + +def build_commands(inputs, paths, root, build_dir): + """Plan configuration and builds for the CMake targets selected by the user.""" + if inputs.test_only: + return [] + expressions = any(path.startswith("test/expressions") for path in paths) + regex = selection_regex(paths) + configure = ["cmake", "-S", str(root), "-B", str(build_dir), + "-DCMAKE_BUILD_TYPE=" + inputs.config, "-DBUILD_TESTING=ON", + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-DSTAN_TEST_HEADERS=OFF", + "-DSTAN_BUILD_PROB_TESTS=" + ("ON" if any(path.startswith("test/prob") for path in paths) else "OFF"), + "-DSTAN_BUILD_EXPRESSION_TESTS=" + ("ON" if expressions else "OFF"), + "-DSTAN_TEST_NAME_FILTERS=" + ";".join(inputs.f), + "-DSTAN_SELECTED_TEST_REGEX=" + regex] + if expressions: + configure.append("-DSTAN_EXPRESSION_TEST_DIR=" + str(build_dir / "expression-sources")) + if inputs.configure_only: + return [configure + inputs.cmake_arg] + # Individual files and fixtures build through one aggregate target. + targets = [target_for(path)[0] for path in paths if not path.endswith(".cpp") and not is_fixture(path)] + if regex: + targets.append("stan_selected_tests") + build = ["cmake", "--build", str(build_dir), "--config", inputs.config, + "--parallel", str(inputs.j), "--target"] + targets + return [configure + inputs.cmake_arg, build] + + +def parse_cache(text): + cache = {} + for line in text.splitlines(): + if not line.startswith(("//", "#")) and "=" in line and ":" in line.split("=", 1)[0]: + key, value = line.split("=", 1) + cache[key.split(":", 1)[0]] = value + return cache + + +def test_command(path, build_dir, config, cache, windows): + """Plan an executable invocation using supplied CMake settings.""" + target, folder = target_for(path) + executable = build_dir / folder / target + if cache.get("CMAKE_CONFIGURATION_TYPES"): + executable = executable.parent / config / executable.name + if cache.get("CMAKE_SYSTEM_NAME") == "Windows" or windows: + executable = executable.with_suffix(".exe") + args = [str(executable), "--gtest_output=xml:" + str(build_dir / "test-results" / (target + ".xml"))] + if cache.get("STAN_EFFECTIVE_MPI", cache.get("STAN_MPI")) in ("ON", "TRUE", "1"): + ranks = "2" if not path.endswith(".cpp") or "mpi_" in path else "1" + args = [cache.get("MPIEXEC_EXECUTABLE", "mpirun"), + cache.get("MPIEXEC_NUMPROC_FLAG", "-np"), ranks] + [ + flag for flag in cache.get("MPIEXEC_PREFLAGS", "").split(";") if flag] + args + [ + flag for flag in cache.get("MPIEXEC_POSTFLAGS", "").split(";") if flag] + return args + + +def source_filter(paths): + return "^(" + "|".join(re.escape(path) for path in sorted(set(paths))) + ")$" if paths else "" + + +def build_tests(inventory, source_pattern, build_dir, jobs, batch_size): + """Plan builds using the source labels registered by CMake.""" + pattern = re.compile(source_pattern) + selected, groups = [], {} + for test in inventory["tests"]: + labels = [label for entry in test.get("properties", []) + if entry["name"] == "LABELS" for label in entry["value"]] + if any(pattern.search(label) for label in labels if not label.startswith("stan_build:")): + selected.append(test["name"]) + for label in labels: + if label.startswith("stan_build:"): + groups.setdefault(label[len("stan_build:"):], set()).add(test["name"]) + if not selected: + raise ValueError("No registered tests match: " + source_pattern) + # Makefile generators serialize separate top-level targets. An existing + # aggregate shares one dependency graph and honors the requested job count. + remaining, targets = set(selected), [] + for target, members in sorted(groups.items(), key=lambda item: (-len(item[1]), item[0])): + if len(members) > 1 and members <= remaining: + targets.append(target) + remaining.difference_update(members) + targets.extend(name for name in selected if name in remaining) + return [["cmake", "--build", str(build_dir), "--parallel", str(jobs), "--target"] + + targets[offset:offset + batch_size] for offset in range(0, len(targets), batch_size)] + + +def main(arguments, root, working_dir, windows, error_stream): + """Perform command-line I/O. Helpers above only transform explicit inputs.""" + inputs = processCLIArgs(arguments) try: - # pass 1: make test executables - if not inputs.test_only: - for batch in batched(tests): - if inputs.debug: - print("Test batch: ", batch) - makeTest(" ".join(batch), inputs.j) - if not inputs.make_only: - # pass 2: run test targets - for t in tests: - if inputs.debug: - print("run single test: %s" % t) - runTest(t, inputs.run_all, mpi=stan_mpi, j=inputs.j) - except BaseException as e: - print(e, file=sys.stderr) - sys.exit(1) - finally: - cleanupJumboTests(jumboFiles) - pass + if getattr(inputs, "changed", False) or inputs.command == "changed-unit-filter": + files = subprocess.run(["git", "diff", "--name-only", "--diff-filter=d", + "origin/develop...HEAD"], cwd=root, check=True, + text=True, capture_output=True).stdout.splitlines() + if getattr(inputs, "debug", False): + print("Changed files:", files) + existing = {path.relative_to(root).as_posix() + for path in (root / "test/unit").rglob("*_test.cpp")} + paths = findChangedTests(files, existing) + if inputs.command == "changed-unit-filter": + print(source_filter(paths)) + return 0 + if not paths: + print("No changed tests were found!") + return 0 + elif inputs.command is None: + matches = {} + for argument in inputs.tests: + # os.path.join keeps a trailing slash, so "test/prob/*/" matches directories. + expanded = glob.glob(os.path.join(str(root), argument)) + if not expanded and argument.replace("\\", "/").lstrip("./").startswith( + ("test/prob/", "test/expressions/")) and argument.endswith("_test.cpp"): + expanded = [str(root / argument)] + matches[argument] = [Path(path).resolve().relative_to(root).as_posix() + for path in expanded] + directories = {path for expanded in matches.values() for path in expanded + if (root / path).is_dir()} + paths = select_paths(matches, directories) + build_dir = (working_dir / inputs.build_dir).resolve() + if inputs.command == "build-tests": + inventory = subprocess.run(["ctest", "--show-only=json-v1"], + cwd=build_dir, check=True, text=True, capture_output=True) + commands = build_tests(json.loads(inventory.stdout), inputs.source_regex, + build_dir, inputs.jobs, 50 if windows else 500) + else: + generate = inputs.command == "generate-expressions" + if not generate: + paths = [path for path in paths if not path.endswith(".cpp") + or all(value in path for value in inputs.f)] + if not paths and not inputs.configure_only: + raise ValueError("No matching tests found") + expressions = any(path.startswith("test/expressions") for path in paths) + if inputs.only_functions and not expressions: + raise ValueError("--only-functions requires test/expressions") + generate = expressions and not inputs.test_only + if generate: + # Keep the legacy generator's output setting inside the I/O boundary. + sys.path.insert(0, str(root / "test")) + import generate_expression_tests + output = build_dir / "expression-sources" + output.mkdir(parents=True, exist_ok=True) + for source in output.glob("*test.cpp"): + source.unlink() + functions = inputs.functions if inputs.command else inputs.only_functions + # Like develop, four files per job keep each translation unit's memory bounded. + batches = inputs.batches if inputs.command else (inputs.e if inputs.e > 0 else inputs.j * 4) + generate_expression_tests.src_folder = str(output) + "/" + generate_expression_tests.main(functions, batches) + if not list(output.glob("*test.cpp")): + raise RuntimeError("Expression generator produced no tests") + if inputs.command == "generate-expressions": + return 0 + commands = build_commands(inputs, paths, root, build_dir) + if inputs.fresh: + # cmake --fresh (CMake 3.24+) deletes the same two paths. + (build_dir / "CMakeCache.txt").unlink(missing_ok=True) + shutil.rmtree(build_dir / "CMakeFiles", ignore_errors=True) + for command in commands: + print(" ".join(shlex.quote(str(arg)) for arg in command), flush=True) + subprocess.run(command, cwd=root, check=True) + if inputs.command == "build-tests" or inputs.make_only or inputs.configure_only: + return 0 + fixtures = [path for path in paths if is_fixture(path)] + if fixtures: + inventory = subprocess.run(["ctest", "--show-only=json-v1"], + cwd=build_dir, check=True, text=True, capture_output=True) + generated =fixture_tests(json.loads(inventory.stdout), fixtures) + if not generated: + raise ValueError("No generated tests found for: " + " ".join(fixtures)) + paths = [path for path in paths if not is_fixture(path)] + generated + cache_file = build_dir / "CMakeCache.txt" + cache = parse_cache(cache_file.read_text() if cache_file.exists() else "") + (build_dir / "test-results").mkdir(parents=True, exist_ok=True) + failed = False + commands = [test_command(path, build_dir, inputs.config, cache, windows) for path in paths] + if inputs.test_jobs > 1: + # Concurrent executables print their output in order once each finishes. + def run(command): + return subprocess.run(command, cwd=root, text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + with ThreadPoolExecutor(inputs.test_jobs) as pool: + for command, result in zip(commands, pool.map(run, commands)): + print(" ".join(shlex.quote(arg) for arg in command), flush=True) + print(result.stdout, end="", flush=True) + failed = failed or result.returncode != 0 + return int(failed) + for command in commands: + print(" ".join(shlex.quote(arg) for arg in command), flush=True) + try: + subprocess.run(command, cwd=root, check=True) + except subprocess.CalledProcessError: + if not inputs.run_all: + raise + failed = True + return int(failed) + except (ValueError, OSError, RuntimeError, subprocess.CalledProcessError) as error: + print(error, file=error_stream) + return 1 if __name__ == "__main__": - main() + sys.exit(main(sys.argv[1:], Path(__file__).resolve().parent, + Path.cwd(), os.name == "nt", sys.stderr)) diff --git a/stan/math/fwd/fun/mdivide_left_tri_low.hpp b/stan/math/fwd/fun/mdivide_left_tri_low.hpp index 8dfd8f4cb2d..d17a4c385f2 100644 --- a/stan/math/fwd/fun/mdivide_left_tri_low.hpp +++ b/stan/math/fwd/fun/mdivide_left_tri_low.hpp @@ -20,9 +20,6 @@ template , std::decay_t::RowsAtCompileTime, std::decay_t::ColsAtCompileTime> mdivide_left_tri_low(T1&& A, T2&& b) { - constexpr int S1 = std::decay_t::RowsAtCompileTime; - constexpr int C2 = std::decay_t::ColsAtCompileTime; - check_square("mdivide_left_tri_low", "A", A); check_multiplicable("mdivide_left_tri_low", "A", A, "b", b); if (A.size() == 0) { @@ -47,7 +44,6 @@ template * = nullptr, inline Eigen::Matrix, std::decay_t::RowsAtCompileTime, std::decay_t::ColsAtCompileTime> mdivide_left_tri_low(T1&& A, T2&& b) { - constexpr int S1 = std::decay_t::RowsAtCompileTime; check_square("mdivide_left_tri_low", "A", A); check_multiplicable("mdivide_left_tri_low", "A", A, "b", b); if (A.size() == 0) { @@ -64,8 +60,6 @@ template * = nullptr, inline Eigen::Matrix, std::decay_t::RowsAtCompileTime, std::decay_t::ColsAtCompileTime> mdivide_left_tri_low(T1&& A, T2&& b) { - constexpr int S1 = std::decay_t::RowsAtCompileTime; - constexpr int C2 = std::decay_t::ColsAtCompileTime; check_square("mdivide_left_tri_low", "A", A); check_multiplicable("mdivide_left_tri_low", "A", A, "b", b); if (A.size() == 0) { diff --git a/stan/math/opencl/kernel_generator/colwise_reduction.hpp b/stan/math/opencl/kernel_generator/colwise_reduction.hpp index e4520b154c6..088577f8a88 100644 --- a/stan/math/opencl/kernel_generator/colwise_reduction.hpp +++ b/stan/math/opencl/kernel_generator/colwise_reduction.hpp @@ -222,7 +222,6 @@ class colwise_sum_ : public colwise_reduction, T, sum_op> { */ template * = nullptr> inline auto colwise_sum(T&& a) { - auto&& arg_copy = as_operation_cl(std::forward(a)).deep_copy(); return colwise_sum_>( as_operation_cl(std::forward(a))); } @@ -266,7 +265,6 @@ class colwise_prod_ : public colwise_reduction, T, prod_op> { */ template * = nullptr> inline auto colwise_prod(T&& a) { - auto&& arg_copy = as_operation_cl(std::forward(a)).deep_copy(); return colwise_prod_>( as_operation_cl(std::forward(a))); } @@ -315,7 +313,6 @@ class colwise_max_ : public colwise_reduction< */ template * = nullptr> inline auto colwise_max(T&& a) { - auto&& arg_copy = as_operation_cl(std::forward(a)).deep_copy(); return colwise_max_>( as_operation_cl(std::forward(a))); } diff --git a/stan/math/opencl/kernel_generator/multi_result_kernel.hpp b/stan/math/opencl/kernel_generator/multi_result_kernel.hpp index 0b0cbadec57..7c235d8af26 100644 --- a/stan/math/opencl/kernel_generator/multi_result_kernel.hpp +++ b/stan/math/opencl/kernel_generator/multi_result_kernel.hpp @@ -342,11 +342,10 @@ class results_cl { index_apply([this, &exprs](auto... Is) { auto tmp = std::tuple_cat(make_assignment_pair( std::get(results_), std::get(exprs.expressions_))...); - index_apply::value>( - [this, &tmp](auto... Is2) { - assignment_impl(std::make_tuple(std::make_pair( - std::get(tmp).first, std::get(tmp).second)...)); - }); + index_apply::value>([&tmp](auto... Is2) { + assignment_impl(std::make_tuple(std::make_pair( + std::get(tmp).first, std::get(tmp).second)...)); + }); }); } diff --git a/stan/math/opencl/kernel_generator/reduction_2d.hpp b/stan/math/opencl/kernel_generator/reduction_2d.hpp index 4d019f1012c..b028c50f60a 100644 --- a/stan/math/opencl/kernel_generator/reduction_2d.hpp +++ b/stan/math/opencl/kernel_generator/reduction_2d.hpp @@ -231,7 +231,6 @@ class sum_2d_ : public reduction_2d, T, sum_op> { */ template * = nullptr> inline auto sum_2d(T&& a) { - auto&& arg_copy = as_operation_cl(std::forward(a)).deep_copy(); return sum_2d_>(as_operation_cl(std::forward(a))); } @@ -274,7 +273,6 @@ class prod_2d_ : public reduction_2d, T, prod_op> { */ template * = nullptr> inline auto prod_2d(T&& a) { - auto&& arg_copy = as_operation_cl(std::forward(a)).deep_copy(); return prod_2d_>(as_operation_cl(std::forward(a))); } @@ -321,7 +319,6 @@ class max_2d_ */ template * = nullptr> inline auto max_2d(T&& a) { - auto&& arg_copy = as_operation_cl(std::forward(a)).deep_copy(); return max_2d_>(as_operation_cl(std::forward(a))); } diff --git a/stan/math/opencl/opencl_context.hpp b/stan/math/opencl/opencl_context.hpp index e2373df126d..3a1c95fd0a5 100644 --- a/stan/math/opencl/opencl_context.hpp +++ b/stan/math/opencl/opencl_context.hpp @@ -433,7 +433,8 @@ class opencl_context { kernel_caches_.push_back(cache); } }; -static opencl_context opencl_context; +// Share the context and its kernel-cache registry across translation units. +inline opencl_context opencl_context; } // namespace math } // namespace stan diff --git a/stan/math/opencl/prim/binomial_logit_glm_lpmf.hpp b/stan/math/opencl/prim/binomial_logit_glm_lpmf.hpp index a569498f5e1..340f9250b84 100644 --- a/stan/math/opencl/prim/binomial_logit_glm_lpmf.hpp +++ b/stan/math/opencl/prim/binomial_logit_glm_lpmf.hpp @@ -34,7 +34,6 @@ inline return_type_t binomial_logit_glm_lpmf( const T_beta_cl& beta) { static const char* function = "binomial_logit_glm_lpmf(OpenCL)"; using T_partials_return = partials_return_t; - constexpr bool is_y_vector = !is_stan_scalar::value; constexpr bool is_alpha_vector = !is_stan_scalar::value; const size_t N_instances diff --git a/stan/math/opencl/prim/logistic_lcdf.hpp b/stan/math/opencl/prim/logistic_lcdf.hpp index f16c9518bd1..4bda338ded5 100644 --- a/stan/math/opencl/prim/logistic_lcdf.hpp +++ b/stan/math/opencl/prim/logistic_lcdf.hpp @@ -64,7 +64,6 @@ inline return_type_t logistic_lcdf( auto sigma_positive_finite_expr = 0 < sigma_val && isfinite(sigma_val); auto any_y_neg_inf = colwise_max(cast(y_val == NEGATIVE_INFTY)); - auto cond = y_val == INFTY; auto inv_sigma = elt_divide(1.0, sigma_val); auto mu_minus_y_div_sigma = elt_multiply(mu_val - y_val, inv_sigma); auto exp_scaled_diff = exp(mu_minus_y_div_sigma); diff --git a/stan/math/opencl/rev/add_diag.hpp b/stan/math/opencl/rev/add_diag.hpp index 00f4d7ca7a9..660c307312b 100644 --- a/stan/math/opencl/rev/add_diag.hpp +++ b/stan/math/opencl/rev/add_diag.hpp @@ -34,7 +34,7 @@ inline auto add_diag(const T_m& mat, const T_a& to_add) { var_value> res = add_diag(value_of(mat), value_of(to_add)); - reverse_pass_callback([mat_arena, to_add_arena, res]() mutable { + reverse_pass_callback([=]() mutable { if constexpr (is_autodiff_v) { adjoint_of(mat_arena) += res.adj(); } diff --git a/stan/math/opencl/rev/append_col.hpp b/stan/math/opencl/rev/append_col.hpp index 44d735e6e4f..82e2d2eab8f 100644 --- a/stan/math/opencl/rev/append_col.hpp +++ b/stan/math/opencl/rev/append_col.hpp @@ -45,7 +45,7 @@ inline var_value> append_col(T_a&& a, T_b&& b) { return make_callback_var( append_col(value_of(a_arena), value_of(b_arena)), - [a_arena, b_arena](const vari_value>& res) mutable { + [=](const vari_value>& res) mutable { if constexpr (is_autodiff_v) { adjoint_of(a_arena) += block_zero_based( res.adj(), 0, 0, a_arena.rows(), a_arena.cols()); diff --git a/stan/math/opencl/rev/append_row.hpp b/stan/math/opencl/rev/append_row.hpp index 19a418ead62..395835fe133 100644 --- a/stan/math/opencl/rev/append_row.hpp +++ b/stan/math/opencl/rev/append_row.hpp @@ -43,7 +43,7 @@ inline var_value> append_row(T_a&& a, T_b&& b) { return make_callback_var( append_row(value_of(a_arena), value_of(b_arena)), - [a_arena, b_arena](const vari_value>& res) mutable { + [=](const vari_value>& res) mutable { if constexpr (is_autodiff_v) { adjoint_of(a_arena) += block_zero_based( res.adj(), 0, 0, a_arena.rows(), a_arena.cols()); diff --git a/stan/math/opencl/rev/mdivide_left_tri_low.hpp b/stan/math/opencl/rev/mdivide_left_tri_low.hpp index 1431e25e49c..ad848c4ea92 100644 --- a/stan/math/opencl/rev/mdivide_left_tri_low.hpp +++ b/stan/math/opencl/rev/mdivide_left_tri_low.hpp @@ -39,7 +39,7 @@ inline var_value> mdivide_left_tri_low(T1&& A, T2&& b) { = tri_inverse(value_of(A_arena)); return make_callback_var( A_tri_inv * value_of(b_arena), - [A_arena, b_arena, A_tri_inv](const vari_value>& res) { + [=](const vari_value>& res) { matrix_cl adjB = transpose(A_tri_inv) * res.adj(); if constexpr (is_autodiff_v) { matrix_cl adjA = adjB * transpose(res.val()); diff --git a/stan/math/opencl/rev/mdivide_right_tri_low.hpp b/stan/math/opencl/rev/mdivide_right_tri_low.hpp index 9450ece53a3..a9717623565 100644 --- a/stan/math/opencl/rev/mdivide_right_tri_low.hpp +++ b/stan/math/opencl/rev/mdivide_right_tri_low.hpp @@ -39,7 +39,7 @@ inline var_value> mdivide_right_tri_low(T2&& b, T1&& A) { = tri_inverse(value_of(A_arena)); return make_callback_var( value_of(b_arena) * A_tri_inv, - [A_arena, b_arena, A_tri_inv](const vari_value>& res) { + [=](const vari_value>& res) { matrix_cl adjB = res.adj() * transpose(A_tri_inv); if constexpr (is_autodiff_v) { matrix_cl adjA = transpose(res.val()) * adjB; diff --git a/stan/math/opencl/zeros_strict_tri.hpp b/stan/math/opencl/zeros_strict_tri.hpp index 339f987cb59..8e4b9989328 100644 --- a/stan/math/opencl/zeros_strict_tri.hpp +++ b/stan/math/opencl/zeros_strict_tri.hpp @@ -44,7 +44,6 @@ inline void matrix_cl::zeros_strict_tri() try { return; } this->view_ = both(this->view_, invert(matrix_view)); - cl::CommandQueue& cmdQueue = opencl_context.queue(); opencl_kernels::fill_strict_tri(cl::NDRange(this->rows(), this->cols()), *this, 0.0, this->rows(), this->cols(), matrix_view); diff --git a/stan/math/prim/constraint/unit_vector_free.hpp b/stan/math/prim/constraint/unit_vector_free.hpp index c69a7ac9421..01624e01a4e 100644 --- a/stan/math/prim/constraint/unit_vector_free.hpp +++ b/stan/math/prim/constraint/unit_vector_free.hpp @@ -25,7 +25,7 @@ inline auto unit_vector_free(EigVec&& x) { auto&& x_ref = to_ref(std::forward(x)); check_unit_vector("stan::math::unit_vector_free", "Unit vector variable", x_ref); - return x_ref; + return std::forward(x_ref); } /** diff --git a/stan/math/prim/core/init_threadpool_tbb.hpp b/stan/math/prim/core/init_threadpool_tbb.hpp index ca96dc3a8b2..99e4f361a56 100644 --- a/stan/math/prim/core/init_threadpool_tbb.hpp +++ b/stan/math/prim/core/init_threadpool_tbb.hpp @@ -68,6 +68,25 @@ inline int get_num_threads() { #endif } +#ifdef TBB_INTERFACE_NEW +/** Own the concurrency control before the arena, so the arena dies first. */ +struct threadpool_tbb_state { + tbb::global_control control; + tbb::task_arena arena; + + explicit threadpool_tbb_state(int num_threads) + : control(tbb::global_control::max_allowed_parallelism, num_threads), + arena(num_threads, 1) { + arena.initialize(); + } +}; + +/** Construct independently owned thread-pool state for an explicit count. */ +inline threadpool_tbb_state make_threadpool_tbb(int num_threads) { + return threadpool_tbb_state(num_threads); +} +#endif + } // namespace internal #ifdef TBB_INTERFACE_NEW @@ -83,13 +102,12 @@ inline int get_num_threads() { * of get_num_threads. The TBB scheduler will be activated by calling * this function. * - * The function returns a reference to the static - * tbb::global_control instance. + * The first call fixes the concurrency configuration for the process. * * @param n_threads The maximum number of threads available to the tbb. If not * set will search for the environment variable `STAN_NUM_THREADS`. A value of * -1 will assume all detectable threads are available for the process. - * @return reference to the static tbb::global_control + * @return reference to the static tbb::task_arena * @throws std::runtime_error if n_threads (defaults to zero) is not provided * and the value of STAN_NUM_THREADS environment variable is invalid. */ @@ -108,12 +126,10 @@ inline tbb::task_arena& init_threadpool_tbb(int n_threads = 0) { "' but it must be positive or -1"); } #endif - static tbb::global_control tbb_gc( - tbb::global_control::max_allowed_parallelism, tbb_max_threads); - static tbb::task_arena tbb_arena(tbb_max_threads, 1); - tbb_arena.initialize(); + static auto state = internal::make_threadpool_tbb(tbb_max_threads); + state.arena.initialize(); - return tbb_arena; + return state.arena; } #else /** diff --git a/stan/math/prim/fun/log_softmax.hpp b/stan/math/prim/fun/log_softmax.hpp index d6c9017edb6..8e34955c048 100644 --- a/stan/math/prim/fun/log_softmax.hpp +++ b/stan/math/prim/fun/log_softmax.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace stan { namespace math { @@ -53,7 +54,7 @@ inline auto log_softmax(Container&& x) { std::forward(a), [](auto&& v) -> plain_type_t { if (v.size() == 0) { - return v; + return std::forward(v); } return (v.array() - log_sum_exp(v)).matrix(); }); diff --git a/stan/math/prim/fun/softmax.hpp b/stan/math/prim/fun/softmax.hpp index 63a51876a60..a737a2e2d53 100644 --- a/stan/math/prim/fun/softmax.hpp +++ b/stan/math/prim/fun/softmax.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace stan { namespace math { @@ -56,7 +57,7 @@ inline auto softmax(Container&& x) { std::forward(a), [](auto&& v) -> plain_type_t { if (v.size() == 0) { - return v; + return std::forward(v); } const auto theta = (v.array() - v.maxCoeff()).exp(); return (theta / theta.sum()).matrix(); diff --git a/stan/math/prim/functor/finite_diff_gradient.hpp b/stan/math/prim/functor/finite_diff_gradient.hpp index 72237fd6c0e..7ec59cdb36c 100644 --- a/stan/math/prim/functor/finite_diff_gradient.hpp +++ b/stan/math/prim/functor/finite_diff_gradient.hpp @@ -43,6 +43,8 @@ template inline void finite_diff_gradient(const F& f, const Eigen::VectorXd& x, double& fx, Eigen::VectorXd& grad_fx, double epsilon = 1e-03) { + static constexpr int h_scale[3] = {3, 2, 1}; + static constexpr int mults[3] = {1, -9, 45}; Eigen::VectorXd x_temp(x); int d = x.size(); grad_fx.resize(d); @@ -52,23 +54,13 @@ inline void finite_diff_gradient(const F& f, const Eigen::VectorXd& x, for (int i = 0; i < d; ++i) { double delta_f = 0.0; - x_temp(i) = x(i) + 3.0 * epsilon; - delta_f = f(x_temp); - - x_temp(i) = x(i) + 2.0 * epsilon; - delta_f -= 9.0 * f(x_temp); - - x_temp(i) = x(i) + epsilon; - delta_f += 45.0 * f(x_temp); - - x_temp(i) = x(i) + -3.0 * epsilon; - delta_f -= f(x_temp); - - x_temp(i) = x(i) + -2.0 * epsilon; - delta_f += 9.0 * f(x_temp); - - x_temp(i) = x(i) + -epsilon; - delta_f -= 45.0 * f(x_temp); + for (int j = 0; j < 3; ++j) { + x_temp(i) = x(i) + h_scale[j] * epsilon; + const double f_plus = f(x_temp); + x_temp(i) = x(i) - h_scale[j] * epsilon; + // Cancel constant terms before weighting the symmetric differences. + delta_f += (f_plus - f(x_temp)) * mults[j]; + } delta_f /= 60 * epsilon; diff --git a/stan/math/prim/functor/finite_diff_gradient_auto.hpp b/stan/math/prim/functor/finite_diff_gradient_auto.hpp index 8c9951ae37b..002f094ed1a 100644 --- a/stan/math/prim/functor/finite_diff_gradient_auto.hpp +++ b/stan/math/prim/functor/finite_diff_gradient_auto.hpp @@ -51,8 +51,8 @@ template ; - static constexpr int h_scale[6] = {3, 2, 1, -3, -2, -1}; - static constexpr int mults[6] = {1, -9, 45, -1, 9, -45}; + static constexpr int h_scale[3] = {3, 2, 1}; + static constexpr int mults[3] = {1, -9, 45}; fx = f(x); grad_fx.resize(x.size()); @@ -61,12 +61,17 @@ inline void finite_diff_gradient_auto(const F& f, VectorT&& x, ScalarT& fx, grad_map = EigT::NullaryExpr(x.size(), [&f, &x](Eigen::Index i) { double h = finite_diff_stepsize(value_of_rec(x[i])); ScalarT delta_f = 0; - for (int j = 0; j < 6; ++j) { - auto x_temp + for (int j = 0; j < 3; ++j) { + auto x_plus = EigT::NullaryExpr(x.size(), [&x, &i, &h, &j](Eigen::Index k) { return k == i ? x[i] + h * h_scale[j] : x[k]; }); - delta_f += f(std::move(x_temp)) * mults[j]; + auto x_minus + = EigT::NullaryExpr(x.size(), [&x, &i, &h, &j](Eigen::Index k) { + return k == i ? x[i] - h * h_scale[j] : x[k]; + }); + // Cancel constant terms before weighting the symmetric differences. + delta_f += (f(std::move(x_plus)) - f(std::move(x_minus))) * mults[j]; } return delta_f / (60 * h); }); diff --git a/stan/math/prim/prob/normal_sufficient_lpdf.hpp b/stan/math/prim/prob/normal_sufficient_lpdf.hpp index 996b5e2d052..83b8a061ffc 100644 --- a/stan/math/prim/prob/normal_sufficient_lpdf.hpp +++ b/stan/math/prim/prob/normal_sufficient_lpdf.hpp @@ -75,8 +75,6 @@ inline return_type_t normal_sufficient_lpdf( decltype(auto) y_val = to_ref(as_value_column_array_or_scalar(y_ref)); decltype(auto) s_squared_val = to_ref(as_value_column_array_or_scalar(s_squared_ref)); - decltype(auto) n_obs_val_int - = to_ref(as_value_column_array_or_scalar(n_obs_ref)); decltype(auto) n_obs_val = to_ref( promote_scalar(as_value_column_array_or_scalar(n_obs_ref))); decltype(auto) mu_val = to_ref(as_value_column_array_or_scalar(mu_ref)); diff --git a/stan/math/prim/prob/student_t_qf.hpp b/stan/math/prim/prob/student_t_qf.hpp index 5a4b9dbf920..c2c096de9a1 100644 --- a/stan/math/prim/prob/student_t_qf.hpp +++ b/stan/math/prim/prob/student_t_qf.hpp @@ -72,7 +72,6 @@ template ; - static constexpr const char* function = "student_t_qf"; const size_t max_size_all = max_size(p, nu, mu, sigma); Eigen::Matrix result(max_size_all); diff --git a/stan/math/rev/constraint/lub_constrain.hpp b/stan/math/rev/constraint/lub_constrain.hpp index df5d9f9dd46..8e6099b1165 100644 --- a/stan/math/rev/constraint/lub_constrain.hpp +++ b/stan/math/rev/constraint/lub_constrain.hpp @@ -49,19 +49,18 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub) { check_less("lub_constrain", "lb", lb_val, ub_val); auto diff = ub_val - lb_val; double inv_logit_x = inv_logit(value_of(x)); - return make_callback_var(diff * inv_logit_x + lb_val, - [x, ub, lb, diff, inv_logit_x](auto& vi) mutable { - if constexpr (is_autodiff_v) { - x.adj() += vi.adj() * diff * inv_logit_x - * (1.0 - inv_logit_x); - } - if constexpr (is_autodiff_v) { - lb.adj() += vi.adj() * (1.0 - inv_logit_x); - } - if constexpr (is_autodiff_v) { - ub.adj() += vi.adj() * inv_logit_x; - } - }); + return make_callback_var( + diff * inv_logit_x + lb_val, [=](auto& vi) mutable { + if constexpr (is_autodiff_v) { + x.adj() += vi.adj() * diff * inv_logit_x * (1.0 - inv_logit_x); + } + if constexpr (is_autodiff_v) { + lb.adj() += vi.adj() * (1.0 - inv_logit_x); + } + if constexpr (is_autodiff_v) { + ub.adj() += vi.adj() * inv_logit_x; + } + }); } } @@ -121,8 +120,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub, double inv_logit_x = inv_logit(value_of(x)); lp += (log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x)))); return make_callback_var( - diff * inv_logit_x + lb_val, - [x, ub, lb, diff, lp, inv_logit_x](auto& vi) mutable { + diff * inv_logit_x + lb_val, [=](auto& vi) mutable { if constexpr (is_autodiff_v) { x.adj() += vi.adj() * diff * inv_logit_x * (1.0 - inv_logit_x) + lp.adj() * (1.0 - 2.0 * inv_logit_x); @@ -167,7 +165,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub) { const auto diff = ub_val - lb_val; auto inv_logit_x = to_arena(inv_logit(arena_x.val().array())); arena_t ret = diff * inv_logit_x + lb_val; - reverse_pass_callback([arena_x, ub, lb, ret, diff, inv_logit_x]() mutable { + reverse_pass_callback([=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x); @@ -211,28 +209,27 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub, lp += (log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x)))).sum(); auto inv_logit_x = to_arena(inv_logit(value_of(arena_x).array())); arena_t ret = diff * inv_logit_x + lb_val; - reverse_pass_callback( - [arena_x, ub, lb, ret, lp, diff, inv_logit_x]() mutable { - if constexpr (is_autodiff_v) { - arena_x.adj().array() - += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x) - + lp.adj() * (1.0 - 2.0 * inv_logit_x); - } - if constexpr (is_autodiff_v && is_autodiff_v) { - const auto lp_calc = lp.adj() * ret.size(); - const auto one_over_diff = 1.0 / diff; - lb.adj() += (ret.adj().array() * (1.0 - inv_logit_x)).sum() - + -one_over_diff * lp_calc; - ub.adj() += (ret.adj().array() * inv_logit_x).sum() - + one_over_diff * lp_calc; - } else if constexpr (is_autodiff_v) { - lb.adj() += (ret.adj().array() * (1.0 - inv_logit_x)).sum() - + -(1.0 / diff) * lp.adj() * ret.size(); - } else if constexpr (is_autodiff_v) { - ub.adj() += (ret.adj().array() * inv_logit_x).sum() - + (1.0 / diff) * lp.adj() * ret.size(); - } - }); + reverse_pass_callback([=]() mutable { + if constexpr (is_autodiff_v) { + arena_x.adj().array() + += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x) + + lp.adj() * (1.0 - 2.0 * inv_logit_x); + } + if constexpr (is_autodiff_v && is_autodiff_v) { + const auto lp_calc = lp.adj() * ret.size(); + const auto one_over_diff = 1.0 / diff; + lb.adj() += (ret.adj().array() * (1.0 - inv_logit_x)).sum() + + -one_over_diff * lp_calc; + ub.adj() += (ret.adj().array() * inv_logit_x).sum() + + one_over_diff * lp_calc; + } else if constexpr (is_autodiff_v) { + lb.adj() += (ret.adj().array() * (1.0 - inv_logit_x)).sum() + + -(1.0 / diff) * lp.adj() * ret.size(); + } else if constexpr (is_autodiff_v) { + ub.adj() += (ret.adj().array() * inv_logit_x).sum() + + (1.0 / diff) * lp.adj() * ret.size(); + } + }); return ret_type(ret); } } @@ -262,8 +259,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub) { auto inv_logit_x = to_arena(inv_logit(value_of(arena_x).array())); arena_t ret = (is_lb_inf).select( ub_val - value_of(arena_x).array().exp(), diff * inv_logit_x + lb_val); - reverse_pass_callback([arena_x, ub, arena_lb, ret, diff, inv_logit_x, - is_lb_inf]() mutable { + reverse_pass_callback([=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += (is_lb_inf).select( ret.adj().array() * -value_of(arena_x).array().exp(), @@ -316,8 +312,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub, .select(arena_x_val, log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x)))) .sum(); - reverse_pass_callback([arena_x, arena_x_val, ub, arena_lb, ret, lp, diff, - inv_logit_x, is_lb_inf]() mutable { + reverse_pass_callback([=]() mutable { const auto lp_adj = lp.adj(); if constexpr (is_autodiff_v) { const auto x_sign = arena_x_val.sign().eval(); @@ -369,8 +364,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub) { auto inv_logit_x = to_arena(inv_logit(arena_x_val.array())); arena_t ret = (is_ub_inf).select( arena_x_val.array().exp() + lb_val, diff * inv_logit_x + lb_val); - reverse_pass_callback([arena_x, arena_x_val, arena_ub, lb, ret, is_ub_inf, - inv_logit_x, diff]() mutable { + reverse_pass_callback([=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += (is_ub_inf).select( ret.adj().array() * arena_x_val.array().exp(), @@ -423,8 +417,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub, auto inv_logit_x = to_arena(inv_logit(arena_x_val.array())); arena_t ret = (is_ub_inf).select( arena_x_val.array().exp() + lb_val, diff * inv_logit_x + lb_val); - reverse_pass_callback([arena_x, arena_x_val, diff, inv_logit_x, arena_ub, - lb, ret, lp, is_ub_inf]() mutable { + reverse_pass_callback([=]() mutable { const auto lp_adj = lp.adj(); if constexpr (is_autodiff_v) { arena_x.adj().array() += (is_ub_inf).select( @@ -477,9 +470,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub) { ub_val - arena_x.val().array().exp(), (is_ub_inf).select(arena_x_val.array().exp() + lb_val, diff * inv_logit_x + lb_val))); - reverse_pass_callback([arena_x, arena_x_val, inv_logit_x, arena_ub, arena_lb, - diff, ret, is_ub_inf, is_lb_inf, - is_lb_ub_inf]() mutable { + reverse_pass_callback([=]() mutable { // The most likely case is neither of them are infinity const bool is_none_inf = !(is_lb_inf.any() || is_ub_inf.any()); if (is_none_inf) { @@ -560,9 +551,7 @@ inline auto lub_constrain(const T& x, const L& lb, const U& ub, arena_x_val.array(), log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x))))) .sum(); - reverse_pass_callback([arena_x, arena_x_val, inv_logit_x, arena_ub, arena_lb, - diff, ret, is_ub_inf, is_lb_inf, is_lb_ub_inf, - lp]() mutable { + reverse_pass_callback([=]() mutable { const auto lp_adj = lp.adj(); // The most likely case is neither of them are infinity const bool is_none_inf = !(is_lb_inf.any() || is_ub_inf.any()); diff --git a/stan/math/rev/fun/fma.hpp b/stan/math/rev/fun/fma.hpp index 517eb3481d6..e1865ead30e 100644 --- a/stan/math/rev/fun/fma.hpp +++ b/stan/math/rev/fun/fma.hpp @@ -194,7 +194,7 @@ namespace internal { template * = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += ret.adj().array() * value_of(arena_y).array(); } @@ -214,7 +214,7 @@ template * = nullptr, require_stan_scalar_t* = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj() += (ret.adj().array() * value_of(arena_y).array()).sum(); } @@ -234,7 +234,7 @@ template * = nullptr, require_stan_scalar_t* = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += ret.adj().array() * value_of(arena_y); } @@ -254,7 +254,7 @@ template * = nullptr, require_all_stan_scalar_t* = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj() += (ret.adj().array() * value_of(arena_y)).sum(); } @@ -274,7 +274,7 @@ template * = nullptr, require_stan_scalar_t* = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += ret.adj().array() * value_of(arena_y).array(); } @@ -294,7 +294,7 @@ template * = nullptr, require_all_stan_scalar_t* = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj() += (ret.adj().array() * value_of(arena_y).array()).sum(); } @@ -314,7 +314,7 @@ template * = nullptr, require_all_stan_scalar_t* = nullptr> inline auto fma_reverse_pass(T1& arena_x, T2& arena_y, T3& arena_z, T4& ret) { - return [arena_x, arena_y, arena_z, ret]() mutable { + return [=]() mutable { if constexpr (is_autodiff_v) { arena_x.adj().array() += ret.adj().array() * value_of(arena_y); } diff --git a/stan/math/rev/fun/inv_inc_beta.hpp b/stan/math/rev/fun/inv_inc_beta.hpp index 64f6b75e75f..01567fa7ed4 100644 --- a/stan/math/rev/fun/inv_inc_beta.hpp +++ b/stan/math/rev/fun/inv_inc_beta.hpp @@ -62,7 +62,7 @@ inline var inv_inc_beta(const T1& a, const T2& b, const T3& p) { double b_val = value_of(b); double p_val = value_of(p); double w = inv_inc_beta(a_val, b_val, p_val); - return make_callback_var(w, [a, b, p, a_val, b_val, w](auto& vi) { + return make_callback_var(w, [=](auto& vi) { double log_w = log(w); double log1m_w = log1m(w); double one_m_a = 1 - a_val; diff --git a/stan/math/rev/functor/cvodes_integrator_adjoint.hpp b/stan/math/rev/functor/cvodes_integrator_adjoint.hpp index d8e030355ee..e3a0749ff08 100644 --- a/stan/math/rev/functor/cvodes_integrator_adjoint.hpp +++ b/stan/math/rev/functor/cvodes_integrator_adjoint.hpp @@ -641,6 +641,9 @@ class cvodes_integrator_adjoint_vari : public vari_base { f_y_t_vars.size(), "states", N_); f_y_t_vars.adj() = -Eigen::Map(NV_DATA_S(yB), N_); grad(); + // CVODES reuses this output buffer; adjoints must not accumulate into its + // previous contents. + N_VConst(0.0, qBdot); math::apply( [&qBdot](auto&&... args) { accumulate_adjoints(NV_DATA_S(qBdot), args...); diff --git a/stan/math/rev/prob/student_t_qf.hpp b/stan/math/rev/prob/student_t_qf.hpp index e44d4cbbc9c..d9ff3e0314b 100644 --- a/stan/math/rev/prob/student_t_qf.hpp +++ b/stan/math/rev/prob/student_t_qf.hpp @@ -39,7 +39,6 @@ inline var student_t_qf(const T_p& p, const T_nu& nu, const T_mu& mu, const double p_val_flip = p_val < 0.5 ? p_val : 1.0 - p_val; const double p_sign = p_val < 0.5 ? -1.0 : 1.0; const double ibeta_arg = inv_inc_beta(0.5 * nu_val, 0.5, 2 * p_val_flip); - const double sqrt_inv_ibeta_m1 = sqrt(inv(ibeta_arg) - 1.0); double ret_val = mu_val + p_sign * sigma_val * sqrt(nu_val) * sqrt(-1.0 + 1.0 / ibeta_arg); @@ -49,7 +48,6 @@ inline var student_t_qf(const T_p& p, const T_nu& nu, const T_mu& mu, const double mu_val = value_of(mu); const double sigma_val = value_of(sigma); const double nu_val = value_of(nu); - const double p_val_flip = p_val < 0.5 ? p_val : 1.0 - p_val; const double p_sign = p_val < 0.5 ? -1.0 : 1.0; const double sqrt_inv_ibeta_m1 = sqrt(inv(ibeta_arg) - 1.0); if constexpr (is_autodiff_v) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000000..a539af84512 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,213 @@ +# Stan's vendored gtest_main initializes the MPI cluster and includes Stan +# headers even when MPI is disabled. Use the test flags instead of +# GoogleTest's internal warning policy for its own sources. +target_link_libraries(gtest_main PRIVATE stan_test_flags) +set_property(TARGET gtest_main PROPERTY COMPILE_FLAGS "") +add_library(stan_test_options INTERFACE) +target_link_libraries(stan_test_options INTERFACE stan_test_flags gtest_main) + +# Set out to TRUE when path contains every STAN_TEST_NAME_FILTERS substring. +function(stan_matches_name_filters path out) + foreach(filter IN LISTS STAN_TEST_NAME_FILTERS) + string(FIND "${path}" "${filter}" match_index) + if(match_index EQUAL -1) + set(${out} FALSE PARENT_SCOPE) + return() + endif() + endforeach() + set(${out} TRUE PARENT_SCOPE) +endfunction() + +# Every test executable is created here so each gets the same dependencies. +function(stan_add_test_binary target) + # Xcode needs a real source alongside object-library references. + add_executable(${target} "${PROJECT_SOURCE_DIR}/cmake/empty.cpp" ${ARGN}) + if(STAN_MPI) + # A static archive can discard the unreferenced registration initializer. + target_sources(${target} PRIVATE $) + endif() + stan_configure_test(${target} stan_test_options "") + if(WIN32) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" "$" + VERBATIM) + endif() +endfunction() + +# Add the CTest test `name` for one source, labeled with ARGN and the +# source's repository path. Its objects (`name`_objects) are shared with the +# executables of stan_add_test_group. +function(stan_add_gtest name source repo_relative pch) + add_library(${name}_objects OBJECT EXCLUDE_FROM_ALL "${source}") + stan_configure_test(${name}_objects stan_test_options "${pch}") + get_source_file_property(generator_target "${source}" STAN_GENERATOR_TARGET) + if(generator_target) + # Every consuming executable needs generation before object compilation. + add_dependencies(${name}_objects ${generator_target}) + endif() + stan_add_test_binary(${name} "$") + if(STAN_MPI) + set(mpi_ranks 1) + if(repo_relative MATCHES "mpi_") + set(mpi_ranks 2) + endif() + add_test(NAME ${name} COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${mpi_ranks} + ${MPIEXEC_PREFLAGS} $ ${MPIEXEC_POSTFLAGS}) + else() + add_test(NAME ${name} COMMAND ${name}) + endif() + set_tests_properties(${name} PROPERTIES + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + LABELS "${ARGN};${repo_relative}") + if(STAN_SELECTED_TEST_REGEX AND repo_relative MATCHES "${STAN_SELECTED_TEST_REGEX}") + set_property(GLOBAL APPEND PROPERTY STAN_SELECTED_TESTS ${name}) + endif() +endfunction() + +# Build the tests in ARGN with `prefix`_subtests, or as one executable with +# `prefix`_all_tests. The stan_build: label lets runTests.py find the group. +function(stan_add_test_group prefix) + add_custom_target(${prefix}_subtests DEPENDS ${ARGN}) + set_property(TEST ${ARGN} APPEND PROPERTY LABELS "stan_build:${prefix}_subtests") + set(objects "") + foreach(test IN LISTS ARGN) + list(APPEND objects "$") + endforeach() + stan_add_test_binary(${prefix}_all_tests ${objects}) + set_target_properties(${prefix}_all_tests PROPERTIES EXCLUDE_FROM_ALL TRUE) +endfunction() + +# Add a test for each source in SOURCES, or for each *test.cpp below +# DIRECTORY, named after its path below REPO_PATH. Add a group for every +# directory level, starting with PREFIX. Return the selected tests in +# `PREFIX`_selected_tests. Sources use PCH, or `LAYER_PCH_PREFIX`__pch +# when it exists for their first directory. With AD_PCH, read each source to +# choose AD_PCH or no PCH. +function(add_gtest_grouped_test) + cmake_parse_arguments(PARSE_ARGV 0 arg "" + "PREFIX;REPO_PATH;DIRECTORY;PCH;LAYER_PCH_PREFIX;AD_PCH" "SOURCES") + set(sources ${arg_SOURCES}) + if(NOT sources) + file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${arg_DIRECTORY}/*test.cpp") + endif() + set(directory_keys "") + set(scanned_sources "") + foreach(src IN LISTS sources) + file(RELATIVE_PATH relative_source "${arg_DIRECTORY}" "${src}") + set(repo_relative "${arg_REPO_PATH}/${relative_source}") + if(NOT STAN_OPENCL AND repo_relative MATCHES "/opencl/") + continue() + endif() + if(NOT STAN_MPI AND repo_relative MATCHES "/[^/]*mpi[^/]*test\\.cpp$") + continue() + endif() + string(REPLACE "/" "_" name "${repo_relative}") + string(REGEX REPLACE "\\.cpp$" "" name "${name}") + set(pch "${arg_PCH}") + if(arg_LAYER_PCH_PREFIX) + string(REGEX MATCH "^[^/]+" layer "${relative_source}") + if(TARGET ${arg_LAYER_PCH_PREFIX}_${layer}_pch) + set(pch ${arg_LAYER_PCH_PREFIX}_${layer}_pch) + endif() + endif() + if(arg_AD_PCH) + # Precompile the AD helpers only for sources that already include them; + # their global aliases must not leak into unrelated tests. A source + # that sets STAN_/EIGEN_ macros before including Stan/Eigen gets no PCH. + file(STRINGS "${src}" directives + REGEX "^#(include |[ \t]*(define|undef)[ \t]+(STAN_|EIGEN_))") + list(APPEND scanned_sources "${src}") + if(directives MATCHES "(^|;)#[ \t]*(define|undef)") + set(pch "") + elseif(directives AND TARGET ${arg_AD_PCH}) + set(pch ${arg_AD_PCH}) + endif() + endif() + get_filename_component(relative_dir "${relative_source}" DIRECTORY) + string(REPLACE "/" ";" labels "${relative_dir}") + stan_add_gtest(${name} "${src}" "${repo_relative}" "${pch}" ${arg_PREFIX} ${labels}) + + stan_matches_name_filters("${repo_relative}" selected) + if(NOT selected) + continue() + endif() + set(key "${relative_dir}") + while(TRUE) + if(key STREQUAL "") + set(key ROOT) + endif() + string(REPLACE "/" "_" key "${key}") + if(NOT DEFINED subtests_${key}) + list(APPEND directory_keys "${key}") + endif() + list(APPEND subtests_${key} "${name}") + if(key STREQUAL "ROOT") + break() + endif() + # The next ancestor comes from the path, not the underscore target name. + get_filename_component(relative_dir "${relative_dir}" DIRECTORY) + set(key "${relative_dir}") + endwhile() + endforeach() + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${scanned_sources}) + foreach(key IN LISTS directory_keys) + if(key STREQUAL "ROOT") + stan_add_test_group(${arg_PREFIX} ${subtests_ROOT}) + else() + stan_add_test_group(${arg_PREFIX}_${key} ${subtests_${key}}) + endif() + endforeach() + set(${arg_PREFIX}_selected_tests "${subtests_ROOT}" PARENT_SCOPE) +endfunction() + +add_subdirectory(unit) +if(STAN_BUILD_PROB_TESTS) + add_subdirectory(prob) +endif() +if(STAN_BUILD_EXPRESSION_TESTS) + set(STAN_EXPRESSION_TEST_DIR "${PROJECT_BINARY_DIR}/expression-sources" + CACHE PATH "Directory containing generated expression tests") + file(GLOB expression_sources CONFIGURE_DEPENDS "${STAN_EXPRESSION_TEST_DIR}/*test.cpp") + if(NOT expression_sources) + message(FATAL_ERROR "Generate expression tests before configuring STAN_BUILD_EXPRESSION_TESTS") + endif() + # The expression helper has no include guard; precompile its heavy guarded + # dependencies so each generated source can still include the helper once. + stan_add_pch(expression_pch stan_test_options ) + add_gtest_grouped_test(PREFIX expressions REPO_PATH test/expressions + DIRECTORY "${STAN_EXPRESSION_TEST_DIR}" PCH expression_pch SOURCES ${expression_sources}) +endif() + +# Makefile generators build separate top-level targets one at a time; one +# aggregate builds a selection of individual tests with every job. +get_property(selected_tests GLOBAL PROPERTY STAN_SELECTED_TESTS) +if(selected_tests) + add_custom_target(stan_selected_tests DEPENDS ${selected_tests}) +endif() + +if(STAN_TEST_HEADERS) + # A PCH would conceal missing includes. Force each header into an otherwise + # empty translation unit, and do not inherit the tests' precompiled headers. + file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/stan/*.hpp") + add_custom_target(test-headers) + foreach(header IN LISTS headers) + file(RELATIVE_PATH relative_header "${PROJECT_SOURCE_DIR}" "${header}") + if(NOT STAN_OPENCL AND relative_header MATCHES "/opencl/") + continue() + endif() + if(NOT STAN_MPI AND relative_header MATCHES "/mpi_[^/]+\\.hpp$") + continue() + endif() + string(REPLACE "/" "_" name "${relative_header}") + string(REGEX REPLACE "\\.hpp$" "-test" name "${name}") + add_library(${name} OBJECT EXCLUDE_FROM_ALL "${PROJECT_SOURCE_DIR}/cmake/empty.cpp") + target_link_libraries(${name} PRIVATE stanmath) + if(MSVC) + target_compile_options(${name} PRIVATE "/FI${header}") + else() + target_compile_options(${name} PRIVATE -O0 -include "${header}" -Wunused-local-typedefs) + endif() + add_dependencies(test-headers ${name}) + endforeach() +endif() diff --git a/test/cmake.py b/test/cmake.py new file mode 100644 index 00000000000..e8469d437e5 --- /dev/null +++ b/test/cmake.py @@ -0,0 +1,217 @@ +"""Check CMake command planning. Run with python3 test/cmake.py.""" + +import io +import json +from pathlib import Path +import re +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "test" / "prob")) +import getDependencies as get_dependencies +import runTests as runner + + +def inputs(*flags): + return runner.processCLIArgs(["test/unit/math/prim/fun", *flags]) + + +class CMakeTest(unittest.TestCase): + def test_directory_build_with_fresh_filters_and_jobs(self): + configure, build = runner.build_commands(inputs("--fresh", "-j20", "-f", "sum"), + ["test/unit/math/prim/fun"], Path("source"), Path("build")) + self.assertNotIn("--fresh", configure) + self.assertIn("-DCMAKE_BUILD_TYPE=ReleaseTesting", configure) + self.assertEqual(build[build.index("--config") + 1], "ReleaseTesting") + self.assertIn("-DSTAN_TEST_NAME_FILTERS=sum", configure) + self.assertEqual(build[build.index("--parallel") + 1], "20") + self.assertEqual(build[build.index("--target") + 1:], ["unit_math_prim_fun_all_tests"]) + execute = runner.test_command("test/unit/math/prim/fun", Path("build"), "Release", {}, False) + self.assertEqual(Path(execute[0]).name, "unit_math_prim_fun_all_tests") + + def test_fresh_deletes_the_cache_and_keeps_objects(self): + commands = [] + + def run(command, **kwargs): + commands.append(command) + return subprocess.CompletedProcess(command, 0, stdout="") + + with tempfile.TemporaryDirectory() as working_dir: + build = Path(working_dir) / "build" + objects = build / "test/unit/CMakeFiles/sum.dir" + for directory in (build / "CMakeFiles/3.16.3", objects): + directory.mkdir(parents=True) + (build / "CMakeCache.txt").write_text("STAN_THREADS:BOOL=ON\n") + (objects / "sum_test.cpp.o").write_text("") + with patch.object(runner.subprocess, "run", run), patch.object(sys, "stdout", io.StringIO()): + code = runner.main(["--configure-only", "--fresh"], ROOT, Path(working_dir), False, io.StringIO()) + self.assertEqual(code, 0) + self.assertFalse((build / "CMakeCache.txt").exists()) + self.assertFalse((build / "CMakeFiles").exists()) + self.assertTrue((objects / "sum_test.cpp.o").exists()) + self.assertEqual(len(commands), 1) + self.assertNotIn("--fresh", commands[0]) + + def test_build_only_and_test_only(self): + build = runner.build_commands(inputs("--make-only"), ["test/unit/math/prim/fun"], + Path("source"), Path("build")) + self.assertEqual(len(build), 2) + self.assertNotIn("--fresh", build[0]) + self.assertEqual(runner.build_commands(inputs("--test-only"), ["test/unit/math/prim/fun"], + Path("source"), Path("build")), []) + + def test_cmake_cache_controls_configuration_and_mpi_launch(self): + cache = runner.parse_cache("// comment\nCMAKE_CONFIGURATION_TYPES:STRING=Debug;Release\n" + "STAN_EFFECTIVE_MPI:INTERNAL=ON\nMPIEXEC_PREFLAGS:STRING=--oversubscribe\n") + command = runner.test_command("test/unit/math/prim/fun", Path("build"), "Debug", cache, True) + self.assertEqual(command[:4], ["mpirun", "-np", "2", "--oversubscribe"]) + self.assertEqual(Path(command[4]), Path("build/test/unit/Debug/unit_math_prim_fun_all_tests.exe")) + + def test_cli_reads_explicit_arguments(self): + self.assertEqual(inputs().j, 1) + self.assertEqual(inputs("-j20").j, 20) + parsed = runner.processCLIArgs(["build-tests", "build", "--source-regex", "^test/unit/", "--jobs", "4"]) + self.assertEqual((parsed.command, parsed.jobs), ("build-tests", 4)) + self.assertEqual(inputs("--test-jobs", "6").test_jobs, 6) + for flags in (("-j0",), ("--test-jobs", "0"), ("--fresh", "--test-only"), + ("--make-only", "--test-only"), ("--configure-only", "--test-only")): + with self.subTest(flags=flags), patch.object(sys, "stderr"): + with self.assertRaises(SystemExit) as error: + inputs(*flags) + self.assertEqual(error.exception.code, 2) + + def test_parallel_test_runs_print_in_order_and_report_failures(self): + codes = {"unit_math_prim_fun_all_tests": 1, "unit_math_rev_fun_all_tests": 0} + + def run(command, **kwargs): + name = Path(command[0]).name + return subprocess.CompletedProcess(command, codes[name], stdout=name + " output\n") + + output = io.StringIO() + with tempfile.TemporaryDirectory() as working_dir, \ + patch.object(runner.subprocess, "run", run), patch.object(sys, "stdout", output): + code = runner.main(["--test-only", "--test-jobs", "2", "test/unit/math/prim/fun", + "test/unit/math/rev/fun"], ROOT, Path(working_dir), False, io.StringIO()) + self.assertEqual(code, 1) + printed = output.getvalue() + self.assertLess(printed.index("unit_math_prim_fun_all_tests output"), + printed.index("unit_math_rev_fun_all_tests output")) + + def test_trailing_slash_selects_distribution_directories(self): + commands = [] + + def run(command, **kwargs): + commands.append(command) + return subprocess.CompletedProcess(command, 0, stdout="") + + with tempfile.TemporaryDirectory() as working_dir, \ + patch.object(runner.subprocess, "run", run), patch.object(sys, "stdout", io.StringIO()): + code = runner.main(["--test-only", "--test-jobs", "2", "test/prob/*/"], + ROOT, Path(working_dir), False, io.StringIO()) + self.assertEqual(code, 0) + names = {Path(command[0]).name for command in commands} + self.assertIn("prob_math_bernoulli_all_tests", names) + self.assertEqual(len(names), len([path for path in (ROOT / "test/prob").iterdir() if path.is_dir()])) + + def test_selection_removes_overlap_and_rejects_invalid_paths(self): + directory = "test/unit/math/prim/fun" + matches = {directory: [directory], "sum": [directory + "/sum_test.cpp"]} + self.assertEqual(runner.select_paths(matches, {directory}), [directory]) + self.assertEqual(runner.target_for(directory + "/sum_test.cpp")[0], + "test_unit_math_prim_fun_sum_test") + with self.assertRaises(ValueError): + runner.select_paths({"stan/math": ["stan/math"]}, {"stan/math"}) + with self.assertRaises(ValueError): + runner.select_paths({"missing": []}, set()) + + def test_changed_headers_select_existing_dependent_tests(self): + existing = {"test/unit/math/" + layer + "/fun/sum_test.cpp" for layer in ("prim", "rev", "fwd", "mix")} + selected = runner.findChangedTests(["stan/math/prim/fun/sum.hpp"], existing) + self.assertEqual(set(selected), existing) + self.assertEqual(runner.findChangedTests(["stan/math/prim/fun/sum.hpp"], existing, limit=1), []) + self.assertRegex(selected[0], runner.source_filter(selected)) + + def test_ci_build_reuses_registered_cmake_labels(self): + inventory = {"tests": [{"name": "sum", "properties": [{"name": "LABELS", "value": + ["prim", "test/unit/math/prim/fun/sum_test.cpp"]}]}, {"name": "grad"}]} + self.assertEqual(runner.build_tests(inventory, "^test/unit/math/prim/", "build", 4, 500), + [["cmake", "--build", "build", "--parallel", "4", "--target", "sum"]]) + with self.assertRaisesRegex(ValueError, "No registered tests match"): + runner.build_tests({"tests": []}, "^test/unit/", "build", 1, 500) + + def test_files_and_fixtures_build_through_one_selected_target(self): + fixture = "test/prob/bernoulli/bernoulli_test.hpp" + source = "test/unit/math/prim/fun/sum_test.cpp" + configure, build = runner.build_commands(inputs(), ["test/unit/math/rev", source, fixture], + Path("source"), Path("build")) + self.assertIn("-DSTAN_BUILD_PROB_TESTS=ON", configure) + self.assertIn("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", configure) + self.assertEqual(build[build.index("--target") + 1:], + ["unit_math_rev_all_tests", "stan_selected_tests"]) + regex = next(argument for argument in configure + if argument.startswith("-DSTAN_SELECTED_TEST_REGEX="))[len("-DSTAN_SELECTED_TEST_REGEX="):] + self.assertRegex(source, regex) + self.assertIsNone(re.search(regex, "test/unit/math/prim/fun/cumulative_sum_test.cpp")) + for mode in ("v", "ffv", "vv", "fd", "fv", "ffd"): + self.assertRegex(f"test/prob/bernoulli/bernoulli_00012_generated_{mode}_test.cpp", regex) + self.assertIsNone(re.search(regex, "test/prob/bernoulli/bernoulli_cdf_00000_generated_v_test.cpp")) + directory_only = runner.build_commands(inputs(), ["test/unit/math/rev"], Path("source"), Path("build")) + self.assertIn("-DSTAN_SELECTED_TEST_REGEX=", directory_only[0]) + self.assertNotIn("stan_selected_tests", directory_only[1]) + only_configure = runner.build_commands(inputs("--configure-only"), [], Path("source"), Path("build")) + self.assertEqual(len(only_configure), 1) + self.assertEqual(only_configure[0][:2], ["cmake", "-S"]) + + def test_fixtures_run_their_generated_tests(self): + labels = ["test/prob/bernoulli/bernoulli_00000_generated_v_test.cpp", + "test/prob/bernoulli/bernoulli_00001_generated_fd_test.cpp", + "test/prob/bernoulli/bernoulli_cdf_00000_generated_v_test.cpp"] + inventory = {"tests": [{"name": label, "properties": [{"name": "LABELS", "value": + ["prob_math", "bernoulli", label]}]} for label in labels]} + self.assertEqual(runner.fixture_tests(inventory, ["test/prob/bernoulli/bernoulli_test.hpp"]), + labels[:2]) + commands = [] + + def run(command, **kwargs): + commands.append(command) + if command[0] == "ctest": + return subprocess.CompletedProcess(command, 0, stdout=json.dumps(inventory)) + return subprocess.CompletedProcess(command, 0, stdout="") + + with tempfile.TemporaryDirectory() as working_dir, \ + patch.object(runner.subprocess, "run", run), patch.object(sys, "stdout", io.StringIO()): + code = runner.main(["--test-only", "--test-jobs", "2", "test/prob/bernoulli/bernoulli_test.hpp"], + ROOT, Path(working_dir), False, io.StringIO()) + self.assertEqual(code, 0) + self.assertEqual({Path(command[0]).name for command in commands[1:]}, + {"test_prob_bernoulli_bernoulli_00000_generated_v_test", + "test_prob_bernoulli_bernoulli_00001_generated_fd_test"}) + + def test_distribution_dependencies_use_the_build_compile_command(self): + compile_commands = [{"file": "/src/lib/gtest_main.cc", "command": + "clang++ -DSTAN -I/src -O0 -MD -MT x.o -MF x.d -o x.o -c /src/lib/gtest_main.cc"}] + command = get_dependencies.dependency_command(compile_commands) + self.assertEqual(command, ["clang++", "-DSTAN", "-I/src", "-O0", "-MM", "-MG", "-x", "c++"]) + output = ("normal_test.o: test/prob/normal/normal_test.hpp \\\n" + " /src/stan/math/prim/prob/normal_lpdf.hpp /usr/include/eigen.h \\\n" + " /src/stan/math/prim/fun/constants.hpp\n") + self.assertEqual(get_dependencies.parse_dependencies(output, Path("/src")), + {"stan/math/prim/prob/normal_lpdf.hpp", "stan/math/prim/fun/constants.hpp"}) + + def test_ci_build_aggregates_only_complete_selected_groups(self): + inventory = {"tests": [{"name": name, "properties": [{"name": "LABELS", "value": + ["test/unit/" + name + ".cpp", "stan_build:all"] + + (["stan_build:pair"] if name != "three" else [])}]} for name in ("one", "two", "three")]} + command = ["cmake", "--build", "build", "--parallel", "4", "--target"] + self.assertEqual(runner.build_tests(inventory, "^test/unit/", "build", 4, 500), [command + ["all"]]) + self.assertEqual(runner.build_tests(inventory, "^test/unit/(one|two)", "build", 4, 500), [command + ["pair"]]) + self.assertEqual(runner.build_tests(inventory, "one", "build", 4, 500), [command + ["one"]]) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/expressions/test_expression_testing_framework.py b/test/expressions/test_expression_testing_framework.py index 487ee8b9bfa..05370e703b7 100644 --- a/test/expressions/test_expression_testing_framework.py +++ b/test/expressions/test_expression_testing_framework.py @@ -16,6 +16,8 @@ class TestExpressionTestingFramework(unittest.TestCase): def runCommand(self, command): + command = command + ("--build-dir", "build", "--fresh", + "--cmake-arg=-DSTAN_TEST_OPTIMIZATION=0") p1 = subprocess.Popen(command, stdout = subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) out, err = p1.communicate() return p1.returncode, out, err diff --git a/test/prob/CMakeLists.txt b/test/prob/CMakeLists.txt new file mode 100644 index 00000000000..9ec3b74901c --- /dev/null +++ b/test/prob/CMakeLists.txt @@ -0,0 +1,88 @@ +# Use the existing Make test generator without importing the branch's C++ +# generator/fixture changes or its large generated typedef headers. +set(STAN_PROB_TEST_BATCH_SIZE 100 CACHE STRING "Distribution test cases per generated source") +if(NOT STAN_PROB_TEST_BATCH_SIZE MATCHES "^[1-9][0-9]*$") + message(FATAL_ERROR "STAN_PROB_TEST_BATCH_SIZE must be a positive integer") +endif() +add_executable(generate_tests generate_tests.cpp) +foreach(feature STAN_TEST_ROW_VECTORS STAN_PROB_TEST_ALL) + if(${feature}) + target_compile_definitions(generate_tests PRIVATE ${feature}) + endif() +endforeach() +if(STAN_USE_SYSTEM_BOOST) + target_link_libraries(generate_tests PRIVATE Boost::boost) +else() + target_include_directories(generate_tests SYSTEM PRIVATE "${STAN_BOOST_ROOT}") +endif() +# The fixtures stay out of the PCH: their TYPED_TEST_P and +# REGISTER_TYPED_TEST_SUITE_P statics must initialize in definition order, and +# Clang 7 reorders static initializers that come from a PCH. The PCH holds +# their dependencies instead. +stan_add_pch(prob_pch stan_test_options + ) + +# These are the argument combinations used by generate_tests.cpp with its +# selected row-vector setting. Non-reverse modes omit all-constant combinations. +set(vector_types 3) +if(STAN_TEST_ROW_VECTORS) + set(vector_types 4) +endif() +set(modes v ffv vv) +if(STAN_PROB_TEST_ALL) + list(APPEND modes fd fv ffd) +endif() +file(GLOB_RECURSE fixtures CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*_test.hpp") +set(generated_sources "") +add_custom_target(run_generate_tests) +foreach(fixture IN LISTS fixtures) + file(RELATIVE_PATH relative_fixture "${PROJECT_SOURCE_DIR}" "${fixture}") + string(REGEX REPLACE "_test\\.hpp$" "" base "${relative_fixture}") + configure_file("${fixture}" "${PROJECT_BINARY_DIR}/${relative_fixture}" COPYONLY) + file(STRINGS "${fixture}" arguments REGEX "^// Arguments:") + string(REGEX REPLACE "^// Arguments: *" "" arguments "${arguments}") + string(REGEX REPLACE ", *" ";" arguments "${arguments}") + set(combinations 1) + set(constants 1) + foreach(argument IN LISTS arguments) + if(argument STREQUAL "Ints") + math(EXPR combinations "${combinations} * ${vector_types}") + math(EXPR constants "${constants} * ${vector_types}") + elseif(argument STREQUAL "Doubles") + math(EXPR combinations "${combinations} * 2 * ${vector_types}") + math(EXPR constants "${constants} * ${vector_types}") + else() + message(FATAL_ERROR "Unsupported distribution argument '${argument}' in ${fixture}") + endif() + endforeach() + set(outputs "") + foreach(mode IN LISTS modes) + set(count ${combinations}) + if(NOT mode STREQUAL "v") + math(EXPR count "${count} - ${constants}") + endif() + # The existing generator deliberately emits one extra (possibly empty) + # batch when the case count is a multiple of the batch size. + math(EXPR last_batch "${count} / ${STAN_PROB_TEST_BATCH_SIZE}") + foreach(batch RANGE 0 ${last_batch}) + math(EXPR padded "100000 + ${batch}") + string(SUBSTRING "${padded}" 1 5 padded) + list(APPEND outputs "${PROJECT_BINARY_DIR}/${base}_${padded}_generated_${mode}_test.cpp") + endforeach() + endforeach() + add_custom_command(OUTPUT ${outputs} + COMMAND generate_tests "${relative_fixture}" ${STAN_PROB_TEST_BATCH_SIZE} + DEPENDS generate_tests "${fixture}" "${PROJECT_BINARY_DIR}/${relative_fixture}" + WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" + COMMENT "Generating distribution tests for ${relative_fixture}" + VERBATIM) + string(REPLACE "/" "_" generation_target "generate_${base}") + add_custom_target(${generation_target} DEPENDS ${outputs}) + add_dependencies(run_generate_tests ${generation_target}) + set_source_files_properties(${outputs} PROPERTIES STAN_GENERATOR_TARGET ${generation_target}) + list(APPEND generated_sources ${outputs}) +endforeach() +if(generated_sources) + add_gtest_grouped_test(PREFIX prob_math REPO_PATH test/prob + DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" PCH prob_pch SOURCES ${generated_sources}) +endif() diff --git a/test/prob/bernoulli/bernoulli_test.hpp b/test/prob/bernoulli/bernoulli_test.hpp index 7c195892cc5..726d8af0dd2 100644 --- a/test/prob/bernoulli/bernoulli_test.hpp +++ b/test/prob/bernoulli/bernoulli_test.hpp @@ -81,8 +81,3 @@ class AgradDistributionsBernoulli : public AgradDistributionTest { throw std::domain_error("n should either be 1 or 0"); } }; - -TEST(ProbDistributionsBernoulliCDF, Values) { - EXPECT_FLOAT_EQ(1, stan::math::bernoulli_cdf(1, 0.57)); - EXPECT_FLOAT_EQ(1 - 0.57, stan::math::bernoulli_cdf(0, 0.57)); -} diff --git a/test/prob/beta_binomial/beta_binomial_test.hpp b/test/prob/beta_binomial/beta_binomial_test.hpp index 1320e917453..0dd5016c339 100644 --- a/test/prob/beta_binomial/beta_binomial_test.hpp +++ b/test/prob/beta_binomial/beta_binomial_test.hpp @@ -90,8 +90,3 @@ class AgradDistributionsBetaBinomial : public AgradDistributionTest { - lbeta(alpha, beta); } }; - -TEST(ProbDistributionsBetaBinomialCDF, Values) { - EXPECT_FLOAT_EQ(0.8868204314, - stan::math::beta_binomial_cdf(49, 100, 1.349, 3.938)); -} diff --git a/test/prob/binomial/binomial_test.hpp b/test/prob/binomial/binomial_test.hpp index d8a674b1b8b..6df4aec7bed 100644 --- a/test/prob/binomial/binomial_test.hpp +++ b/test/prob/binomial/binomial_test.hpp @@ -71,18 +71,3 @@ class AgradDistributionsBinomial : public AgradDistributionTest { + (N - n) * log1m(theta); } }; - -TEST(ProbDistributionsBinomialCDF, Values) { - EXPECT_FLOAT_EQ(0.042817421, stan::math::binomial_cdf(24, 54, 0.57)); - EXPECT_FLOAT_EQ(1.0 - 0.57, - stan::math::binomial_cdf( - 0, 1, 0.57)); // Consistency with expected Bernoulli - EXPECT_FLOAT_EQ(1.0, stan::math::binomial_cdf( - 1, 1, 0.57)); // Consistency with expected Bernoulli - EXPECT_FLOAT_EQ(stan::math::bernoulli_cdf(0, 0.57), - stan::math::binomial_cdf( - 0, 1, 0.57)); // Consistency with implemented Bernoulli - EXPECT_FLOAT_EQ(stan::math::bernoulli_cdf(1, 0.57), - stan::math::binomial_cdf( - 1, 1, 0.57)); // Consistency with implemented Bernoulli -} diff --git a/test/prob/cauchy/cauchy_test.hpp b/test/prob/cauchy/cauchy_test.hpp index c6bbc853a81..c1e32eaad1a 100644 --- a/test/prob/cauchy/cauchy_test.hpp +++ b/test/prob/cauchy/cauchy_test.hpp @@ -87,10 +87,3 @@ class AgradDistributionsCauchy : public AgradDistributionTest { return -stan::math::LOG_PI - log(sigma) - log1p(square((y - mu) / sigma)); } }; - -TEST(ProbDistributionsCauchy, Cumulative) { - using stan::math::cauchy_cdf; - EXPECT_FLOAT_EQ(0.75, cauchy_cdf(1.0, 0.0, 1.0)); - EXPECT_FLOAT_EQ(0.187167, cauchy_cdf(-1.5, 0.0, 1.0)); - EXPECT_FLOAT_EQ(0.187167, cauchy_cdf(-2.5, -1.0, 1.0)); -} diff --git a/test/prob/double_exponential/double_exponential_test.hpp b/test/prob/double_exponential/double_exponential_test.hpp index db5aa235a59..df245c625a3 100644 --- a/test/prob/double_exponential/double_exponential_test.hpp +++ b/test/prob/double_exponential/double_exponential_test.hpp @@ -114,14 +114,3 @@ class AgradDistributionsDoubleExponential : public AgradDistributionTest { return -LOG_TWO - log(sigma) - fabs(y - mu) / sigma; } }; - -TEST(ProbDistributionsDoubleExponential, Cumulative) { - EXPECT_FLOAT_EQ(0.5, stan::math::double_exponential_cdf(1.0, 1.0, 1.0)); - EXPECT_FLOAT_EQ(0.8160603, stan::math::double_exponential_cdf(2.0, 1.0, 1.0)); - EXPECT_FLOAT_EQ(0.003368973, - stan::math::double_exponential_cdf(-3.0, 2.0, 1.0)); - EXPECT_FLOAT_EQ(0.6967347, stan::math::double_exponential_cdf(1.0, 0.0, 2.0)); - EXPECT_FLOAT_EQ(0.2246645, stan::math::double_exponential_cdf(1.9, 2.3, 0.5)); - EXPECT_FLOAT_EQ(0.10094826, - stan::math::double_exponential_cdf(1.9, 2.3, 0.25)); -} diff --git a/test/prob/exponential/exponential_test.hpp b/test/prob/exponential/exponential_test.hpp index 91a7983daf3..e82b87cd112 100644 --- a/test/prob/exponential/exponential_test.hpp +++ b/test/prob/exponential/exponential_test.hpp @@ -76,19 +76,3 @@ class AgradDistributionsExponential : public AgradDistributionTest { return log(beta) - beta * y; } }; - -TEST(ProbDistributionsExponential, Cumulative) { - using stan::math::exponential_cdf; - using std::numeric_limits; - EXPECT_FLOAT_EQ(0.95021293, exponential_cdf(2.0, 1.5)); - EXPECT_FLOAT_EQ(1.0, exponential_cdf(15.0, 3.9)); - EXPECT_FLOAT_EQ(0.62280765, exponential_cdf(0.25, 3.9)); - - // ?? - // EXPECT_FLOAT_EQ(0.0, - // exponential_cdf(-numeric_limits::infinity(), - // 1.5)); - EXPECT_FLOAT_EQ(0.0, exponential_cdf(0.0, 1.5)); - EXPECT_FLOAT_EQ(1.0, - exponential_cdf(numeric_limits::infinity(), 1.5)); -} diff --git a/test/prob/frechet/frechet_test.hpp b/test/prob/frechet/frechet_test.hpp index 2bd2b5ec92d..f27305623d8 100644 --- a/test/prob/frechet/frechet_test.hpp +++ b/test/prob/frechet/frechet_test.hpp @@ -86,14 +86,3 @@ class AgradDistributionsFrechet : public AgradDistributionTest { - pow(sigma / y, alpha); } }; - -TEST(ProbDistributionsFrechet, Cumulative) { - using stan::math::frechet_cdf; - using std::numeric_limits; - EXPECT_FLOAT_EQ(0.6065306597, frechet_cdf(2.0, 1.0, 1.0)); - EXPECT_FLOAT_EQ(2.744338423e-5, frechet_cdf(0.8, 2.9, 1.8)); - EXPECT_FLOAT_EQ(0.0, frechet_cdf(0.25, 3.9, 1.7)); - - EXPECT_FLOAT_EQ(1.0, - frechet_cdf(numeric_limits::infinity(), 1.0, 1.0)); -} diff --git a/test/prob/getDependencies.py b/test/prob/getDependencies.py index 88ff363c0bd..5272567252e 100755 --- a/test/prob/getDependencies.py +++ b/test/prob/getDependencies.py @@ -1,76 +1,108 @@ #!/usr/bin/env python3 """ This script determines which distributions may have changed -based on the files #included by their test. It then prints a set of strings -which will run said tests when fed to the top-level runTests.py script +based on the files #included by their test. It then prints the distribution +fixtures (test/prob//_test.hpp) whose tests should run; feed +them to the top-level runTests.py script. + +The dependencies come from the compiler with the flags of a configured CMake +build directory (runTests.py --configure-only), so they match the test build. """ -from typing import Set -import sys -import subprocess +from concurrent.futures import ThreadPoolExecutor +import argparse +import json +import os from pathlib import Path -import glob +import shlex +import subprocess +import sys +from typing import List, Set -def get_dependencies(file: Path) -> Set[str]: - file_dot_d = file.with_suffix(".d") - subprocess.run(["make", str(file_dot_d)], stdout=subprocess.DEVNULL) - contents = file_dot_d.read_text() - file_dot_d.unlink() +ROOT = Path(__file__).resolve().parents[2] - deps = set( - filter( - lambda s: "stan/math" in s and s.endswith(".hpp"), - map(str.strip, contents.split(" ")), - ) - ) - return deps | {str(file)} +def dependency_command(compile_commands: List[dict]) -> List[str]: + """Turn the gtest_main compile command into a dependency listing command.""" + for entry in compile_commands: + if entry["file"].endswith("gtest_main.cc"): + arguments = entry.get("arguments") or shlex.split(entry["command"]) + break + else: + raise ValueError("No gtest_main.cc compile command; configure with tests enabled") + command, skip = [], False + for argument in arguments: + if skip: + skip = False + elif argument in ("-o", "-MT", "-MF", "-MQ"): + skip = True + elif argument in ("-c", "-MD", "-MMD") or argument.endswith("gtest_main.cc"): + continue + else: + command.append(argument) + return command + ["-MM", "-MG", "-x", "c++"] -def get_changed() -> Set[str]: - changed_files = subprocess.run( - ["git", "diff", "--name-only", "origin/develop...HEAD"], - text=True, - capture_output=True, - ).stdout.splitlines() - return set(changed_files) +def parse_dependencies(output: str, root: Path) -> Set[str]: + """Keep the stan/math headers from make-style dependency output.""" + words = output.replace("\\\n", " ").split(":", 1)[-1].split() + deps = set() + for word in words: + path = Path(word) if Path(word).is_absolute() else root / word + try: + relative = Path(os.path.normpath(path)).relative_to(root).as_posix() + except ValueError: + continue + if "stan/math" in relative and relative.endswith(".hpp"): + deps.add(relative) + return deps -def add_tests_from_hpp(tests_to_run, test): - pattern = str(test).replace("_test.hpp", "_0*_test.cpp") - tests = glob.glob(pattern) - tests_to_run.extend(tests) +def get_dependencies(command: List[str], fixture: str, root: Path) -> Set[str]: + result = subprocess.run(command + [fixture], cwd=root, text=True, capture_output=True) + if result.returncode != 0: + raise RuntimeError(f"Listing dependencies of {fixture} failed:\n{result.stderr}") + return parse_dependencies(result.stdout, root) | {fixture} -if __name__ == "__main__": - if Path("makefile") not in Path(".").iterdir(): - raise ValueError("getDependencies must be ran from the top-level repository") - pretend = "--pretend-all" in sys.argv +def get_changed(root: Path) -> Set[str]: + return set(subprocess.run(["git", "diff", "--name-only", "origin/develop...HEAD"], + cwd=root, text=True, capture_output=True, + check=True).stdout.splitlines()) - changed = get_changed() - tests_to_run = [] - distribution_tests = Path("test", "prob") +def main(arguments, root: Path) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--pretend-all", action="store_true", + help="select every distribution fixture") + parser.add_argument("--build-dir", default="build", + help="configured CMake build directory (default: build)") + inputs = parser.parse_args(arguments) - subprocess.run(["make", "generate-tests"], stdout=subprocess.DEVNULL) + fixtures = sorted(path.relative_to(root).as_posix() + for path in (root / "test" / "prob").glob("*/*_test.hpp")) + if inputs.pretend_all: + selected = fixtures + else: + compile_commands = Path(inputs.build_dir) / "compile_commands.json" + if not compile_commands.is_absolute(): + compile_commands = Path.cwd() / compile_commands + command = dependency_command(json.loads(compile_commands.read_text())) + changed = get_changed(root) + with ThreadPoolExecutor(os.cpu_count()) as pool: + all_deps = list(pool.map(lambda fixture: get_dependencies(command, fixture, root), + fixtures)) + selected = [] + for fixture, deps in zip(fixtures, all_deps): + intersection = changed & deps + if intersection: + print(f"{fixture} has {len(intersection)} changed dependencies out of " + f"{len(deps)} files #included.", file=sys.stderr) + selected.append(fixture) + if selected: + print("\n".join(selected)) + return 0 - for dist in distribution_tests.iterdir(): - if not dist.is_dir(): - continue - print(f"Considering {dist}.", file=sys.stderr) - for test in dist.iterdir(): - if test.suffix != ".hpp": continue - if pretend: - add_tests_from_hpp(tests_to_run, test) - else: - deps = get_dependencies(test) - intersection = changed & deps - if intersection: - print( - f"{test} has {len(intersection)} changed dependencies out of {len(deps)} files #included.", - file=sys.stderr, - ) - add_tests_from_hpp(tests_to_run, test) - - if tests_to_run: - print("\n".join(set(tests_to_run))) + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:], ROOT)) diff --git a/test/prob/inv_gamma/inv_gamma_test.hpp b/test/prob/inv_gamma/inv_gamma_test.hpp index 2cf8b00160a..0395c07d41d 100644 --- a/test/prob/inv_gamma/inv_gamma_test.hpp +++ b/test/prob/inv_gamma/inv_gamma_test.hpp @@ -90,7 +90,3 @@ class AgradDistributionsInvGamma : public AgradDistributionTest { - beta / y; } }; - -TEST(ProbDistributionsInvGammaCdf, Values) { - EXPECT_FLOAT_EQ(0.557873, stan::math::inv_gamma_cdf(4.39, 1.349, 3.938)); -} diff --git a/test/prob/logistic/logistic_test.hpp b/test/prob/logistic/logistic_test.hpp index c0a5b2ae3f8..a6b9d61cf44 100644 --- a/test/prob/logistic/logistic_test.hpp +++ b/test/prob/logistic/logistic_test.hpp @@ -79,7 +79,3 @@ class AgradDistributionsLogistic : public AgradDistributionTest { return -(y - mu) / sigma - log(sigma) - 2.0 * log1p(exp(-(y - mu) / sigma)); } }; - -TEST(ProbDistributionsLogisticCDF, Values) { - EXPECT_FLOAT_EQ(0.047191944, stan::math::logistic_cdf(-3.45, 5.235, 2.89)); -} diff --git a/test/prob/loglogistic/loglogistic_cdf_test.hpp b/test/prob/loglogistic/loglogistic_cdf_test.hpp index bad07c22a48..c9fc37240f4 100644 --- a/test/prob/loglogistic/loglogistic_cdf_test.hpp +++ b/test/prob/loglogistic/loglogistic_cdf_test.hpp @@ -7,7 +7,7 @@ using stan::math::var; using std::numeric_limits; using std::vector; -class AgradCdfLogistic : public AgradCdfTest { +class AgradCdfLoglogistic : public AgradCdfTest { public: void valid_values(vector >& parameters, vector& cdf) { vector param(3); diff --git a/test/prob/lognormal/lognormal_test.hpp b/test/prob/lognormal/lognormal_test.hpp index 246808a7bfe..674b80aa354 100644 --- a/test/prob/lognormal/lognormal_test.hpp +++ b/test/prob/lognormal/lognormal_test.hpp @@ -84,17 +84,3 @@ class AgradDistributionsLognormal : public AgradDistributionTest { - square(log(y) - mu) / (2.0 * sigma * sigma); } }; - -TEST(ProbDistributionsLognormal, Cumulative) { - using stan::math::lognormal_cdf; - EXPECT_FLOAT_EQ(0.4687341, lognormal_cdf(1.2, 0.3, 1.5)); - EXPECT_FLOAT_EQ(0.2835506, lognormal_cdf(12.0, 3.0, 0.9)); - - double pos_inf = std::numeric_limits::infinity(); - - // ?? double neg_inf = -pos_inf; - // ?? EXPECT_FLOAT_EQ(0.0, lognormal_cdf(neg_inf, 0.0, 1.0)); - - EXPECT_FLOAT_EQ(0.0, lognormal_cdf(0.0, 0.0, 1.0)); - EXPECT_FLOAT_EQ(1.0, lognormal_cdf(pos_inf, 0.0, 1.0)); -} diff --git a/test/prob/neg_binomial_2/neg_binomial_2_ccdf_log_test.hpp b/test/prob/neg_binomial_2/neg_binomial_2_ccdf_log_test.hpp index d46026074ae..731a4a6de11 100644 --- a/test/prob/neg_binomial_2/neg_binomial_2_ccdf_log_test.hpp +++ b/test/prob/neg_binomial_2/neg_binomial_2_ccdf_log_test.hpp @@ -9,7 +9,7 @@ using stan::math::var; using std::numeric_limits; using std::vector; -class AgradCdfLogNegBinomial2 : public AgradCcdfLogTest { +class AgradCcdfLogNegBinomial2 : public AgradCcdfLogTest { public: void valid_values(vector >& parameters, vector& ccdf_log) { diff --git a/test/prob/pareto/pareto_test.hpp b/test/prob/pareto/pareto_test.hpp index 2147bf254dc..5115ed532f9 100644 --- a/test/prob/pareto/pareto_test.hpp +++ b/test/prob/pareto/pareto_test.hpp @@ -90,7 +90,3 @@ class AgradDistributionsPareto : public AgradDistributionTest { return log(alpha) + lmultiply(alpha, y_min) - lmultiply(alpha + 1.0, y); } }; - -TEST(ProbDistributionsParetoCDF, Values) { - EXPECT_FLOAT_EQ(0.60434447, stan::math::pareto_cdf(3.45, 2.89, 5.235)); -} diff --git a/test/prob/poisson/poisson_log_test.hpp b/test/prob/poisson/poisson_log_test.hpp index 2fb6cf9fccb..3b65f38a2a5 100644 --- a/test/prob/poisson/poisson_log_test.hpp +++ b/test/prob/poisson/poisson_log_test.hpp @@ -8,7 +8,7 @@ using stan::math::var; using std::numeric_limits; using std::vector; -class AgradDistributionsPoisson : public AgradDistributionTest { +class AgradDistributionsPoissonLog : public AgradDistributionTest { public: void valid_values(vector >& parameters, vector& log_prob) { diff --git a/test/prob/std_normal/std_normal_cdf_log_test.hpp b/test/prob/std_normal/std_normal_cdf_log_test.hpp index fb1a83a14ae..d67e5dfd225 100644 --- a/test/prob/std_normal/std_normal_cdf_log_test.hpp +++ b/test/prob/std_normal/std_normal_cdf_log_test.hpp @@ -9,7 +9,7 @@ using stan::math::square; using std::numeric_limits; using std::vector; -class AgradCdfLogNormal : public AgradCdfLogTest { +class AgradCdfLogStdNormal : public AgradCdfLogTest { public: void valid_values(vector >& parameters, vector& cdf_log) { diff --git a/test/prob/utility.hpp b/test/prob/utility.hpp index 0a6a7ee0857..035416e959e 100644 --- a/test/prob/utility.hpp +++ b/test/prob/utility.hpp @@ -29,7 +29,7 @@ struct is_empty { // ------------------------------------------------------------ namespace std { -std::ostream& operator<<(std::ostream& os, const vector& param) { +inline std::ostream& operator<<(std::ostream& os, const vector& param) { os << "("; for (size_t n = 0; n < param.size(); n++) { os << param[n]; @@ -40,7 +40,7 @@ std::ostream& operator<<(std::ostream& os, const vector& param) { return os; } -std::ostream& operator<<(std::ostream& os, const vector& param) { +inline std::ostream& operator<<(std::ostream& os, const vector& param) { os << "("; for (size_t n = 0; n < param.size(); n++) { os << param[n]; @@ -64,7 +64,8 @@ inline T get_param(const vector& params, const size_t n) { } template <> -empty get_param(const vector& /*params*/, const size_t /*n*/) { +inline empty get_param(const vector& /*params*/, + const size_t /*n*/) { return empty(); } @@ -133,8 +134,8 @@ inline T get_params(const vector>& parameters, const size_t p) { // handle empty template <> -empty get_params(const vector>& /*parameters*/, - const size_t /*p*/) { +inline empty get_params(const vector>& /*parameters*/, + const size_t /*p*/) { return empty(); } // handle scalars @@ -175,7 +176,7 @@ inline fvar get_params>(const vector>& parameters, return param; } template <> -fvar> get_params>>( +inline fvar> get_params>>( const vector>& parameters, const size_t p) { fvar> param(0); if (p < parameters[0].size()) { @@ -185,7 +186,7 @@ fvar> get_params>>( return param; } template <> -fvar> get_params>>( +inline fvar> get_params>>( const vector>& parameters, const size_t p) { fvar> param(0); if (p < parameters[0].size()) { @@ -195,7 +196,8 @@ fvar> get_params>>( return param; } template <> -int get_params(const vector>& parameters, const size_t p) { +inline int get_params(const vector>& parameters, + const size_t p) { int param(0); if (p < parameters[0].size()) param = (int)parameters[0][p]; @@ -212,7 +214,7 @@ inline vector get_params>( return param; } template <> -vector get_params>( +inline vector get_params>( const vector>& parameters, const size_t p) { vector param(parameters.size()); for (size_t n = 0; n < parameters.size(); n++) @@ -230,7 +232,7 @@ inline vector get_params>( return param; } template <> -vector> get_params>>( +inline vector> get_params>>( const vector>& parameters, const size_t p) { vector> param(parameters.size()); for (size_t n = 0; n < parameters.size(); n++) @@ -241,7 +243,7 @@ vector> get_params>>( return param; } template <> -vector> get_params>>( +inline vector> get_params>>( const vector>& parameters, const size_t p) { vector> param(parameters.size()); for (size_t n = 0; n < parameters.size(); n++) @@ -252,7 +254,7 @@ vector> get_params>>( return param; } template <> -vector>> get_params>>>( +inline vector>> get_params>>>( const vector>& parameters, const size_t p) { vector>> param(parameters.size()); for (size_t n = 0; n < parameters.size(); n++) @@ -263,7 +265,7 @@ vector>> get_params>>>( return param; } template <> -vector>> get_params>>>( +inline vector>> get_params>>>( const vector>& parameters, const size_t p) { vector>> param(parameters.size()); for (size_t n = 0; n < parameters.size(); n++) @@ -300,8 +302,8 @@ inline T get_params(const vector>& parameters, // handle empty template <> -empty get_params(const vector>& /*parameters*/, - const size_t /*n*/, const size_t /*p*/) { +inline empty get_params(const vector>& /*parameters*/, + const size_t /*n*/, const size_t /*p*/) { return empty(); } // handle scalars @@ -342,7 +344,7 @@ inline fvar get_params>(const vector>& parameters, return param; } template <> -fvar> get_params>>( +inline fvar> get_params>>( const vector>& parameters, const size_t n, const size_t p) { fvar> param(0); if (p < parameters[0].size()) { @@ -352,7 +354,7 @@ fvar> get_params>>( return param; } template <> -fvar> get_params>>( +inline fvar> get_params>>( const vector>& parameters, const size_t n, const size_t p) { fvar> param(0); if (p < parameters[0].size()) { @@ -362,8 +364,8 @@ fvar> get_params>>( return param; } template <> -int get_params(const vector>& parameters, const size_t n, - const size_t p) { +inline int get_params(const vector>& parameters, + const size_t n, const size_t p) { int param(0); if (p < parameters[0].size()) param = (int)parameters[n][p]; @@ -381,7 +383,7 @@ inline vector get_params>( return param; } template <> -vector get_params>( +inline vector get_params>( const vector>& parameters, const size_t /*n*/, const size_t p) { vector param(parameters.size()); @@ -401,7 +403,7 @@ inline vector get_params>( return param; } template <> -vector> get_params>>( +inline vector> get_params>>( const vector>& parameters, const size_t /*n*/, const size_t p) { vector> param(parameters.size()); @@ -413,7 +415,7 @@ vector> get_params>>( return param; } template <> -vector> get_params>>( +inline vector> get_params>>( const vector>& parameters, const size_t /*n*/, const size_t p) { vector> param(parameters.size()); @@ -425,7 +427,7 @@ vector> get_params>>( return param; } template <> -vector>> get_params>>>( +inline vector>> get_params>>>( const vector>& parameters, const size_t /*n*/, const size_t p) { vector>> param(parameters.size()); @@ -437,7 +439,7 @@ vector>> get_params>>>( return param; } template <> -vector>> get_params>>>( +inline vector>> get_params>>>( const vector>& parameters, const size_t /*n*/, const size_t p) { vector>> param(parameters.size()); @@ -541,8 +543,9 @@ typename scalar_type::type select_var_param( } template <> -empty select_var_param(const vector>& /*parameters*/, - const size_t /*n*/, const size_t /*p*/) { +inline empty select_var_param( + const vector>& /*parameters*/, const size_t /*n*/, + const size_t /*p*/) { return empty(); } diff --git a/test/prob/weibull/weibull_test.hpp b/test/prob/weibull/weibull_test.hpp index 54e4660c47e..cd7e198966e 100644 --- a/test/prob/weibull/weibull_test.hpp +++ b/test/prob/weibull/weibull_test.hpp @@ -84,18 +84,3 @@ class AgradDistributionsWeibull : public AgradDistributionTest { - pow(y / sigma, alpha); } }; - -TEST(ProbDistributionsWeibull, Cumulative) { - using stan::math::weibull_cdf; - using std::numeric_limits; - EXPECT_FLOAT_EQ(0.86466472, weibull_cdf(2.0, 1.0, 1.0)); - EXPECT_FLOAT_EQ(0.0032585711, weibull_cdf(0.25, 2.9, 1.8)); - EXPECT_FLOAT_EQ(1.0, weibull_cdf(3.9, 1.7, 0.25)); - - // EXPECT_FLOAT_EQ(0.0, - // weibull_cdf(-numeric_limits::infinity(), - // 1.0, 1.0)); - EXPECT_FLOAT_EQ(0.0, weibull_cdf(0.0, 1.0, 1.0)); - EXPECT_FLOAT_EQ(1.0, - weibull_cdf(numeric_limits::infinity(), 1.0, 1.0)); -} diff --git a/test/sig_utils.py b/test/sig_utils.py index c19073598ca..f109e1c6d98 100644 --- a/test/sig_utils.py +++ b/test/sig_utils.py @@ -1,6 +1,7 @@ import itertools import numbers import os +from pathlib import Path import re import subprocess import sys @@ -275,25 +276,20 @@ def get_signatures(): Retrieves function signatures from stanc3 :return: list of signatures """ - if os.name == "nt": - stanc3 = ".\\test\\expressions\\stanc.exe" - else: - stanc3 = "./test/expressions/stanc" - p = subprocess.Popen((make, stanc3.replace("\\", "/"))) + root = Path(__file__).resolve().parent.parent + stanc3 = root / "test/expressions" / ("stanc.exe" if os.name == "nt" else "stanc") + p = subprocess.Popen(("cmake", "-DSTAN_STANC_FILE=" + str(stanc3), + "-P", str(root / "cmake/stanc.cmake"))) if p.wait() != 0: sys.stderr.write("Error in making stanc3!") sys.exit(-1) - p = subprocess.Popen( - (stanc3 + " --dump-stan-math-signatures"), - stdout=subprocess.PIPE, - universal_newlines=True, - shell=True, - ) - res = parse_signature_file(p.stdout) - if p.wait() != 0: - sys.stderr.write("Error in getting signatures from stanc3!\n") - sys.exit(-1) + with subprocess.Popen([str(stanc3), "--dump-stan-math-signatures"], + stdout=subprocess.PIPE, universal_newlines=True) as p: + res = parse_signature_file(p.stdout) + if p.wait() != 0: + sys.stderr.write("Error in getting signatures from stanc3!\n") + sys.exit(-1) return res + internal_signatures diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt new file mode 100644 index 00000000000..362494db49a --- /dev/null +++ b/test/unit/CMakeLists.txt @@ -0,0 +1,41 @@ +# Keep each autodiff layer's PCH small. The AD helper header includes mix.hpp +# and test_ad_matvar.hpp, so those need no separate entries in its PCH. +foreach(layer prim fwd rev mix) + stan_add_pch(unit_${layer}_pch stan_test_options ) +endforeach() +stan_add_pch(unit_ad_pch stan_test_options ) +add_gtest_grouped_test(PREFIX unit_math REPO_PATH test/unit/math + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/math" PCH unit_mix_pch + LAYER_PCH_PREFIX unit AD_PCH unit_ad_pch) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Nonzero glibc allocations expose uninitialized CVODES quadrature buffers. + set_tests_properties(test_unit_math_rev_functor_cos_ode_typed_test PROPERTIES + ENVIRONMENT "MALLOC_PERTURB_=170") +endif() +# The timeout covers every parameterized case in the executable. Some +# Laplace suites exceed CTest's default 1500 seconds even with -O3. +get_property(laplace_tests DIRECTORY PROPERTY TESTS) +list(FILTER laplace_tests INCLUDE REGEX "^test_unit_math_laplace_") +if(laplace_tests) + set_tests_properties(${laplace_tests} PROPERTIES TIMEOUT 3600) +endif() + +# Umbrella and multiple-translation-unit checks must compile without a PCH. +# The public runner also accepts test/unit, so group them with test/unit/math. +set(unit_tests ${unit_math_selected_tests}) +foreach(source math_c11_test.cpp math_include_test.cpp multiple_translation_units_test.cpp) + get_filename_component(name "${source}" NAME_WE) + stan_add_gtest(test_unit_${name} "${source}" "test/unit/${source}" "" unit) + stan_matches_name_filters("test/unit/${source}" selected) + if(selected) + list(APPEND unit_tests test_unit_${name}) + endif() +endforeach() +add_library(stan_multiple_translation_units SHARED + multiple_translation_units1.cpp multiple_translation_units2.cpp) +target_link_libraries(stan_multiple_translation_units PRIVATE stanmath) +target_link_libraries(test_unit_multiple_translation_units_test PRIVATE stan_multiple_translation_units) +if(unit_tests) + stan_add_test_group(unit ${unit_tests}) + target_link_libraries(unit_all_tests PRIVATE stan_multiple_translation_units) +endif() diff --git a/test/unit/math/mix/functor/finite_diff_gradient_auto_test.cpp b/test/unit/math/mix/functor/finite_diff_gradient_auto_test.cpp index eeae792742a..18fed3f7d2d 100644 --- a/test/unit/math/mix/functor/finite_diff_gradient_auto_test.cpp +++ b/test/unit/math/mix/functor/finite_diff_gradient_auto_test.cpp @@ -48,3 +48,20 @@ TEST(MathMixMatFunctor, FiniteDiffGradientAuto) { w << 1, 2, 3, 4, 5; expect_match_autodiff(log_fun, w); } + +TEST(MathMixMatFunctor, FiniteDiffGradientAutoConstant) { + // Keep the evaluations observable so the compiler cannot fold the stencil. + const volatile double constant = -937.6; + auto f = [&constant](const auto&) { return constant; }; + Eigen::VectorXd x(3); + x << 0, 1.7, -1000; + double fx; + Eigen::VectorXd grad; + stan::math::finite_diff_gradient_auto(f, x, fx, grad); + + EXPECT_DOUBLE_EQ(-937.6, fx); + ASSERT_EQ(x.size(), grad.size()); + for (int i = 0; i < grad.size(); ++i) { + EXPECT_DOUBLE_EQ(0, grad(i)); + } +} diff --git a/test/unit/math/opencl/kernel_generator/calc_if_test.cpp b/test/unit/math/opencl/kernel_generator/calc_if_test.cpp index 0343ae0a350..89c233eb81d 100644 --- a/test/unit/math/opencl/kernel_generator/calc_if_test.cpp +++ b/test/unit/math/opencl/kernel_generator/calc_if_test.cpp @@ -50,7 +50,6 @@ TEST(KernelGenerator, calc_if_multi_result_pass_test) { } TEST(KernelGenerator, calc_if_multi_result_no_output_test) { - double eps = 0; MatrixXd m(2, 2); m << 1, 2, 3, 4; matrix_cl m_cl(m); diff --git a/test/unit/math/opencl/kernel_generator/colwise_reduction_test.cpp b/test/unit/math/opencl/kernel_generator/colwise_reduction_test.cpp index 0ee89e52094..4982efa00e2 100644 --- a/test/unit/math/opencl/kernel_generator/colwise_reduction_test.cpp +++ b/test/unit/math/opencl/kernel_generator/colwise_reduction_test.cpp @@ -59,6 +59,26 @@ TEST(KernelGenerator, colwise_prod_test) { EXPECT_MATRIX_NEAR(correct, res, 1e-9); } +TEST(KernelGenerator, colwise_reductions_rvalue_input) { + MatrixXd m(3, 2); + m << 1.1, 1.2, 1.3, 1.4, 1.5, 1.6; + + matrix_cl res_cl = stan::math::colwise_sum(matrix_cl(m)); + MatrixXd res = stan::math::from_matrix_cl(res_cl).colwise().sum(); + MatrixXd correct = m.colwise().sum(); + EXPECT_MATRIX_NEAR(correct, res, 1e-9); + + matrix_cl prod_cl = stan::math::colwise_prod(matrix_cl(m)); + MatrixXd prod = stan::math::from_matrix_cl(prod_cl).colwise().prod(); + MatrixXd correct_prod = m.colwise().prod(); + EXPECT_MATRIX_NEAR(correct_prod, prod, 1e-9); + + matrix_cl max_cl = stan::math::colwise_max(matrix_cl(m)); + MatrixXd max = stan::math::from_matrix_cl(max_cl).colwise().maxCoeff(); + MatrixXd correct_max = m.colwise().maxCoeff(); + EXPECT_MATRIX_NEAR(correct_max, max, 1e-9); +} + TEST(KernelGenerator, colwise_min_test) { MatrixXd m(3, 2); m << 1.1, 1.2, 1.3, 1.4, 1.5, 1.6; diff --git a/test/unit/math/opencl/kernel_generator/reduction_2d_test.cpp b/test/unit/math/opencl/kernel_generator/reduction_2d_test.cpp index 0569d901a1f..ca92f3a7ee6 100644 --- a/test/unit/math/opencl/kernel_generator/reduction_2d_test.cpp +++ b/test/unit/math/opencl/kernel_generator/reduction_2d_test.cpp @@ -57,6 +57,23 @@ TEST(KernelGenerator, prod_2d_test) { EXPECT_NEAR(correct, res, 1e-9); } +TEST(KernelGenerator, reductions_2d_rvalue_input) { + MatrixXd m(3, 2); + m << 1.1, 1.2, 1.3, 1.4, 1.5, 1.6; + + matrix_cl res_cl = stan::math::sum_2d(matrix_cl(m)); + double res = stan::math::from_matrix_cl(res_cl).sum(); + EXPECT_NEAR(m.sum(), res, 1e-9); + + matrix_cl prod_cl = stan::math::prod_2d(matrix_cl(m)); + double prod = stan::math::from_matrix_cl(prod_cl).prod(); + EXPECT_NEAR(m.prod(), prod, 1e-9); + + matrix_cl max_cl = stan::math::max_2d(matrix_cl(m)); + double max = stan::math::from_matrix_cl(max_cl).maxCoeff(); + EXPECT_NEAR(m.maxCoeff(), max, 1e-9); +} + TEST(KernelGenerator, min_2d_test) { MatrixXd m(3, 2); m << 1.1, 1.2, 1.3, 1.4, 1.5, 1.6; diff --git a/test/unit/math/opencl/matrix_cl_test.cpp b/test/unit/math/opencl/matrix_cl_test.cpp index 92a50033378..eb3f86678d8 100644 --- a/test/unit/math/opencl/matrix_cl_test.cpp +++ b/test/unit/math/opencl/matrix_cl_test.cpp @@ -50,7 +50,6 @@ TEST(MathMatrixCL, matrix_cl_value) { TEST(MathMatrixCL, assignment) { using stan::math::matrix_cl; Eigen::Matrix mat_1; - Eigen::Matrix mat_2; mat_1 << 1, 2, 3, 4; matrix_cl mat1_cl(mat_1); matrix_cl mat2_cl(2, 2); diff --git a/test/unit/math/opencl/pinned_matrix_test.cpp b/test/unit/math/opencl/pinned_matrix_test.cpp index eb266809655..6ff90df7af4 100644 --- a/test/unit/math/opencl/pinned_matrix_test.cpp +++ b/test/unit/math/opencl/pinned_matrix_test.cpp @@ -1,10 +1,11 @@ #ifdef STAN_OPENCL #include #include +#include #include #include -TEST(AgradRev, pinned_matrix_matrix_test) { +TEST_F(AgradRev, pinned_matrix_matrix_test) { using Eigen::MatrixXd; using stan::math::pinned_matrix; @@ -45,7 +46,7 @@ TEST(AgradRev, pinned_matrix_matrix_test) { stan::math::recover_memory(); } -TEST(AgradRev, pinned_matrix_vector_test) { +TEST_F(AgradRev, pinned_matrix_vector_test) { using Eigen::VectorXd; using stan::math::pinned_matrix; @@ -86,7 +87,7 @@ TEST(AgradRev, pinned_matrix_vector_test) { stan::math::recover_memory(); } -TEST(AgradRev, pinned_matrix_row_vector_test) { +TEST_F(AgradRev, pinned_matrix_row_vector_test) { using Eigen::RowVectorXd; using stan::math::pinned_matrix; diff --git a/test/unit/math/opencl/prim/mdivide_left_tri_low_test.cpp b/test/unit/math/opencl/prim/mdivide_left_tri_low_test.cpp index c0151631ed1..93e5d029013 100644 --- a/test/unit/math/opencl/prim/mdivide_left_tri_low_test.cpp +++ b/test/unit/math/opencl/prim/mdivide_left_tri_low_test.cpp @@ -49,7 +49,6 @@ inline void mdivide_left_tri_low_Ab_test(int size) { stan::math::matrix_cl m0_cl(m1, stan::math::matrix_cl_view::Lower); stan::math::matrix_d m1_cl = stan::math::from_matrix_cl( stan::math::mdivide_left_tri_low(m0_cl, m0_cl)); - double max_error = 0; for (int i = 0; i < m1_cpu.size(); i++) { stan::test::expect_near_rel("mdivide_left_tri_low (OpenCL)", m1_cpu(i), m1_cl(i)); @@ -77,7 +76,6 @@ inline void mdivide_left_tri_low_A_test(int size) { stan::math::matrix_cl m1_A_cl(m1, stan::math::matrix_cl_view::Lower); m1_cl = stan::math::from_matrix_cl(stan::math::mdivide_left_tri_low(m1_A_cl)); - double max_error = 0; for (int i = 0; i < size; i++) { for (int j = 0; j <= i; j++) { stan::test::expect_near_rel("mdivide_left_tri_low (OpenCL)", m1_cpu(i, j), diff --git a/test/unit/math/opencl/prim/mdivide_right_tri_low_test.cpp b/test/unit/math/opencl/prim/mdivide_right_tri_low_test.cpp index a339cfbb107..d59099dc3cc 100644 --- a/test/unit/math/opencl/prim/mdivide_right_tri_low_test.cpp +++ b/test/unit/math/opencl/prim/mdivide_right_tri_low_test.cpp @@ -48,7 +48,6 @@ inline void mdivide_right_tri_low_Ab_test(int size) { stan::math::matrix_cl m1_A_cl(m1, stan::math::matrix_cl_view::Lower); stan::math::matrix_d m1_cl = stan::math::from_matrix_cl( stan::math::mdivide_right_tri_low(m1_A_cl, m1_A_cl)); - double max_error = 0; for (int i = 0; i < m1_cpu.size(); i++) { stan::test::expect_near_rel("mdivide_right_tri_low (OpenCL)", m1_cpu(i), m1_cl(i)); diff --git a/test/unit/math/opencl/rev/append_array_test.cpp b/test/unit/math/opencl/rev/append_array_test.cpp index 3b2252a767c..4e9023f5e72 100644 --- a/test/unit/math/opencl/rev/append_array_test.cpp +++ b/test/unit/math/opencl/rev/append_array_test.cpp @@ -15,8 +15,6 @@ TEST(OpenCLAppendArray, prim_rev_values_small) { } TEST(OpenCLAppendArray, prim_rev_size_0) { - int N = 0; - std::vector a{}; std::vector b{}; stan::math::test::compare_cpu_opencl_prim_rev(append_array_functor, a, b); diff --git a/test/unit/math/opencl/rev/arena_matrix_cl_test.cpp b/test/unit/math/opencl/rev/arena_matrix_cl_test.cpp index 7176cfd947d..bb067482643 100644 --- a/test/unit/math/opencl/rev/arena_matrix_cl_test.cpp +++ b/test/unit/math/opencl/rev/arena_matrix_cl_test.cpp @@ -1,9 +1,10 @@ #ifdef STAN_OPENCL #include +#include #include #include -TEST(AgradRev, arena_matrix_cl_shallow_copies) { +TEST_F(AgradRev, arena_matrix_cl_shallow_copies) { stan::math::arena_matrix_cl a(3, 2); stan::math::arena_matrix_cl b(a); stan::math::arena_matrix_cl c; @@ -12,13 +13,13 @@ TEST(AgradRev, arena_matrix_cl_shallow_copies) { EXPECT_EQ(a.buffer()(), c.buffer()()); } -TEST(AgradRev, arena_matrix_cl_to_matrix_cl_conversion) { +TEST_F(AgradRev, arena_matrix_cl_to_matrix_cl_conversion) { stan::math::arena_matrix_cl a(3, 2); const stan::math::matrix_cl& b(a); EXPECT_EQ(a.buffer()(), b.buffer()()); } -TEST(AgradRev, arena_matrix_cl_to_matrix_cl_move_construction) { +TEST_F(AgradRev, arena_matrix_cl_to_matrix_cl_move_construction) { stan::math::arena_matrix_cl a(3, 2); cl::Buffer a_buf = a.buffer(); stan::math::matrix_cl b(std::move(a)); @@ -26,7 +27,7 @@ TEST(AgradRev, arena_matrix_cl_to_matrix_cl_move_construction) { EXPECT_EQ(a_buf(), b.buffer()()); } -TEST(AgradRev, arena_matrix_cl_to_matrix_cl_move_assignment) { +TEST_F(AgradRev, arena_matrix_cl_to_matrix_cl_move_assignment) { stan::math::arena_matrix_cl a(3, 2); cl::Buffer a_buf = a.buffer(); stan::math::matrix_cl b; diff --git a/test/unit/math/opencl/rev/bernoulli_cdf_test.cpp b/test/unit/math/opencl/rev/bernoulli_cdf_test.cpp index 107708c0d80..79975958028 100644 --- a/test/unit/math/opencl/rev/bernoulli_cdf_test.cpp +++ b/test/unit/math/opencl/rev/bernoulli_cdf_test.cpp @@ -45,7 +45,6 @@ auto bernoulli_cdf_functor = [](const auto& n, const auto& theta) { TEST(ProbDistributionsBernoulliCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{0, 1, 3}; Eigen::VectorXd theta(N); @@ -59,7 +58,6 @@ TEST(ProbDistributionsBernoulliCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsBernoulliCdf, opencl_matches_cpu_small_n_negative) { int N = 3; - int M = 2; std::vector n{0, 1, -5}; Eigen::VectorXd theta(N); @@ -83,8 +81,6 @@ TEST(ProbDistributionsBernoulliCdf, opencl_broadcast_n) { } TEST(ProbDistributionsBernoulliCdf, opencl_broadcast_theta) { - int N = 3; - std::vector n{0, 1, 0}; double theta_scal = 0.4; diff --git a/test/unit/math/opencl/rev/bernoulli_lccdf_test.cpp b/test/unit/math/opencl/rev/bernoulli_lccdf_test.cpp index 732b1e62f2c..51a2e43a4a6 100644 --- a/test/unit/math/opencl/rev/bernoulli_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/bernoulli_lccdf_test.cpp @@ -45,7 +45,6 @@ auto bernoulli_lccdf_functor = [](const auto& n, const auto& theta) { TEST(ProbDistributionsBernoulliLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{0, 0, 0}; Eigen::VectorXd theta(N); @@ -59,7 +58,6 @@ TEST(ProbDistributionsBernoulliLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsBernoulliLccdf, opencl_matches_cpu_small_n_negative) { int N = 3; - int M = 2; std::vector n{0, 0, -5}; Eigen::VectorXd theta(N); @@ -73,7 +71,6 @@ TEST(ProbDistributionsBernoulliLccdf, opencl_matches_cpu_small_n_negative) { TEST(ProbDistributionsBernoulliLccdf, opencl_matches_cpu_small_n_over_one) { int N = 3; - int M = 2; std::vector n{0, 1, 5}; Eigen::VectorXd theta(N); @@ -97,8 +94,6 @@ TEST(ProbDistributionsBernoulliLccdf, opencl_broadcast_n) { } TEST(ProbDistributionsBernoulliLccdf, opencl_broadcast_theta) { - int N = 3; - std::vector n{0, 0, 0}; double theta_scal = 0.4; diff --git a/test/unit/math/opencl/rev/bernoulli_logit_lpmf_test.cpp b/test/unit/math/opencl/rev/bernoulli_logit_lpmf_test.cpp index 401b88349f7..b6d86ae5e90 100644 --- a/test/unit/math/opencl/rev/bernoulli_logit_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/bernoulli_logit_lpmf_test.cpp @@ -48,7 +48,6 @@ auto bernoulli_logit_lpmf_functor_propto TEST(ProbDistributionsBernoulliLogit, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{0, 1, 1}; Eigen::VectorXd theta(N); @@ -78,8 +77,6 @@ TEST(ProbDistributionsBernoulliLogit, opencl_broadcast_n) { } TEST(ProbDistributionsBernoulliLogit, opencl_broadcast_theta) { - int N = 3; - std::vector n{0, 1, 0}; double theta = 0.4; diff --git a/test/unit/math/opencl/rev/bernoulli_lpmf_test.cpp b/test/unit/math/opencl/rev/bernoulli_lpmf_test.cpp index 2dd8ae41530..7351e45d7a2 100644 --- a/test/unit/math/opencl/rev/bernoulli_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/bernoulli_lpmf_test.cpp @@ -47,7 +47,6 @@ auto bernoulli_lpmf_functor_propto = [](const auto& n, const auto& theta) { TEST(ProbDistributionsBernoulli, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{0, 1, 0}; Eigen::VectorXd theta(N); @@ -77,8 +76,6 @@ TEST(ProbDistributionsBernoulli, opencl_broadcast_n) { } TEST(ProbDistributionsBernoulli, opencl_broadcast_theta) { - int N = 3; - std::vector n{0, 1, 0}; double theta_scal = 0.4; diff --git a/test/unit/math/opencl/rev/beta_lpdf_test.cpp b/test/unit/math/opencl/rev/beta_lpdf_test.cpp index dd0c3fce94e..a12ef5a5c80 100644 --- a/test/unit/math/opencl/rev/beta_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/beta_lpdf_test.cpp @@ -85,7 +85,6 @@ auto beta_lpdf_functor_propto TEST(ProbDistributionsBeta, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0, 0.5, 1; diff --git a/test/unit/math/opencl/rev/beta_proportion_lpdf_test.cpp b/test/unit/math/opencl/rev/beta_proportion_lpdf_test.cpp index 6709ea03e42..157d598bc09 100644 --- a/test/unit/math/opencl/rev/beta_proportion_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/beta_proportion_lpdf_test.cpp @@ -65,7 +65,6 @@ auto beta_proportion_lpdf_functor_propto TEST(ProbDistributionsBetaProportion, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/binomial_logit_lpmf_test.cpp b/test/unit/math/opencl/rev/binomial_logit_lpmf_test.cpp index 92b85841292..4ee784145d0 100644 --- a/test/unit/math/opencl/rev/binomial_logit_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/binomial_logit_lpmf_test.cpp @@ -114,8 +114,6 @@ TEST(ProbDistributionsBinomialLogit, opencl_broadcast_N) { } TEST(ProbDistributionsBinomialLogit, opencl_broadcast_alpha) { - int N = 3; - std::vector n{0, 1, 12}; std::vector m{0, 1, 123}; double alpha_scal = 0.4; diff --git a/test/unit/math/opencl/rev/binomial_lpmf_test.cpp b/test/unit/math/opencl/rev/binomial_lpmf_test.cpp index 7be5166eb86..e84dc9c6eab 100644 --- a/test/unit/math/opencl/rev/binomial_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/binomial_lpmf_test.cpp @@ -119,8 +119,6 @@ TEST(ProbDistributionsBinomial, opencl_broadcast_N) { } TEST(ProbDistributionsBinomial, opencl_broadcast_theta) { - int N = 3; - std::vector n{0, 1, 12}; std::vector m{0, 1, 123}; double theta_scal = 0.4; diff --git a/test/unit/math/opencl/rev/cauchy_lccdf_test.cpp b/test/unit/math/opencl/rev/cauchy_lccdf_test.cpp index 342bf34c129..29fa178f33b 100644 --- a/test/unit/math/opencl/rev/cauchy_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/cauchy_lccdf_test.cpp @@ -64,7 +64,6 @@ auto cauchy_lccdf_functor TEST(ProbDistributionsCauchyLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -0.6, 1.0; diff --git a/test/unit/math/opencl/rev/cauchy_lpdf_test.cpp b/test/unit/math/opencl/rev/cauchy_lpdf_test.cpp index 7623819af84..a31ab65c0fe 100644 --- a/test/unit/math/opencl/rev/cauchy_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/cauchy_lpdf_test.cpp @@ -67,7 +67,6 @@ auto cauchy_lpdf_functor_propto TEST(ProbDistributionsCauchy, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/double_exponential_cdf_test.cpp b/test/unit/math/opencl/rev/double_exponential_cdf_test.cpp index fc441df05e9..91f9ed8afd6 100644 --- a/test/unit/math/opencl/rev/double_exponential_cdf_test.cpp +++ b/test/unit/math/opencl/rev/double_exponential_cdf_test.cpp @@ -63,7 +63,6 @@ auto double_exponential_cdf_functor TEST(ProbDistributionsDoubleExponentialCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/double_exponential_lccdf_test.cpp b/test/unit/math/opencl/rev/double_exponential_lccdf_test.cpp index eca87505ee9..3edb3b9bb21 100644 --- a/test/unit/math/opencl/rev/double_exponential_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/double_exponential_lccdf_test.cpp @@ -66,7 +66,6 @@ auto double_exponential_lccdf_functor TEST(ProbDistributionsDoubleExponentialLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 2.8, 1.0; diff --git a/test/unit/math/opencl/rev/double_exponential_lcdf_test.cpp b/test/unit/math/opencl/rev/double_exponential_lcdf_test.cpp index 6e8f2f57f90..88a0d8e6aa1 100644 --- a/test/unit/math/opencl/rev/double_exponential_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/double_exponential_lcdf_test.cpp @@ -63,7 +63,6 @@ auto double_exponential_lcdf_functor TEST(ProbDistributionsDoubleExponentialLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.7; diff --git a/test/unit/math/opencl/rev/exp_mod_normal_cdf_test.cpp b/test/unit/math/opencl/rev/exp_mod_normal_cdf_test.cpp index 33c53bb16a7..711b9e38a3e 100644 --- a/test/unit/math/opencl/rev/exp_mod_normal_cdf_test.cpp +++ b/test/unit/math/opencl/rev/exp_mod_normal_cdf_test.cpp @@ -88,7 +88,6 @@ auto exp_mod_normal_cdf_functor TEST(ProbDistributionsDoubleExpModNormalCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, 1.4; @@ -109,7 +108,6 @@ TEST(ProbDistributionsDoubleExpModNormalCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsDoubleExpModNormalCdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -INFINITY, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/exp_mod_normal_lccdf_test.cpp b/test/unit/math/opencl/rev/exp_mod_normal_lccdf_test.cpp index 677ce2a2f89..d03eae230b3 100644 --- a/test/unit/math/opencl/rev/exp_mod_normal_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/exp_mod_normal_lccdf_test.cpp @@ -88,7 +88,6 @@ auto exp_mod_normal_lccdf_functor TEST(ProbDistributionsDoubleExpModNormalLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, 1.4; @@ -109,7 +108,6 @@ TEST(ProbDistributionsDoubleExpModNormalLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsDoubleExpModNormalLccdf, opencl_matches_cpu_small_y_pos_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, INFINITY; @@ -130,7 +128,6 @@ TEST(ProbDistributionsDoubleExpModNormalLccdf, TEST(ProbDistributionsDoubleExpModNormalLccdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, -INFINITY; diff --git a/test/unit/math/opencl/rev/exponential_cdf_test.cpp b/test/unit/math/opencl/rev/exponential_cdf_test.cpp index 405ba03f6a5..8002466f000 100644 --- a/test/unit/math/opencl/rev/exponential_cdf_test.cpp +++ b/test/unit/math/opencl/rev/exponential_cdf_test.cpp @@ -48,7 +48,6 @@ auto exponential_cdf_functor = [](const auto& y, const auto& beta) { TEST(ProbDistributionsExponentialCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/exponential_lccdf_test.cpp b/test/unit/math/opencl/rev/exponential_lccdf_test.cpp index 99cea24f1d4..23386c90562 100644 --- a/test/unit/math/opencl/rev/exponential_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/exponential_lccdf_test.cpp @@ -48,7 +48,6 @@ auto exponential_lccdf_functor = [](const auto& y, const auto& beta) { TEST(ProbDistributionsExponentialLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/exponential_lcdf_test.cpp b/test/unit/math/opencl/rev/exponential_lcdf_test.cpp index 25d47a0248f..3a236b149a0 100644 --- a/test/unit/math/opencl/rev/exponential_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/exponential_lcdf_test.cpp @@ -48,7 +48,6 @@ auto exponential_lcdf_functor = [](const auto& y, const auto& beta) { TEST(ProbDistributionsExponentialLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/exponential_lpdf_test.cpp b/test/unit/math/opencl/rev/exponential_lpdf_test.cpp index 01e5671360a..cbf8018af98 100644 --- a/test/unit/math/opencl/rev/exponential_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/exponential_lpdf_test.cpp @@ -57,7 +57,6 @@ auto exponential_lpdf_functor_propto = [](const auto& y, const auto& beta) { TEST(ProbDistributionsExponential, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0, 0.5, 12.3; diff --git a/test/unit/math/opencl/rev/frechet_cdf_test.cpp b/test/unit/math/opencl/rev/frechet_cdf_test.cpp index 0fbab3d7f5d..c5302fe7f4b 100644 --- a/test/unit/math/opencl/rev/frechet_cdf_test.cpp +++ b/test/unit/math/opencl/rev/frechet_cdf_test.cpp @@ -63,7 +63,6 @@ auto frechet_cdf_functor TEST(ProbDistributionsFrechetCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/frechet_lccdf_test.cpp b/test/unit/math/opencl/rev/frechet_lccdf_test.cpp index 36405481bac..918c6671cfc 100644 --- a/test/unit/math/opencl/rev/frechet_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/frechet_lccdf_test.cpp @@ -63,7 +63,6 @@ auto frechet_lccdf_functor TEST(ProbDistributionsFrechetLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/frechet_lcdf_test.cpp b/test/unit/math/opencl/rev/frechet_lcdf_test.cpp index a9ab54e177c..905da61790b 100644 --- a/test/unit/math/opencl/rev/frechet_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/frechet_lcdf_test.cpp @@ -63,7 +63,6 @@ auto frechet_lcdf_functor TEST(ProbDistributionsFrechetLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/gumbel_cdf_test.cpp b/test/unit/math/opencl/rev/gumbel_cdf_test.cpp index f65c838f4e6..572a06e0bd1 100644 --- a/test/unit/math/opencl/rev/gumbel_cdf_test.cpp +++ b/test/unit/math/opencl/rev/gumbel_cdf_test.cpp @@ -62,7 +62,6 @@ auto gumbel_cdf_functor = [](const auto& y, const auto& mu, const auto& sigma) { TEST(ProbDistributionsGumbelCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/gumbel_lccdf_test.cpp b/test/unit/math/opencl/rev/gumbel_lccdf_test.cpp index 859e48573c9..f6aa9a09500 100644 --- a/test/unit/math/opencl/rev/gumbel_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/gumbel_lccdf_test.cpp @@ -63,7 +63,6 @@ auto gumbel_lccdf_functor TEST(ProbDistributionsGumbelLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/gumbel_lcdf_test.cpp b/test/unit/math/opencl/rev/gumbel_lcdf_test.cpp index 9340c0b4183..b40cc75cef0 100644 --- a/test/unit/math/opencl/rev/gumbel_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/gumbel_lcdf_test.cpp @@ -63,7 +63,6 @@ auto gumbel_lcdf_functor TEST(ProbDistributionsGumbelLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/inv_gamma_lpdf_test.cpp b/test/unit/math/opencl/rev/inv_gamma_lpdf_test.cpp index d86600bd966..08bd621ff18 100644 --- a/test/unit/math/opencl/rev/inv_gamma_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/inv_gamma_lpdf_test.cpp @@ -79,7 +79,6 @@ auto inv_gamma_lpdf_functor_propto TEST(ProbDistributionsInvGamma, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 10, 0.5, 1; @@ -102,7 +101,6 @@ TEST(ProbDistributionsInvGamma, opencl_matches_cpu_small) { TEST(ProbDistributionsInvGamma, opencl_matches_cpu_small_zero_y) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0, 0.5, 1; diff --git a/test/unit/math/opencl/rev/log_mix_test.cpp b/test/unit/math/opencl/rev/log_mix_test.cpp index 68bccc12594..dc9eb721b83 100644 --- a/test/unit/math/opencl/rev/log_mix_test.cpp +++ b/test/unit/math/opencl/rev/log_mix_test.cpp @@ -59,7 +59,6 @@ auto log_mix_functor = [](const auto& theta, const auto& lambda) { TEST(OpenCLLogMix, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd theta1(N); theta1 << 0.5, 0.4, 0.1; diff --git a/test/unit/math/opencl/rev/logistic_cdf_test.cpp b/test/unit/math/opencl/rev/logistic_cdf_test.cpp index f279c45691e..385dab309b2 100644 --- a/test/unit/math/opencl/rev/logistic_cdf_test.cpp +++ b/test/unit/math/opencl/rev/logistic_cdf_test.cpp @@ -63,7 +63,6 @@ auto logistic_cdf_functor TEST(ProbDistributionsLogisticCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsLogisticCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsLogisticCdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -INFINITY, 1.0; diff --git a/test/unit/math/opencl/rev/logistic_lccdf_test.cpp b/test/unit/math/opencl/rev/logistic_lccdf_test.cpp index 50796ef91b6..00ff9dff489 100644 --- a/test/unit/math/opencl/rev/logistic_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/logistic_lccdf_test.cpp @@ -63,7 +63,6 @@ auto logistic_lccdf_functor TEST(ProbDistributionsLogisticLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsLogisticLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsLogisticLccdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -INFINITY, 1.0; diff --git a/test/unit/math/opencl/rev/logistic_lcdf_test.cpp b/test/unit/math/opencl/rev/logistic_lcdf_test.cpp index c3cb1cc9d4e..2a0e9ecd3a0 100644 --- a/test/unit/math/opencl/rev/logistic_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/logistic_lcdf_test.cpp @@ -63,7 +63,6 @@ auto logistic_lcdf_functor TEST(ProbDistributionsLogisticLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsLogisticLcdf, opencl_matches_cpu_small) { TEST(ProbDistributionsLogisticLcdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -INFINITY, 1.0; diff --git a/test/unit/math/opencl/rev/logistic_lpdf_test.cpp b/test/unit/math/opencl/rev/logistic_lpdf_test.cpp index 44cc923c6ea..9c3ba21f4b7 100644 --- a/test/unit/math/opencl/rev/logistic_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/logistic_lpdf_test.cpp @@ -79,7 +79,6 @@ auto logistic_lpdf_functor_propto TEST(ProbDistributionsLogistic, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 10, -0.5, 1; @@ -181,7 +180,6 @@ TEST(ProbDistributionsLogistic, opencl_matches_cpu_big) { TEST(ProbDistributionsLogistic, opencl_sigma_mu_scalar) { int N = 3; - int M = 2; double sigma = 0.3; double mu = 0.5; diff --git a/test/unit/math/opencl/rev/lognormal_cdf_test.cpp b/test/unit/math/opencl/rev/lognormal_cdf_test.cpp index bb45cc07d4a..4c80c21b39e 100644 --- a/test/unit/math/opencl/rev/lognormal_cdf_test.cpp +++ b/test/unit/math/opencl/rev/lognormal_cdf_test.cpp @@ -63,7 +63,6 @@ auto lognormal_cdf_functor TEST(ProbDistributionsLognormalCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsLognormalCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsLognormalCdf, opencl_matches_cpu_small_y_zero) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.0, 1.0; diff --git a/test/unit/math/opencl/rev/lognormal_lccdf_test.cpp b/test/unit/math/opencl/rev/lognormal_lccdf_test.cpp index 324864b55c3..a3619fcb1a3 100644 --- a/test/unit/math/opencl/rev/lognormal_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/lognormal_lccdf_test.cpp @@ -63,7 +63,6 @@ auto lognormal_lccdf_functor TEST(ProbDistributionsLognormalLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsLognormalLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsLognormalLccdf, opencl_matches_cpu_small_y_zero) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.0, 1.0; diff --git a/test/unit/math/opencl/rev/lognormal_lcdf_test.cpp b/test/unit/math/opencl/rev/lognormal_lcdf_test.cpp index b3c9163b015..9fdb5da9a8e 100644 --- a/test/unit/math/opencl/rev/lognormal_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/lognormal_lcdf_test.cpp @@ -63,7 +63,6 @@ auto lognormal_lcdf_functor TEST(ProbDistributionsLognormalLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsLognormalLcdf, opencl_matches_cpu_small) { TEST(ProbDistributionsLognormalLcdf, opencl_matches_cpu_small_y_zero) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.0, 1.0; diff --git a/test/unit/math/opencl/rev/lognormal_lpdf_test.cpp b/test/unit/math/opencl/rev/lognormal_lpdf_test.cpp index b3c0bdbd490..b984669aa5e 100644 --- a/test/unit/math/opencl/rev/lognormal_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/lognormal_lpdf_test.cpp @@ -85,7 +85,6 @@ auto lognormal_lpdf_functor_propto TEST(ProbDistributionsLognormal, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.1, 0.5, 1; @@ -107,7 +106,6 @@ TEST(ProbDistributionsLognormal, opencl_matches_cpu_small) { } TEST(ProbDistributionsLognormal, opencl_matches_cpu_small_zero_y) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0, 0.5, 1; diff --git a/test/unit/math/opencl/rev/multi_normal_cholesky_lpdf_test.cpp b/test/unit/math/opencl/rev/multi_normal_cholesky_lpdf_test.cpp index 221cec889d7..61443d022c4 100644 --- a/test/unit/math/opencl/rev/multi_normal_cholesky_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/multi_normal_cholesky_lpdf_test.cpp @@ -90,7 +90,6 @@ auto multi_normal_cholesky_lpdf_functor_propto TEST(ProbDistributionsMultiNormalCholesky, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y1(N); y1 << 0.5, 0.4, 0.1; diff --git a/test/unit/math/opencl/rev/neg_binomial_2_log_lpmf_test.cpp b/test/unit/math/opencl/rev/neg_binomial_2_log_lpmf_test.cpp index d9e2e89b852..6aea791d8fe 100644 --- a/test/unit/math/opencl/rev/neg_binomial_2_log_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/neg_binomial_2_log_lpmf_test.cpp @@ -69,7 +69,6 @@ auto neg_binomial_2_log_lpmf_functor_propto TEST(ProbDistributionsNegBinomial2Log, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{1, 0, 12}; Eigen::VectorXd eta(N); @@ -169,9 +168,6 @@ TEST(ProbDistributionsNegBinomial2Log, opencl_matches_cpu_big) { } TEST(ProbDistributionsNegBinomial2Log, opencl_matches_cpu_eta_phi_scalar) { - int N = 3; - int M = 2; - std::vector n{1, 0, 12}; double eta = 0.3; double phi = 0.8; diff --git a/test/unit/math/opencl/rev/neg_binomial_2_lpmf_test.cpp b/test/unit/math/opencl/rev/neg_binomial_2_lpmf_test.cpp index e7e863610d9..591d1598a86 100644 --- a/test/unit/math/opencl/rev/neg_binomial_2_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/neg_binomial_2_lpmf_test.cpp @@ -74,7 +74,6 @@ auto neg_binomial_2_lpmf_functor_propto TEST(muProbDistributionsNegBinomial2, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{1, 0, 12}; Eigen::VectorXd mu(N); @@ -175,7 +174,6 @@ TEST(muProbDistributionsNegBinomial2, opencl_matches_cpu_big) { TEST(ProbDistributionsNegBinomial2, opencl_scalar_n_mu) { int N = 3; - int M = 2; int n = 1; double mu = 0.3; diff --git a/test/unit/math/opencl/rev/neg_binomial_lpmf_test.cpp b/test/unit/math/opencl/rev/neg_binomial_lpmf_test.cpp index 28014935e8e..ce7613b75e4 100644 --- a/test/unit/math/opencl/rev/neg_binomial_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/neg_binomial_lpmf_test.cpp @@ -74,7 +74,6 @@ auto neg_binomial_lpmf_functor_propto TEST(muProbDistributionsNegBinomial, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{1, 0, 12}; Eigen::VectorXd alpha(N); diff --git a/test/unit/math/opencl/rev/normal_lccdf_test.cpp b/test/unit/math/opencl/rev/normal_lccdf_test.cpp index e03341403ed..190ff5589a3 100644 --- a/test/unit/math/opencl/rev/normal_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/normal_lccdf_test.cpp @@ -63,7 +63,6 @@ auto normal_lccdf_functor TEST(ProbDistributionsNormalLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/normal_lcdf_test.cpp b/test/unit/math/opencl/rev/normal_lcdf_test.cpp index e264c5e1d9c..a062e1304d4 100644 --- a/test/unit/math/opencl/rev/normal_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/normal_lcdf_test.cpp @@ -63,7 +63,6 @@ auto normal_lcdf_functor TEST(ProbDistributionsNormalLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/pareto_cdf_test.cpp b/test/unit/math/opencl/rev/pareto_cdf_test.cpp index 89e7fcd3f8b..556bcb4f687 100644 --- a/test/unit/math/opencl/rev/pareto_cdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_cdf_test.cpp @@ -63,7 +63,6 @@ auto pareto_cdf_functor TEST(ProbDistributionsParetoCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.35, 0.9, 1.5; @@ -81,7 +80,6 @@ TEST(ProbDistributionsParetoCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsParetoCdf, opencl_matches_cpu_small_y_lower_than_y_min) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.35, 0.7, 1.3; diff --git a/test/unit/math/opencl/rev/pareto_lccdf_test.cpp b/test/unit/math/opencl/rev/pareto_lccdf_test.cpp index 64efae248b9..ffd41b62f82 100644 --- a/test/unit/math/opencl/rev/pareto_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_lccdf_test.cpp @@ -63,7 +63,6 @@ auto pareto_lccdf_functor TEST(ProbDistributionsParetoLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.35, 0.9, 1.5; @@ -82,7 +81,6 @@ TEST(ProbDistributionsParetoLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsParetoLccdf, opencl_matches_cpu_small_y_lower_than_y_min) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.35, 0.7, 1.3; diff --git a/test/unit/math/opencl/rev/pareto_lcdf_test.cpp b/test/unit/math/opencl/rev/pareto_lcdf_test.cpp index 24c158cea90..1f2e27bd91d 100644 --- a/test/unit/math/opencl/rev/pareto_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_lcdf_test.cpp @@ -63,7 +63,6 @@ auto pareto_lcdf_functor TEST(ProbDistributionsParetoLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.35, 0.9, 1.5; @@ -81,7 +80,6 @@ TEST(ProbDistributionsParetoLcdf, opencl_matches_cpu_small) { TEST(ProbDistributionsParetoLcdf, opencl_matches_cpu_small_y_lower_than_y_min) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.35, 0.7, 1.3; diff --git a/test/unit/math/opencl/rev/pareto_lpdf_test.cpp b/test/unit/math/opencl/rev/pareto_lpdf_test.cpp index 1ab39a71093..e6378bf2634 100644 --- a/test/unit/math/opencl/rev/pareto_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_lpdf_test.cpp @@ -77,7 +77,6 @@ auto pareto_lpdf_functor_propto TEST(ProbDistributionsPareto, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/pareto_type_2_cdf_test.cpp b/test/unit/math/opencl/rev/pareto_type_2_cdf_test.cpp index e3bc404da15..13677126159 100644 --- a/test/unit/math/opencl/rev/pareto_type_2_cdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_type_2_cdf_test.cpp @@ -86,7 +86,6 @@ auto pareto_type_2_cdf_functor TEST(ProbDistributionsParetoType2Cdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.5, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/pareto_type_2_lccdf_test.cpp b/test/unit/math/opencl/rev/pareto_type_2_lccdf_test.cpp index fa71fc0cc8f..2d936ba78a9 100644 --- a/test/unit/math/opencl/rev/pareto_type_2_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_type_2_lccdf_test.cpp @@ -86,7 +86,6 @@ auto pareto_type_2_lccdf_functor TEST(ProbDistributionsParetoType2Lccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.5, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/pareto_type_2_lcdf_test.cpp b/test/unit/math/opencl/rev/pareto_type_2_lcdf_test.cpp index 2242c01a8bc..a6ee89e5097 100644 --- a/test/unit/math/opencl/rev/pareto_type_2_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/pareto_type_2_lcdf_test.cpp @@ -86,7 +86,6 @@ auto pareto_type_2_lcdf_functor TEST(ProbDistributionsParetoType2Lcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.5, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/poisson_log_lpmf_test.cpp b/test/unit/math/opencl/rev/poisson_log_lpmf_test.cpp index bc7dcfc3171..98ba889142e 100644 --- a/test/unit/math/opencl/rev/poisson_log_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/poisson_log_lpmf_test.cpp @@ -47,7 +47,6 @@ auto poisson_log_lpmf_functor_propto = [](const auto& n, const auto& alpha) { TEST(ProbDistributionsPoissonLog, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{0, 1, 6}; Eigen::VectorXd alpha(N); @@ -77,8 +76,6 @@ TEST(ProbDistributionsPoissonLog, opencl_broadcast_n) { } TEST(ProbDistributionsPoissonLog, opencl_broadcast_alpha) { - int N = 3; - std::vector n{0, 1, 5}; double alpha_scal = 0.4; diff --git a/test/unit/math/opencl/rev/poisson_lpmf_test.cpp b/test/unit/math/opencl/rev/poisson_lpmf_test.cpp index 0da588c6ab7..2176126def3 100644 --- a/test/unit/math/opencl/rev/poisson_lpmf_test.cpp +++ b/test/unit/math/opencl/rev/poisson_lpmf_test.cpp @@ -47,7 +47,6 @@ auto poisson_lpmf_functor_propto = [](const auto& n, const auto& alpha) { TEST(ProbDistributionsPoisson, opencl_matches_cpu_small) { int N = 3; - int M = 2; std::vector n{0, 1, 6}; Eigen::VectorXd alpha(N); @@ -75,8 +74,6 @@ TEST(ProbDistributionsPoisson, opencl_broadcast_n) { } TEST(ProbDistributionsPoisson, opencl_broadcast_alpha) { - int N = 3; - std::vector n{0, 1, 5}; double alpha_scal = 0.4; diff --git a/test/unit/math/opencl/rev/rayleigh_cdf_test.cpp b/test/unit/math/opencl/rev/rayleigh_cdf_test.cpp index 7db4882d0b8..ea8619bf53e 100644 --- a/test/unit/math/opencl/rev/rayleigh_cdf_test.cpp +++ b/test/unit/math/opencl/rev/rayleigh_cdf_test.cpp @@ -46,7 +46,6 @@ auto rayleigh_cdf_functor = [](const auto& y, const auto& mu) { TEST(ProbDistributionsRayleighCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/rayleigh_lccdf_test.cpp b/test/unit/math/opencl/rev/rayleigh_lccdf_test.cpp index 4ab1ea7384d..b9c5eadea45 100644 --- a/test/unit/math/opencl/rev/rayleigh_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/rayleigh_lccdf_test.cpp @@ -48,7 +48,6 @@ auto rayleigh_lccdf_functor = [](const auto& y, const auto& mu) { TEST(ProbDistributionsRayleighLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/rayleigh_lcdf_test.cpp b/test/unit/math/opencl/rev/rayleigh_lcdf_test.cpp index 713b5639fb4..c89f97394f3 100644 --- a/test/unit/math/opencl/rev/rayleigh_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/rayleigh_lcdf_test.cpp @@ -46,7 +46,6 @@ auto rayleigh_lcdf_functor = [](const auto& y, const auto& mu) { TEST(ProbDistributionsRayleighLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/rayleigh_lpdf_test.cpp b/test/unit/math/opencl/rev/rayleigh_lpdf_test.cpp index 93460840c2f..1c81a0b1f84 100644 --- a/test/unit/math/opencl/rev/rayleigh_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/rayleigh_lpdf_test.cpp @@ -52,7 +52,6 @@ auto rayleigh_lpdf_functor_propto = [](const auto& y, const auto& sigma) { TEST(ProbDistributionsRayleigh, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.1, 0.5, 12.3; diff --git a/test/unit/math/opencl/rev/segment_test.cpp b/test/unit/math/opencl/rev/segment_test.cpp index 8f78952a071..c87a7fb47a2 100644 --- a/test/unit/math/opencl/rev/segment_test.cpp +++ b/test/unit/math/opencl/rev/segment_test.cpp @@ -19,15 +19,15 @@ TEST(MathMatrixCL, segment_exception) { EXPECT_THROW(segment(m3_cl, 1, 5), std::invalid_argument); stan::math::var_value> m4_cl = m1_cl; - EXPECT_THROW(segment(m1_cl, 1, 5), std::invalid_argument); + EXPECT_THROW(segment(m4_cl, 1, 5), std::invalid_argument); stan::math::var_value> m5_cl = m2_cl; - EXPECT_THROW(segment(m2_cl, 0, 2), std::invalid_argument); - EXPECT_THROW(segment(m2_cl, 1, 5), std::invalid_argument); + EXPECT_THROW(segment(m5_cl, 0, 2), std::invalid_argument); + EXPECT_THROW(segment(m5_cl, 1, 5), std::invalid_argument); stan::math::var_value> m6_cl = m3_cl; - EXPECT_THROW(segment(m3_cl, 0, 2), std::invalid_argument); - EXPECT_THROW(segment(m3_cl, 1, 5), std::invalid_argument); + EXPECT_THROW(segment(m6_cl, 0, 2), std::invalid_argument); + EXPECT_THROW(segment(m6_cl, 1, 5), std::invalid_argument); } auto segment_functor = [](const auto& a, size_t i, size_t n) { diff --git a/test/unit/math/opencl/rev/skew_double_exponential_cdf_test.cpp b/test/unit/math/opencl/rev/skew_double_exponential_cdf_test.cpp index 7d82bffb838..397f97673ba 100644 --- a/test/unit/math/opencl/rev/skew_double_exponential_cdf_test.cpp +++ b/test/unit/math/opencl/rev/skew_double_exponential_cdf_test.cpp @@ -88,7 +88,6 @@ auto skew_double_exponential_cdf_functor TEST(ProbDistributionsSkewDoubleExponentialCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, 1.4; @@ -109,7 +108,6 @@ TEST(ProbDistributionsSkewDoubleExponentialCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsSkewDoubleExponentialCdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -INFINITY, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/skew_double_exponential_lccdf_test.cpp b/test/unit/math/opencl/rev/skew_double_exponential_lccdf_test.cpp index 0628af47960..79092f6a583 100644 --- a/test/unit/math/opencl/rev/skew_double_exponential_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/skew_double_exponential_lccdf_test.cpp @@ -88,7 +88,6 @@ auto skew_double_exponential_lccdf_functor TEST(ProbDistributionsSkewDoubleExponentialLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, 1.4; @@ -109,7 +108,6 @@ TEST(ProbDistributionsSkewDoubleExponentialLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsSkewDoubleExponentialLccdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -INFINITY, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/skew_double_exponential_lcdf_test.cpp b/test/unit/math/opencl/rev/skew_double_exponential_lcdf_test.cpp index c8b5a250856..03ad9cbf643 100644 --- a/test/unit/math/opencl/rev/skew_double_exponential_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/skew_double_exponential_lcdf_test.cpp @@ -88,7 +88,6 @@ auto skew_double_exponential_lcdf_functor TEST(ProbDistributionsSkewDoubleExponentialLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -0.3, 1.8, 1.4; @@ -109,7 +108,6 @@ TEST(ProbDistributionsSkewDoubleExponentialLcdf, opencl_matches_cpu_small) { TEST(ProbDistributionsSkewDoubleExponentialLcdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << -INFINITY, 1.8, 1.4; diff --git a/test/unit/math/opencl/rev/skew_normal_lpdf_test.cpp b/test/unit/math/opencl/rev/skew_normal_lpdf_test.cpp index 4e60315ee71..ad5220e8557 100644 --- a/test/unit/math/opencl/rev/skew_normal_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/skew_normal_lpdf_test.cpp @@ -90,7 +90,6 @@ auto skew_normal_lpdf_functor_propto TEST(ProbDistributionsSkewNormal, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/std_normal_cdf_test.cpp b/test/unit/math/opencl/rev/std_normal_cdf_test.cpp index 1bf3edff524..dea8b16e9f4 100644 --- a/test/unit/math/opencl/rev/std_normal_cdf_test.cpp +++ b/test/unit/math/opencl/rev/std_normal_cdf_test.cpp @@ -26,7 +26,6 @@ auto std_normal_cdf_functor TEST(ProbDistributionsStdNormalCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/std_normal_lccdf_test.cpp b/test/unit/math/opencl/rev/std_normal_lccdf_test.cpp index b261a062258..6f4b1e1f866 100644 --- a/test/unit/math/opencl/rev/std_normal_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/std_normal_lccdf_test.cpp @@ -26,7 +26,6 @@ auto std_normal_lccdf_functor TEST(ProbDistributionsStdNormalLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/std_normal_lcdf_test.cpp b/test/unit/math/opencl/rev/std_normal_lcdf_test.cpp index 52f3562c6fe..97fdbb38225 100644 --- a/test/unit/math/opencl/rev/std_normal_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/std_normal_lcdf_test.cpp @@ -26,7 +26,6 @@ auto std_normal_lcdf_functor TEST(ProbDistributionsStdNormalLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/std_normal_lpdf_test.cpp b/test/unit/math/opencl/rev/std_normal_lpdf_test.cpp index 7f8fcea56bd..ed4556ba09c 100644 --- a/test/unit/math/opencl/rev/std_normal_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/std_normal_lpdf_test.cpp @@ -27,7 +27,6 @@ auto std_normal_lpdf_functor_propto TEST(ProbDistributionsStdNormal, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/to_arena_test.cpp b/test/unit/math/opencl/rev/to_arena_test.cpp index 33b6a2853ed..caa5620df60 100644 --- a/test/unit/math/opencl/rev/to_arena_test.cpp +++ b/test/unit/math/opencl/rev/to_arena_test.cpp @@ -1,9 +1,10 @@ #ifdef STAN_OPENCL #include +#include #include #include -TEST(AgradRev, to_arena_matrix_cl_test) { +TEST_F(AgradRev, to_arena_matrix_cl_test) { Eigen::MatrixXd m(3, 2); m << 1, 2, 3, 4, 5, 6; stan::math::matrix_cl a(m); @@ -22,7 +23,7 @@ TEST(AgradRev, to_arena_matrix_cl_test) { stan::math::recover_memory(); } -TEST(AgradRev, to_arena_kg_expression_test) { +TEST_F(AgradRev, to_arena_kg_expression_test) { Eigen::MatrixXd m(3, 2); m << 1, 2, 3, 4, 5, 6; stan::math::matrix_cl a(m); @@ -40,7 +41,7 @@ TEST(AgradRev, to_arena_kg_expression_test) { stan::math::recover_memory(); } -TEST(AgradRev, to_arena_var_value_matrix_cl_test) { +TEST_F(AgradRev, to_arena_var_value_matrix_cl_test) { Eigen::MatrixXd val(3, 2); val << 1, 2, 3, 4, 5, 6; Eigen::MatrixXd adj(3, 2); diff --git a/test/unit/math/opencl/rev/uniform_cdf_test.cpp b/test/unit/math/opencl/rev/uniform_cdf_test.cpp index 262e6985897..5ed2189f1fe 100644 --- a/test/unit/math/opencl/rev/uniform_cdf_test.cpp +++ b/test/unit/math/opencl/rev/uniform_cdf_test.cpp @@ -63,7 +63,6 @@ auto uniform_cdf_functor TEST(ProbDistributionsUniformCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsUniformCdf, opencl_matches_cpu_small) { TEST(ProbDistributionsUniformCdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -INFINITY, 1.0; diff --git a/test/unit/math/opencl/rev/uniform_lccdf_test.cpp b/test/unit/math/opencl/rev/uniform_lccdf_test.cpp index bed3d490b85..05b8d00a41e 100644 --- a/test/unit/math/opencl/rev/uniform_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/uniform_lccdf_test.cpp @@ -63,7 +63,6 @@ auto uniform_lccdf_functor TEST(ProbDistributionsUniformLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsUniformLccdf, opencl_matches_cpu_small) { TEST(ProbDistributionsUniformLccdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -INFINITY, 1.0; diff --git a/test/unit/math/opencl/rev/uniform_lcdf_test.cpp b/test/unit/math/opencl/rev/uniform_lcdf_test.cpp index 5e028cb777e..cec558ada3f 100644 --- a/test/unit/math/opencl/rev/uniform_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/uniform_lcdf_test.cpp @@ -63,7 +63,6 @@ auto uniform_lcdf_functor TEST(ProbDistributionsUniformLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; @@ -81,7 +80,6 @@ TEST(ProbDistributionsUniformLcdf, opencl_matches_cpu_small) { TEST(ProbDistributionsUniformLcdf, opencl_matches_cpu_small_y_neg_inf) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, -INFINITY, 1.0; diff --git a/test/unit/math/opencl/rev/uniform_lpdf_test.cpp b/test/unit/math/opencl/rev/uniform_lpdf_test.cpp index 8f58bf84d32..6a1546d3bd1 100644 --- a/test/unit/math/opencl/rev/uniform_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/uniform_lpdf_test.cpp @@ -94,7 +94,6 @@ TEST(ProbDistributionsUniform, opencl_matches_cpu_small) { TEST(ProbDistributionsUniform, opencl_matches_cpu_small_y_out_of_bounds) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 14.0; diff --git a/test/unit/math/opencl/rev/vari_test.cpp b/test/unit/math/opencl/rev/vari_test.cpp index 5800f8875da..24233040f95 100644 --- a/test/unit/math/opencl/rev/vari_test.cpp +++ b/test/unit/math/opencl/rev/vari_test.cpp @@ -1,26 +1,28 @@ #ifdef STAN_OPENCL #include #include +#include #include #include -TEST(AgradRev, matrix_cl_vari_block) { +TEST_F(AgradRev, matrix_cl_vari_block) { using stan::math::vari_value; Eigen::MatrixXd a = Eigen::MatrixXd::Random(3, 3); Eigen::MatrixXd b = Eigen::MatrixXd::Random(3, 3); stan::math::matrix_cl a_cl(a); stan::math::matrix_cl b_cl(b); - vari_value> A(a); + stan::math::nested_rev_autodiff nested; + auto& A = *new vari_value>(a); EXPECT_MATRIX_EQ(a.block(0, 1, 2, 2), stan::math::from_matrix_cl(A.block(0, 1, 2, 2).val_)); - vari_value> B(a_cl); + auto& B = *new vari_value>(a_cl); B.adj_ = b_cl; EXPECT_MATRIX_EQ(a.block(0, 1, 2, 2), stan::math::from_matrix_cl(B.block(0, 1, 2, 2).val_)); EXPECT_MATRIX_EQ(b.block(0, 1, 2, 2), stan::math::from_matrix_cl(B.block(0, 1, 2, 2).adj_)); - vari_value> C(a_cl, a_cl); + auto& C = *new vari_value>(a_cl, a_cl); EXPECT_MATRIX_EQ(a.block(0, 1, 2, 2), stan::math::from_matrix_cl(C.block(0, 1, 2, 2).val_)); EXPECT_MATRIX_EQ(a.block(0, 1, 2, 2), diff --git a/test/unit/math/opencl/rev/weibull_cdf_test.cpp b/test/unit/math/opencl/rev/weibull_cdf_test.cpp index c8c6fe620c4..3fac364865e 100644 --- a/test/unit/math/opencl/rev/weibull_cdf_test.cpp +++ b/test/unit/math/opencl/rev/weibull_cdf_test.cpp @@ -63,7 +63,6 @@ auto weibull_cdf_functor TEST(ProbDistributionsWeibullCdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/weibull_lccdf_test.cpp b/test/unit/math/opencl/rev/weibull_lccdf_test.cpp index bf845190153..91b82eda794 100644 --- a/test/unit/math/opencl/rev/weibull_lccdf_test.cpp +++ b/test/unit/math/opencl/rev/weibull_lccdf_test.cpp @@ -63,7 +63,6 @@ auto weibull_lccdf_functor TEST(ProbDistributionsWeibullLccdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/weibull_lcdf_test.cpp b/test/unit/math/opencl/rev/weibull_lcdf_test.cpp index 4f0b1b89b63..a81c190e45a 100644 --- a/test/unit/math/opencl/rev/weibull_lcdf_test.cpp +++ b/test/unit/math/opencl/rev/weibull_lcdf_test.cpp @@ -63,7 +63,6 @@ auto weibull_lcdf_functor TEST(ProbDistributionsWeibullLcdf, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/opencl/rev/weibull_lpdf_test.cpp b/test/unit/math/opencl/rev/weibull_lpdf_test.cpp index c6c4e13ab69..dbea51cb619 100644 --- a/test/unit/math/opencl/rev/weibull_lpdf_test.cpp +++ b/test/unit/math/opencl/rev/weibull_lpdf_test.cpp @@ -77,7 +77,6 @@ auto weibull_lpdf_functor_propto TEST(ProbDistributionsWeibull, opencl_matches_cpu_small) { int N = 3; - int M = 2; Eigen::VectorXd y(N); y << 0.3, 0.8, 1.0; diff --git a/test/unit/math/prim/constraint/sum_to_zero_transform_test.cpp b/test/unit/math/prim/constraint/sum_to_zero_transform_test.cpp index 0184b77fc57..ebea34da8eb 100644 --- a/test/unit/math/prim/constraint/sum_to_zero_transform_test.cpp +++ b/test/unit/math/prim/constraint/sum_to_zero_transform_test.cpp @@ -90,7 +90,7 @@ TEST(prob_transform, sum_to_zero_mat_rt) { EXPECT_EQ(x.rows() + 1, y.rows()); EXPECT_EQ(x.cols() + 1, y.cols()); - EXPECT_FLOAT_EQ(y.sum(), 0.0); + EXPECT_NEAR(y.sum(), 0.0, 1E-15); EXPECT_MATRIX_NEAR(y.rowwise().sum(), Eigen::VectorXd::Zero(4), 1E-15); EXPECT_MATRIX_NEAR(y.colwise().sum(), Eigen::RowVectorXd::Zero(5), 1E-15); EXPECT_NO_THROW(stan::math::check_sum_to_zero("checkSumToZero", "y", y)); diff --git a/test/unit/math/prim/constraint/unit_vector_transform_test.cpp b/test/unit/math/prim/constraint/unit_vector_transform_test.cpp index 4bf5a3957bf..2622aaae874 100644 --- a/test/unit/math/prim/constraint/unit_vector_transform_test.cpp +++ b/test/unit/math/prim/constraint/unit_vector_transform_test.cpp @@ -3,6 +3,20 @@ #include #include +TEST(prob_transform, unit_vector_free_owns_evaluated_result) { + Eigen::VectorXd x(2); + x << 0.6, 0.8; + const Eigen::VectorXd expected = x; + + auto from_lvalue = stan::math::unit_vector_free(x); + EXPECT_MATRIX_EQ(expected, x); + auto from_expression + = stan::math::unit_vector_free(x + Eigen::VectorXd::Zero(2)); + x.setZero(); + EXPECT_MATRIX_EQ(expected, from_lvalue); + EXPECT_MATRIX_EQ(expected, from_expression); +} + TEST(prob_transform, unit_vector_rt0) { using Eigen::Dynamic; using Eigen::Matrix; diff --git a/test/unit/math/prim/core/get_num_threads_test.cpp b/test/unit/math/prim/core/get_num_threads_test.cpp index 5b86e308c89..c65e2d05a49 100644 --- a/test/unit/math/prim/core/get_num_threads_test.cpp +++ b/test/unit/math/prim/core/get_num_threads_test.cpp @@ -5,6 +5,7 @@ #ifdef STAN_THREADS TEST(get_num_threads, correct_values_stan_threads) { + scoped_stan_num_threads restore_environment; set_n_threads("10"); EXPECT_EQ(stan::math::internal::get_num_threads(), 10); @@ -17,6 +18,7 @@ TEST(get_num_threads, correct_values_stan_threads) { } TEST(get_num_threads, incorrect_values) { + scoped_stan_num_threads restore_environment; set_n_threads("abc"); EXPECT_THROW_MSG(stan::math::internal::get_num_threads(), std::invalid_argument, "positive number or -1"); @@ -35,6 +37,7 @@ TEST(get_num_threads, incorrect_values) { } #else TEST(get_num_threads, correct_values_no_stan_threads) { + scoped_stan_num_threads restore_environment; set_n_threads("10"); EXPECT_EQ(stan::math::internal::get_num_threads(), 1); @@ -45,3 +48,24 @@ TEST(get_num_threads, correct_values_no_stan_threads) { EXPECT_EQ(stan::math::internal::get_num_threads(), 1); } #endif + +TEST(get_num_threads, restores_environment) { + scoped_stan_num_threads restore_environment; + set_n_threads("7"); + { + scoped_stan_num_threads restore_previous; + set_n_threads("4"); + } + EXPECT_STREQ("7", std::getenv("STAN_NUM_THREADS")); + +#ifdef _WIN32 + _putenv_s("STAN_NUM_THREADS", ""); +#else + unsetenv("STAN_NUM_THREADS"); +#endif + { + scoped_stan_num_threads restore_absence; + set_n_threads("2"); + } + EXPECT_EQ(nullptr, std::getenv("STAN_NUM_THREADS")); +} diff --git a/test/unit/math/prim/core/init_threadpool_tbb_late_test.cpp b/test/unit/math/prim/core/init_threadpool_tbb_late_test.cpp index 4cefd6b96c0..46bfd4b3cc2 100644 --- a/test/unit/math/prim/core/init_threadpool_tbb_late_test.cpp +++ b/test/unit/math/prim/core/init_threadpool_tbb_late_test.cpp @@ -7,26 +7,19 @@ #ifdef TBB_INTERFACE_NEW -#include - TEST(intel_tbb_new_late_init, check_status) { - const int num_threads = tbb::global_control::max_allowed_parallelism; - - if (num_threads > 1) { - set_n_threads(num_threads - 1); - tbb::task_arena& tbb_arena = stan::math::init_threadpool_tbb(); - tbb_arena.execute([&]() { - EXPECT_TRUE(tbb_arena.is_active()); - - // STAN_NUM_THREADS is not being honored if we have first - // initialized the TBB scheduler outside of init_threadpool_tbb -#ifdef STAN_THREADS - EXPECT_EQ(num_threads, tbb::this_task_arena::max_concurrency()); -#else - EXPECT_EQ(num_threads, 1); -#endif + const int num_threads = 4; + tbb::task_arena existing_arena(num_threads, 1); + existing_arena.execute([num_threads]() { + EXPECT_EQ(num_threads, tbb::this_task_arena::max_concurrency()); + auto state = stan::math::internal::make_threadpool_tbb(num_threads - 1); + EXPECT_TRUE(state.arena.is_active()); + EXPECT_EQ(num_threads - 1, state.arena.max_concurrency()); + state.arena.execute([nested_threads = num_threads - 1]() { + EXPECT_EQ(nested_threads, tbb::this_task_arena::max_concurrency()); }); - } + EXPECT_EQ(num_threads, tbb::this_task_arena::max_concurrency()); + }); } #else @@ -34,6 +27,7 @@ TEST(intel_tbb_new_late_init, check_status) { #include TEST(intel_tbb_late_init, check_status) { + scoped_stan_num_threads restore_environment; const int num_threads = tbb::task_scheduler_init::default_num_threads(); tbb::task_scheduler_init tbb_scheduler; diff --git a/test/unit/math/prim/core/init_threadpool_tbb_test.cpp b/test/unit/math/prim/core/init_threadpool_tbb_test.cpp index b20adbf2e2e..b9522099cc8 100644 --- a/test/unit/math/prim/core/init_threadpool_tbb_test.cpp +++ b/test/unit/math/prim/core/init_threadpool_tbb_test.cpp @@ -5,19 +5,34 @@ #include TEST(intel_tbb_new_init, check_status) { + scoped_stan_num_threads restore_environment; set_n_threads(-1); - auto& tbb_init = stan::math::init_threadpool_tbb(); - EXPECT_TRUE(tbb_init.is_active()); - -#ifdef STAN_THREADS - EXPECT_EQ(std::thread::hardware_concurrency(), tbb_init.max_concurrency()); -#else - EXPECT_EQ(1, tbb_init.max_concurrency()); + auto& arena = stan::math::init_threadpool_tbb(); + ASSERT_TRUE(arena.is_active()); + const int original_threads = arena.max_concurrency(); +#ifndef STAN_THREADS + EXPECT_EQ(1, original_threads); #endif - auto& tbb_reinit = stan::math::init_threadpool_tbb(); - EXPECT_TRUE(tbb_init.is_active()); + // Other tests may have initialized the singleton before this test. + set_n_threads(original_threads + 1); + auto& reinit = stan::math::init_threadpool_tbb(); + EXPECT_EQ(&arena, &reinit); + EXPECT_TRUE(reinit.is_active()); + EXPECT_EQ(original_threads, reinit.max_concurrency()); + auto& explicit_reinit = stan::math::init_threadpool_tbb(original_threads + 2); + EXPECT_EQ(&arena, &explicit_reinit); + EXPECT_EQ(original_threads, explicit_reinit.max_concurrency()); +} - tbb_init.terminate(); - EXPECT_FALSE(tbb_init.is_active()); +#ifdef STAN_THREADS +TEST(intel_tbb_new_init, validates_after_initialization) { + scoped_stan_num_threads restore_environment; + stan::math::init_threadpool_tbb(2); + EXPECT_THROW_MSG(stan::math::init_threadpool_tbb(-2), std::invalid_argument, + "positive or -1"); + set_n_threads("invalid"); + EXPECT_THROW_MSG(stan::math::init_threadpool_tbb(), std::invalid_argument, + "positive number or -1"); } +#endif diff --git a/test/unit/math/prim/core/set_num_threads_test.cpp b/test/unit/math/prim/core/set_num_threads_test.cpp index f46eeec0760..e6d2c1758bc 100644 --- a/test/unit/math/prim/core/set_num_threads_test.cpp +++ b/test/unit/math/prim/core/set_num_threads_test.cpp @@ -1,14 +1,22 @@ #include #include +#include TEST(intel_tbb_new_init, set_threads) { - int num_threads = std::thread::hardware_concurrency(); - auto& tbb_init = stan::math::init_threadpool_tbb(num_threads - 1); - EXPECT_TRUE(tbb_init.is_active()); - -#ifdef STAN_THREADS - EXPECT_EQ(num_threads - 1, tbb_init.max_concurrency()); -#else - EXPECT_EQ(1, tbb_init.max_concurrency()); -#endif + using control = tbb::global_control; + const auto previous = control::active_value(control::max_allowed_parallelism); + for (int num_threads : {1, 2, 4}) { + { + auto state = stan::math::internal::make_threadpool_tbb(num_threads); + EXPECT_TRUE(state.arena.is_active()); + EXPECT_EQ(num_threads, state.arena.max_concurrency()); + EXPECT_EQ(std::min(previous, static_cast(num_threads)), + control::active_value(control::max_allowed_parallelism)); + state.arena.execute([num_threads]() { + EXPECT_EQ(num_threads, tbb::this_task_arena::max_concurrency()); + }); + } + EXPECT_EQ(previous, + control::active_value(control::max_allowed_parallelism)); + } } diff --git a/test/unit/math/prim/fun/eval_test.cpp b/test/unit/math/prim/fun/eval_test.cpp index 148cc2f1e12..5b6605bd7f3 100644 --- a/test/unit/math/prim/fun/eval_test.cpp +++ b/test/unit/math/prim/fun/eval_test.cpp @@ -196,7 +196,7 @@ TEST(MathFunctions, eval_return_type_expression) { TEST(MathFunctions, eval_return_type_short_circuit_static_sized_matrix) { Eigen::Matrix a; - const Eigen::Matrix b; + const Eigen::Matrix b{}; EXPECT_TRUE((std::is_same&>::value)); EXPECT_TRUE((std::is_same a; - const Eigen::Matrix b; + const Eigen::Matrix b{}; EXPECT_TRUE((std::is_same&>::value)); EXPECT_TRUE((std::is_same a; - const Eigen::Matrix b; + const Eigen::Matrix b{}; EXPECT_TRUE((std::is_same&>::value)); EXPECT_TRUE((std::is_same +#include + +TEST(MathPrimFunctor, FiniteDiffGradientConstant) { + // Preserve the evaluations when testing cancellation under FMA contraction. + const volatile double constant = -937.6; + auto f = [&constant](const Eigen::VectorXd&) { return constant; }; + Eigen::VectorXd x(3); + x << 0, 1.7, -1000; + double fx; + Eigen::VectorXd grad; + stan::math::finite_diff_gradient(f, x, fx, grad); + + EXPECT_DOUBLE_EQ(-937.6, fx); + ASSERT_EQ(x.size(), grad.size()); + for (int i = 0; i < grad.size(); ++i) { + EXPECT_DOUBLE_EQ(0, grad(i)); + } +} + +TEST(MathPrimFunctor, FiniteDiffGradientPolynomial) { + auto f = [](const Eigen::VectorXd& x) { + return x(0) * x(0) * x(0) + x(1) * x(1) * x(1) * x(1); + }; + Eigen::VectorXd x(2); + x << 1.7, -0.3; + double fx; + Eigen::VectorXd grad; + stan::math::finite_diff_gradient(f, x, fx, grad); + + EXPECT_DOUBLE_EQ(f(x), fx); + ASSERT_EQ(2, grad.size()); + EXPECT_NEAR(3 * x(0) * x(0), grad(0), 1e-10); + EXPECT_NEAR(4 * x(1) * x(1) * x(1), grad(1), 1e-10); +} diff --git a/test/unit/math/prim/functor/reduce_sum_util.hpp b/test/unit/math/prim/functor/reduce_sum_util.hpp index 18172224e22..bd028713e37 100644 --- a/test/unit/math/prim/functor/reduce_sum_util.hpp +++ b/test/unit/math/prim/functor/reduce_sum_util.hpp @@ -160,22 +160,22 @@ inline void test_slices(T1 result, T2&& vec_value, Args&&... args) { << "Failed for reduce_sum"; } -auto reduce_sum_static_int_sum_lpdf = [](auto&&... args) { +inline auto reduce_sum_static_int_sum_lpdf = [](auto&&... args) { return stan::math::reduce_sum_static(std::vector(2, 10.0), 1, get_new_msg(), args...); }; -auto reduce_sum_static_sum_lpdf = [](auto&& data, auto&&... args) { +inline auto reduce_sum_static_sum_lpdf = [](auto&& data, auto&&... args) { return stan::math::reduce_sum_static(data, 1, get_new_msg(), args...); }; -auto reduce_sum_int_sum_lpdf = [](auto&&... args) { +inline auto reduce_sum_int_sum_lpdf = [](auto&&... args) { return stan::math::reduce_sum(std::vector(2, 10.0), 1, get_new_msg(), args...); }; -auto reduce_sum_sum_lpdf = [](auto&& data, auto&&... args) { +inline auto reduce_sum_sum_lpdf = [](auto&& data, auto&&... args) { return stan::math::reduce_sum(data, 1, get_new_msg(), args...); }; diff --git a/test/unit/math/prim/functor/utils_threads.hpp b/test/unit/math/prim/functor/utils_threads.hpp index 77de14ffeb2..83187bfd439 100644 --- a/test/unit/math/prim/functor/utils_threads.hpp +++ b/test/unit/math/prim/functor/utils_threads.hpp @@ -1,9 +1,33 @@ #ifndef TEST_UNIT_MATH_PRIM_FUNCTOR_UTILS_THREADS_HPP #define TEST_UNIT_MATH_PRIM_FUNCTOR_UTILS_THREADS_HPP -#include +#include +#include #include +// Restore the caller's environment, including an originally absent variable. +class scoped_stan_num_threads { + const bool was_set_; + const std::string previous_; + + public: + scoped_stan_num_threads() + : was_set_(std::getenv("STAN_NUM_THREADS") != nullptr), + previous_(was_set_ ? std::getenv("STAN_NUM_THREADS") : "") {} + + ~scoped_stan_num_threads() { +#ifdef _WIN32 + _putenv_s("STAN_NUM_THREADS", previous_.c_str()); +#else + if (was_set_) { + setenv("STAN_NUM_THREADS", previous_.c_str(), 1); + } else { + unsetenv("STAN_NUM_THREADS"); + } +#endif + } +}; + // utility to set number of threads to use inline void set_n_threads(int num_threads) { static char env_string[256]; diff --git a/test/unit/math/prim/prob/bernoulli_test.cpp b/test/unit/math/prim/prob/bernoulli_test.cpp index a1e3b722d7a..6e364f76a30 100644 --- a/test/unit/math/prim/prob/bernoulli_test.cpp +++ b/test/unit/math/prim/prob/bernoulli_test.cpp @@ -59,3 +59,8 @@ TEST(ProbDistributionsBernoulli, chiSquareGoodnessFitTest) { assert_chi_squared(counts, expected, 1e-6); } + +TEST(ProbDistributionsBernoulliCDF, Values) { + EXPECT_FLOAT_EQ(1, stan::math::bernoulli_cdf(1, 0.57)); + EXPECT_FLOAT_EQ(1 - 0.57, stan::math::bernoulli_cdf(0, 0.57)); +} diff --git a/test/unit/math/prim/prob/beta_binomial_test.cpp b/test/unit/math/prim/prob/beta_binomial_test.cpp index 9adc12fe794..f776c6255ad 100644 --- a/test/unit/math/prim/prob/beta_binomial_test.cpp +++ b/test/unit/math/prim/prob/beta_binomial_test.cpp @@ -56,3 +56,8 @@ TEST(ProbDistributionBetaBinomial, error_check) { 4, 0.6, stan::math::positive_infinity(), rng), std::domain_error); } + +TEST(ProbDistributionsBetaBinomialCDF, Values) { + EXPECT_FLOAT_EQ(0.8868204314, + stan::math::beta_binomial_cdf(49, 100, 1.349, 3.938)); +} diff --git a/test/unit/math/prim/prob/binomial_test.cpp b/test/unit/math/prim/prob/binomial_test.cpp index c12507db534..67b202ef044 100644 --- a/test/unit/math/prim/prob/binomial_test.cpp +++ b/test/unit/math/prim/prob/binomial_test.cpp @@ -85,3 +85,18 @@ TEST(ProbDistributionsBinomial, chiSquareGoodnessFitTest) { EXPECT_TRUE(chi < quantile(complement(mydist, 1e-6))); } + +TEST(ProbDistributionsBinomialCDF, Values) { + EXPECT_FLOAT_EQ(0.042817421, stan::math::binomial_cdf(24, 54, 0.57)); + EXPECT_FLOAT_EQ(1.0 - 0.57, + stan::math::binomial_cdf( + 0, 1, 0.57)); // Consistency with expected Bernoulli + EXPECT_FLOAT_EQ(1.0, stan::math::binomial_cdf( + 1, 1, 0.57)); // Consistency with expected Bernoulli + EXPECT_FLOAT_EQ(stan::math::bernoulli_cdf(0, 0.57), + stan::math::binomial_cdf( + 0, 1, 0.57)); // Consistency with implemented Bernoulli + EXPECT_FLOAT_EQ(stan::math::bernoulli_cdf(1, 0.57), + stan::math::binomial_cdf( + 1, 1, 0.57)); // Consistency with implemented Bernoulli +} diff --git a/test/unit/math/prim/prob/cauchy_test.cpp b/test/unit/math/prim/prob/cauchy_test.cpp index 7409f7bcc73..d6216dfcf54 100644 --- a/test/unit/math/prim/prob/cauchy_test.cpp +++ b/test/unit/math/prim/prob/cauchy_test.cpp @@ -80,3 +80,10 @@ TEST(ProbDistributionsCauchy, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsCauchy, Cumulative) { + using stan::math::cauchy_cdf; + EXPECT_FLOAT_EQ(0.75, cauchy_cdf(1.0, 0.0, 1.0)); + EXPECT_FLOAT_EQ(0.187167, cauchy_cdf(-1.5, 0.0, 1.0)); + EXPECT_FLOAT_EQ(0.187167, cauchy_cdf(-2.5, -1.0, 1.0)); +} diff --git a/test/unit/math/prim/prob/double_exponential_test.cpp b/test/unit/math/prim/prob/double_exponential_test.cpp index 8e8d677e226..ee44c82c03a 100644 --- a/test/unit/math/prim/prob/double_exponential_test.cpp +++ b/test/unit/math/prim/prob/double_exponential_test.cpp @@ -81,3 +81,14 @@ TEST(ProbDistributionsDoubleExponential, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsDoubleExponential, Cumulative) { + EXPECT_FLOAT_EQ(0.5, stan::math::double_exponential_cdf(1.0, 1.0, 1.0)); + EXPECT_FLOAT_EQ(0.8160603, stan::math::double_exponential_cdf(2.0, 1.0, 1.0)); + EXPECT_FLOAT_EQ(0.003368973, + stan::math::double_exponential_cdf(-3.0, 2.0, 1.0)); + EXPECT_FLOAT_EQ(0.6967347, stan::math::double_exponential_cdf(1.0, 0.0, 2.0)); + EXPECT_FLOAT_EQ(0.2246645, stan::math::double_exponential_cdf(1.9, 2.3, 0.5)); + EXPECT_FLOAT_EQ(0.10094826, + stan::math::double_exponential_cdf(1.9, 2.3, 0.25)); +} diff --git a/test/unit/math/prim/prob/exponential_test.cpp b/test/unit/math/prim/prob/exponential_test.cpp index c581dd64508..b977e99327c 100644 --- a/test/unit/math/prim/prob/exponential_test.cpp +++ b/test/unit/math/prim/prob/exponential_test.cpp @@ -74,3 +74,19 @@ TEST(ProbDistributionsExponential, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsExponential, Cumulative) { + using stan::math::exponential_cdf; + using std::numeric_limits; + EXPECT_FLOAT_EQ(0.95021293, exponential_cdf(2.0, 1.5)); + EXPECT_FLOAT_EQ(1.0, exponential_cdf(15.0, 3.9)); + EXPECT_FLOAT_EQ(0.62280765, exponential_cdf(0.25, 3.9)); + + // ?? + // EXPECT_FLOAT_EQ(0.0, + // exponential_cdf(-numeric_limits::infinity(), + // 1.5)); + EXPECT_FLOAT_EQ(0.0, exponential_cdf(0.0, 1.5)); + EXPECT_FLOAT_EQ(1.0, + exponential_cdf(numeric_limits::infinity(), 1.5)); +} diff --git a/test/unit/math/prim/prob/frechet_test.cpp b/test/unit/math/prim/prob/frechet_test.cpp index 8dd29a48bfb..c091987a2b6 100644 --- a/test/unit/math/prim/prob/frechet_test.cpp +++ b/test/unit/math/prim/prob/frechet_test.cpp @@ -86,3 +86,14 @@ TEST(ProbDistributionsFrechet, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsFrechet, Cumulative) { + using stan::math::frechet_cdf; + using std::numeric_limits; + EXPECT_FLOAT_EQ(0.6065306597, frechet_cdf(2.0, 1.0, 1.0)); + EXPECT_FLOAT_EQ(2.744338423e-5, frechet_cdf(0.8, 2.9, 1.8)); + EXPECT_FLOAT_EQ(0.0, frechet_cdf(0.25, 3.9, 1.7)); + + EXPECT_FLOAT_EQ(1.0, + frechet_cdf(numeric_limits::infinity(), 1.0, 1.0)); +} diff --git a/test/unit/math/prim/prob/inv_gamma_test.cpp b/test/unit/math/prim/prob/inv_gamma_test.cpp index 4687a03df55..9d5ceb632de 100644 --- a/test/unit/math/prim/prob/inv_gamma_test.cpp +++ b/test/unit/math/prim/prob/inv_gamma_test.cpp @@ -81,3 +81,7 @@ TEST(ProbDistributionsInvGamma, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsInvGammaCdf, Values) { + EXPECT_FLOAT_EQ(0.557873, stan::math::inv_gamma_cdf(4.39, 1.349, 3.938)); +} diff --git a/test/unit/math/prim/prob/logistic_test.cpp b/test/unit/math/prim/prob/logistic_test.cpp index 4af72d4c321..73893d1a2db 100644 --- a/test/unit/math/prim/prob/logistic_test.cpp +++ b/test/unit/math/prim/prob/logistic_test.cpp @@ -80,3 +80,7 @@ TEST(ProbDistributionsLogistic, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsLogisticCDF, Values) { + EXPECT_FLOAT_EQ(0.047191944, stan::math::logistic_cdf(-3.45, 5.235, 2.89)); +} diff --git a/test/unit/math/prim/prob/lognormal_test.cpp b/test/unit/math/prim/prob/lognormal_test.cpp index bfc16aaa881..55b5f12a175 100644 --- a/test/unit/math/prim/prob/lognormal_test.cpp +++ b/test/unit/math/prim/prob/lognormal_test.cpp @@ -80,3 +80,17 @@ TEST(ProbDistributionsLogNormalPrim, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsLognormal, Cumulative) { + using stan::math::lognormal_cdf; + EXPECT_FLOAT_EQ(0.4687341, lognormal_cdf(1.2, 0.3, 1.5)); + EXPECT_FLOAT_EQ(0.2835506, lognormal_cdf(12.0, 3.0, 0.9)); + + double pos_inf = std::numeric_limits::infinity(); + + // ?? double neg_inf = -pos_inf; + // ?? EXPECT_FLOAT_EQ(0.0, lognormal_cdf(neg_inf, 0.0, 1.0)); + + EXPECT_FLOAT_EQ(0.0, lognormal_cdf(0.0, 0.0, 1.0)); + EXPECT_FLOAT_EQ(1.0, lognormal_cdf(pos_inf, 0.0, 1.0)); +} diff --git a/test/unit/math/prim/prob/pareto_test.cpp b/test/unit/math/prim/prob/pareto_test.cpp index df1c57c64ca..9a2c317f278 100644 --- a/test/unit/math/prim/prob/pareto_test.cpp +++ b/test/unit/math/prim/prob/pareto_test.cpp @@ -80,3 +80,7 @@ TEST(ProbDistributionsPareto, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsParetoCDF, Values) { + EXPECT_FLOAT_EQ(0.60434447, stan::math::pareto_cdf(3.45, 2.89, 5.235)); +} diff --git a/test/unit/math/prim/prob/student_t_qf_test.cpp b/test/unit/math/prim/prob/student_t_qf_test.cpp index 4694264870d..999ef198f91 100644 --- a/test/unit/math/prim/prob/student_t_qf_test.cpp +++ b/test/unit/math/prim/prob/student_t_qf_test.cpp @@ -39,7 +39,6 @@ TEST(MathFunctions, student_t_qf_vec) { TEST(MathFunctions, student_t_qf_inf) { using stan::math::student_t_qf; - long double log_p = std::numeric_limits::min(); const double inf = std::numeric_limits::infinity(); EXPECT_EQ(student_t_qf(0, 2, 0, 1), -inf); EXPECT_EQ(student_t_qf(1, 2, 0, 1), inf); diff --git a/test/unit/math/prim/prob/weibull_test.cpp b/test/unit/math/prim/prob/weibull_test.cpp index 19308aea778..554be30b5c5 100644 --- a/test/unit/math/prim/prob/weibull_test.cpp +++ b/test/unit/math/prim/prob/weibull_test.cpp @@ -78,3 +78,18 @@ TEST(ProbDistributionsWeibull, chiSquareGoodnessFitTest) { // Assert that they match assert_matches_quantiles(samples, quantiles, 1e-6); } + +TEST(ProbDistributionsWeibull, Cumulative) { + using stan::math::weibull_cdf; + using std::numeric_limits; + EXPECT_FLOAT_EQ(0.86466472, weibull_cdf(2.0, 1.0, 1.0)); + EXPECT_FLOAT_EQ(0.0032585711, weibull_cdf(0.25, 2.9, 1.8)); + EXPECT_FLOAT_EQ(1.0, weibull_cdf(3.9, 1.7, 0.25)); + + // EXPECT_FLOAT_EQ(0.0, + // weibull_cdf(-numeric_limits::infinity(), + // 1.0, 1.0)); + EXPECT_FLOAT_EQ(0.0, weibull_cdf(0.0, 1.0, 1.0)); + EXPECT_FLOAT_EQ(1.0, + weibull_cdf(numeric_limits::infinity(), 1.0, 1.0)); +} diff --git a/test/unit/math/rev/core/gradable.hpp b/test/unit/math/rev/core/gradable.hpp index a4c198d1ed6..c7185ae988c 100644 --- a/test/unit/math/rev/core/gradable.hpp +++ b/test/unit/math/rev/core/gradable.hpp @@ -25,7 +25,7 @@ struct gradable { double adj() { return f_.adj(); } }; -gradable setup_quad_form() { +inline gradable setup_quad_form() { using Eigen::Dynamic; using Eigen::Matrix; using stan::math::quad_form; @@ -52,7 +52,7 @@ gradable setup_quad_form() { return gradable(x, f, g_expected); } -gradable setup_simple() { +inline gradable setup_simple() { stan::math::var a = 3; stan::math::var b = 7; std::vector x; diff --git a/test/unit/math/rev/core/profiling_threading_test.cpp b/test/unit/math/rev/core/profiling_threading_test.cpp index b3c2c78fa2b..2e15bbf9779 100644 --- a/test/unit/math/rev/core/profiling_threading_test.cpp +++ b/test/unit/math/rev/core/profiling_threading_test.cpp @@ -30,7 +30,6 @@ struct grouped_count_lpdf { TEST_F(AgradRev, Profiling_profile_threading) { using stan::math::var; using stan::math::test::get_new_msg; - double lambda_d = 10.0; const std::size_t groups = 10; const std::size_t elems_per_group = 1000; const std::size_t elems = groups * elems_per_group; @@ -47,7 +46,6 @@ TEST_F(AgradRev, Profiling_profile_threading) { for (std::size_t i = 0; i != groups; ++i) vlambda_v[i] = i + 0.2; - var lambda_v = vlambda_v[0]; var poisson_lpdf = stan::math::reduce_sum>( diff --git a/test/unit/math/rev/fun/util.hpp b/test/unit/math/rev/fun/util.hpp index 7fdca697ef7..22d51a4aaf3 100644 --- a/test/unit/math/rev/fun/util.hpp +++ b/test/unit/math/rev/fun/util.hpp @@ -26,7 +26,7 @@ struct third_order_mixed { } }; -Eigen::Matrix third_order_mixed_hess( +inline Eigen::Matrix third_order_mixed_hess( const Eigen::Matrix& inp_vec) { Eigen::Matrix hess; @@ -46,7 +46,7 @@ Eigen::Matrix third_order_mixed_hess( return hess; } -Eigen::Matrix norm_hess( +inline Eigen::Matrix norm_hess( const Eigen::Matrix& inp_vec) { using Eigen::Dynamic; using Eigen::Matrix; @@ -62,7 +62,7 @@ Eigen::Matrix norm_hess( return hess; } -std::vector> +inline std::vector> third_order_mixed_grad_hess( const Eigen::Matrix& inp_vec) { using Eigen::Dynamic; @@ -91,7 +91,7 @@ third_order_mixed_grad_hess( return grad_hess_ret; } -std::vector> +inline std::vector> norm_grad_hess(const Eigen::Matrix& inp_vec) { using Eigen::Dynamic; using Eigen::Matrix; diff --git a/test/unit/math/rev/functor/algebra_solver_fp_test.cpp b/test/unit/math/rev/functor/algebra_solver_fp_test.cpp index fd31b9da8d4..079026ace2c 100644 --- a/test/unit/math/rev/functor/algebra_solver_fp_test.cpp +++ b/test/unit/math/rev/functor/algebra_solver_fp_test.cpp @@ -471,7 +471,10 @@ TEST_F(FP_degenerated_func_test, algebra_solver_fp) { auto f_fd = fd_functor(i); finite_diff_gradient_auto(f_fd, y, fx, grad_fx); for (int j = 0; j < M; ++j) { - EXPECT_FLOAT_EQ(grad_fx(j), yp(j).adj()); + // This solution is (y(0), y(0)), independent of y(1). + EXPECT_NEAR(j == 0 ? 1.0 : 0.0, yp(j).adj(), 1e-12); + // Differencing amplifies the numerical solver's convergence error. + EXPECT_NEAR(grad_fx(j), yp(j).adj(), 1e-8); } } } diff --git a/test/unit/math/rev/functor/cos_ode_typed_test.cpp b/test/unit/math/rev/functor/cos_ode_typed_test.cpp index 327842289ee..6e0975b91ae 100644 --- a/test/unit/math/rev/functor/cos_ode_typed_test.cpp +++ b/test/unit/math/rev/functor/cos_ode_typed_test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include /** * @@ -58,20 +59,20 @@ TYPED_TEST_P(cos_arg_test, tol_error) { } TYPED_TEST_P(cos_arg_test, value) { this->test_value(); } TYPED_TEST_P(cos_arg_test, grad) { - this->test_grad_t0(); - this->test_grad_ts(); - this->test_grad_ts_repeat(); - this->test_scalar_arg(); - this->test_std_vector_arg(); - this->test_vector_arg(); - this->test_row_vector_arg(); - this->test_matrix_arg(); - this->test_scalar_std_vector_args(); - this->test_std_vector_std_vector_args(); - this->test_std_vector_vector_args(); - this->test_std_vector_row_vector_args(); - this->test_std_vector_matrix_args(); - this->test_arg_combos_test(); + EXPECT_NO_THROW(this->test_grad_t0()); + EXPECT_NO_THROW(this->test_grad_ts()); + EXPECT_NO_THROW(this->test_grad_ts_repeat()); + EXPECT_NO_THROW(this->test_scalar_arg()); + EXPECT_NO_THROW(this->test_std_vector_arg()); + EXPECT_NO_THROW(this->test_vector_arg()); + EXPECT_NO_THROW(this->test_row_vector_arg()); + EXPECT_NO_THROW(this->test_matrix_arg()); + EXPECT_NO_THROW(this->test_scalar_std_vector_args()); + EXPECT_NO_THROW(this->test_std_vector_std_vector_args()); + EXPECT_NO_THROW(this->test_std_vector_vector_args()); + EXPECT_NO_THROW(this->test_std_vector_row_vector_args()); + EXPECT_NO_THROW(this->test_std_vector_matrix_args()); + EXPECT_NO_THROW(this->test_arg_combos_test()); } TYPED_TEST_P(cos_arg_test, tol_grad) { this->test_tol_int_t0(); @@ -96,3 +97,31 @@ REGISTER_TYPED_TEST_SUITE_P(cos_arg_test, y0_error, t0_error, ts_error, one_arg_error, two_arg_error, rhs_wrong_size_error, error_name, tol_error, value, grad, tol_grad); INSTANTIATE_TYPED_TEST_SUITE_P(StanOde, cos_arg_test, cos_arg_test_types); + +TEST_F(AgradRev, quadrature_buffer_reuse) { + // Repeated solves and reverse passes reuse CVODES' quadrature work buffers. + // Every parameter has the same analytic derivative through their sum. + const std::vector times{0.45, 1.1}; + constexpr double rate = 1.5; + for (int iteration = 0; iteration < 10; ++iteration) { + SCOPED_TRACE(iteration); + stan::math::nested_rev_autodiff nested; + const Eigen::VectorXd initial = Eigen::VectorXd::Zero(1); + std::vector parameters{0.25, 0.5, 0.75}; + ode_adjoint_functor solver; + auto output = solver(stan::test::CosArg1(), initial, 0.0, times, nullptr, + parameters); + for (int index : {0, 1, 0, 1}) { + SCOPED_TRACE(index); + nested.set_zero_all_adjoints(); + ASSERT_NO_THROW(output[index](0).grad()); + const double phase = rate * times[index]; + const double expected + = (phase * std::cos(phase) - std::sin(phase)) / (rate * rate); + EXPECT_NEAR(output[index](0).val(), std::sin(phase) / rate, 1e-8); + for (const auto& parameter : parameters) { + EXPECT_NEAR(parameter.adj(), expected, 1e-8); + } + } + } +} diff --git a/test/unit/math/rev/functor/map_rect_concurrent_prim_test.cpp b/test/unit/math/rev/functor/map_rect_concurrent_prim_test.cpp index b7fe369ae98..fcf2f8378c6 100644 --- a/test/unit/math/rev/functor/map_rect_concurrent_prim_test.cpp +++ b/test/unit/math/rev/functor/map_rect_concurrent_prim_test.cpp @@ -19,6 +19,7 @@ STAN_REGISTER_MAP_RECT(0, hard_work) STAN_REGISTER_MAP_RECT(1, hard_work) struct map_rect_con_prim : public ::testing::Test { + scoped_stan_num_threads restore_environment; Eigen::VectorXd shared_params_d; std::vector job_params_d; std::vector > x_r; diff --git a/test/unit/math/rev/functor/map_rect_concurrent_test.cpp b/test/unit/math/rev/functor/map_rect_concurrent_test.cpp index 65b0dec034a..4617f12d798 100644 --- a/test/unit/math/rev/functor/map_rect_concurrent_test.cpp +++ b/test/unit/math/rev/functor/map_rect_concurrent_test.cpp @@ -18,6 +18,7 @@ STAN_REGISTER_MAP_RECT(0, hard_work) struct map_rect_con : public ::testing::Test { + scoped_stan_num_threads restore_environment; Eigen::VectorXd shared_params_d; std::vector job_params_d; std::vector > x_r; diff --git a/test/unit/math/rev/functor/map_rect_concurrent_threads_test.cpp b/test/unit/math/rev/functor/map_rect_concurrent_threads_test.cpp index 40b20451e73..b733304050b 100644 --- a/test/unit/math/rev/functor/map_rect_concurrent_threads_test.cpp +++ b/test/unit/math/rev/functor/map_rect_concurrent_threads_test.cpp @@ -46,6 +46,7 @@ inline void setup_job(int N, Eigen::VectorXd& shared_params_d, } struct map_rect_con_threads : public ::testing::Test { + scoped_stan_num_threads restore_environment; Eigen::VectorXd shared_params_d; std::vector job_params_d; std::vector > x_r; diff --git a/test/unit/math/rev/functor/map_rect_mpi_prim_test.cpp b/test/unit/math/rev/functor/map_rect_mpi_prim_test.cpp index 0296aa087c2..492da69eaa8 100644 --- a/test/unit/math/rev/functor/map_rect_mpi_prim_test.cpp +++ b/test/unit/math/rev/functor/map_rect_mpi_prim_test.cpp @@ -13,12 +13,13 @@ #include #include -STAN_REGISTER_MAP_RECT(0, hard_work) -STAN_REGISTER_MAP_RECT(1, hard_work) -STAN_REGISTER_MAP_RECT(2, faulty_functor) -STAN_REGISTER_MAP_RECT(3, faulty_functor) +// Keep call IDs unique across the grouped functor executable. +STAN_REGISTER_MAP_RECT(10, hard_work) +STAN_REGISTER_MAP_RECT(11, hard_work) +STAN_REGISTER_MAP_RECT(12, faulty_functor) +STAN_REGISTER_MAP_RECT(13, faulty_functor) -struct MpiJob : public ::testing::Test { +struct MpiPrimJob : public ::testing::Test { Eigen::VectorXd shared_params_d; std::vector job_params_d; const std::size_t N = 10; @@ -64,11 +65,11 @@ struct MpiJobSmallWorld : public ::testing::Test { } }; -TEST_F(MpiJob, hard_work_dd) { - Eigen::VectorXd result_mpi = stan::math::map_rect<0, hard_work>( +TEST_F(MpiPrimJob, hard_work_dd) { + Eigen::VectorXd result_mpi = stan::math::map_rect<10, hard_work>( shared_params_d, job_params_d, x_r, x_i); Eigen::VectorXd result_concurrent - = stan::math::internal::map_rect_concurrent<0, hard_work>( + = stan::math::internal::map_rect_concurrent<10, hard_work>( shared_params_d, job_params_d, x_r, x_i); EXPECT_EQ(result_mpi.rows(), result_concurrent.rows()); @@ -80,10 +81,10 @@ TEST_F(MpiJob, hard_work_dd) { // execute fewer jobs than the cluster size TEST_F(MpiJobSmallWorld, hard_work_dd) { - Eigen::VectorXd result_mpi = stan::math::map_rect<1, hard_work>( + Eigen::VectorXd result_mpi = stan::math::map_rect<11, hard_work>( shared_params_d, job_params_d, x_r, x_i); Eigen::VectorXd result_concurrent - = stan::math::internal::map_rect_concurrent<1, hard_work>( + = stan::math::internal::map_rect_concurrent<11, hard_work>( shared_params_d, job_params_d, x_r, x_i); EXPECT_EQ(result_mpi.rows(), result_concurrent.rows()); @@ -93,10 +94,10 @@ TEST_F(MpiJobSmallWorld, hard_work_dd) { } } -TEST_F(MpiJob, always_faulty_functor) { +TEST_F(MpiPrimJob, always_faulty_functor) { Eigen::VectorXd result; - EXPECT_NO_THROW((result = stan::math::map_rect<2, faulty_functor>( + EXPECT_NO_THROW((result = stan::math::map_rect<12, faulty_functor>( shared_params_d, job_params_d, x_r, x_i))); // faulty functor throws on theta(0) being -1.0 @@ -106,16 +107,16 @@ TEST_F(MpiJob, always_faulty_functor) { // upon the second evaluation throwing is handled internally // different - EXPECT_THROW_MSG((result = stan::math::map_rect<2, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<12, faulty_functor>( shared_params_d, job_params_d, x_r, x_i)), std::domain_error, "Error during MPI evaluation."); // throwing on the very first evaluation - EXPECT_THROW_MSG((result = stan::math::map_rect<3, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<13, faulty_functor>( shared_params_d, job_params_d, x_r, x_i)), std::domain_error, "MPI error on first evaluation."); // things don't get better if we repeat - EXPECT_THROW_MSG((result = stan::math::map_rect<3, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<13, faulty_functor>( shared_params_d, job_params_d, x_r, x_i)), std::domain_error, "MPI error on first evaluation."); } diff --git a/test/unit/math/rev/functor/map_rect_mpi_test.cpp b/test/unit/math/rev/functor/map_rect_mpi_test.cpp index 40a2851ef51..facba63c504 100644 --- a/test/unit/math/rev/functor/map_rect_mpi_test.cpp +++ b/test/unit/math/rev/functor/map_rect_mpi_test.cpp @@ -13,9 +13,10 @@ #include #include -STAN_REGISTER_MAP_RECT(0, hard_work) -STAN_REGISTER_MAP_RECT(1, faulty_functor) -STAN_REGISTER_MAP_RECT(2, faulty_functor) +// Keep call IDs unique across the grouped functor executable. +STAN_REGISTER_MAP_RECT(20, hard_work) +STAN_REGISTER_MAP_RECT(21, faulty_functor) +STAN_REGISTER_MAP_RECT(22, faulty_functor) struct MpiJob : public ::testing::Test { stan::math::vector_d shared_params_d; @@ -67,11 +68,11 @@ TEST_F(MpiJob, hard_work_vv) { job_params_v2_vec.push_back(stan::math::to_array_1d(job_params_v2[i])); } - stan::math::vector_v result_mpi = stan::math::map_rect<0, hard_work>( + stan::math::vector_v result_mpi = stan::math::map_rect<20, hard_work>( shared_params_v, job_params_v, x_r, x_i, 0); stan::math::vector_v result_concurrent - = stan::math::internal::map_rect_concurrent<0, hard_work>( + = stan::math::internal::map_rect_concurrent<20, hard_work>( shared_params_v2, job_params_v2, x_r, x_i, 0); std::vector z_grad1; @@ -111,7 +112,7 @@ TEST_F(MpiJob, hard_work_vv) { TEST_F(MpiJob, always_faulty_functor_vv) { stan::math::vector_v result; - EXPECT_NO_THROW((result = stan::math::map_rect<1, faulty_functor>( + EXPECT_NO_THROW((result = stan::math::map_rect<21, faulty_functor>( shared_params_v, job_params_v, x_r, x_i))); // faulty functor throws on theta(0) being -1.0 @@ -120,12 +121,12 @@ TEST_F(MpiJob, always_faulty_functor_vv) { job_params_v[0](0) = -1; // upon the second evaluation throwing is handled internally different - EXPECT_THROW_MSG((result = stan::math::map_rect<1, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<21, faulty_functor>( shared_params_v, job_params_v, x_r, x_i)), std::domain_error, "Error during MPI evaluation."); // throwing on the very first evaluation - EXPECT_THROW_MSG((result = stan::math::map_rect<2, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<22, faulty_functor>( shared_params_v, job_params_v, x_r, x_i)), std::domain_error, "MPI error on first evaluation."); } @@ -133,7 +134,7 @@ TEST_F(MpiJob, always_faulty_functor_vv) { TEST_F(MpiJob, always_faulty_functor_vd) { stan::math::vector_v result; - EXPECT_NO_THROW((result = stan::math::map_rect<1, faulty_functor>( + EXPECT_NO_THROW((result = stan::math::map_rect<21, faulty_functor>( shared_params_v, job_params_d, x_r, x_i))); // faulty functor throws on theta(0) being -1.0 @@ -142,12 +143,12 @@ TEST_F(MpiJob, always_faulty_functor_vd) { job_params_d[0](0) = -1; // upon the second evaluation throwing is handled internally different - EXPECT_THROW_MSG((result = stan::math::map_rect<1, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<21, faulty_functor>( shared_params_v, job_params_d, x_r, x_i)), std::domain_error, "Error during MPI evaluation."); // throwing on the very first evaluation - EXPECT_THROW_MSG((result = stan::math::map_rect<2, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<22, faulty_functor>( shared_params_v, job_params_d, x_r, x_i)), std::domain_error, "MPI error on first evaluation."); } @@ -155,7 +156,7 @@ TEST_F(MpiJob, always_faulty_functor_vd) { TEST_F(MpiJob, always_faulty_functor_dv) { stan::math::vector_v result; - EXPECT_NO_THROW((result = stan::math::map_rect<1, faulty_functor>( + EXPECT_NO_THROW((result = stan::math::map_rect<21, faulty_functor>( shared_params_d, job_params_v, x_r, x_i))); // faulty functor throws on theta(0) being -1.0 @@ -164,12 +165,12 @@ TEST_F(MpiJob, always_faulty_functor_dv) { job_params_v[0](0) = -1; // upon the second evaluation throwing is handled internally different - EXPECT_THROW_MSG((result = stan::math::map_rect<1, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<21, faulty_functor>( shared_params_d, job_params_v, x_r, x_i)), std::domain_error, "Error during MPI evaluation."); // throwing on the very first evaluation - EXPECT_THROW_MSG((result = stan::math::map_rect<2, faulty_functor>( + EXPECT_THROW_MSG((result = stan::math::map_rect<22, faulty_functor>( shared_params_d, job_params_v, x_r, x_i)), std::domain_error, "MPI error on first evaluation."); } diff --git a/test/unit/math/rev/functor/map_rect_prim_test.cpp b/test/unit/math/rev/functor/map_rect_prim_test.cpp index fbdd38ae275..6b935c5a1b3 100644 --- a/test/unit/math/rev/functor/map_rect_prim_test.cpp +++ b/test/unit/math/rev/functor/map_rect_prim_test.cpp @@ -10,12 +10,13 @@ // the tests here check that map_rect refuses mal-formatted input as // such it does not matter if STAN_MPI is defined or not -STAN_REGISTER_MAP_RECT(0, hard_work) -STAN_REGISTER_MAP_RECT(1, hard_work) -STAN_REGISTER_MAP_RECT(2, hard_work) -STAN_REGISTER_MAP_RECT(3, hard_work) -STAN_REGISTER_MAP_RECT(4, hard_work) -STAN_REGISTER_MAP_RECT(5, hard_work) +// Keep call IDs unique across the grouped functor executable. +STAN_REGISTER_MAP_RECT(30, hard_work) +STAN_REGISTER_MAP_RECT(31, hard_work) +STAN_REGISTER_MAP_RECT(32, hard_work) +STAN_REGISTER_MAP_RECT(33, hard_work) +STAN_REGISTER_MAP_RECT(34, hard_work) +STAN_REGISTER_MAP_RECT(35, hard_work) struct map_rect_prim : public ::testing::Test { Eigen::VectorXd shared_params_d; @@ -44,54 +45,54 @@ TEST_F(map_rect_prim, no_job_input_ok_dd) { x_i.resize(0); x_r.resize(0); - EXPECT_NO_THROW((stan::math::map_rect<0, hard_work>(shared_params_d, - job_params_d, x_r, x_i))); + EXPECT_NO_THROW((stan::math::map_rect<30, hard_work>( + shared_params_d, job_params_d, x_r, x_i))); } TEST_F(map_rect_prim, size_mismatch_job_params_dd) { job_params_d.pop_back(); - EXPECT_THROW((stan::math::map_rect<1, hard_work>(shared_params_d, - job_params_d, x_r, x_i)), + EXPECT_THROW((stan::math::map_rect<31, hard_work>(shared_params_d, + job_params_d, x_r, x_i)), std::invalid_argument); } TEST_F(map_rect_prim, size_mismatch_real_data_dd) { x_r.pop_back(); - EXPECT_THROW((stan::math::map_rect<2, hard_work>(shared_params_d, - job_params_d, x_r, x_i)), + EXPECT_THROW((stan::math::map_rect<32, hard_work>(shared_params_d, + job_params_d, x_r, x_i)), std::invalid_argument); } TEST_F(map_rect_prim, size_mismatch_int_data_dd) { x_i.pop_back(); - EXPECT_THROW((stan::math::map_rect<3, hard_work>(shared_params_d, - job_params_d, x_r, x_i)), + EXPECT_THROW((stan::math::map_rect<33, hard_work>(shared_params_d, + job_params_d, x_r, x_i)), std::invalid_argument); } TEST_F(map_rect_prim, wrong_size_job_params_dd) { job_params_d[1].resize(5); - EXPECT_THROW((stan::math::map_rect<1, hard_work>(shared_params_d, - job_params_d, x_r, x_i)), + EXPECT_THROW((stan::math::map_rect<31, hard_work>(shared_params_d, + job_params_d, x_r, x_i)), std::invalid_argument); } TEST_F(map_rect_prim, wrong_size_real_data_dd) { x_r[1] = std::vector(5, 1); - EXPECT_THROW((stan::math::map_rect<4, hard_work>(shared_params_d, - job_params_d, x_r, x_i)), + EXPECT_THROW((stan::math::map_rect<34, hard_work>(shared_params_d, + job_params_d, x_r, x_i)), std::invalid_argument); } TEST_F(map_rect_prim, wrong_size_int_data_dd) { x_i[1] = std::vector(5, 1); - EXPECT_THROW((stan::math::map_rect<5, hard_work>(shared_params_d, - job_params_d, x_r, x_i)), + EXPECT_THROW((stan::math::map_rect<35, hard_work>(shared_params_d, + job_params_d, x_r, x_i)), std::invalid_argument); } diff --git a/test/unit/math/rev/functor/util_algebra_solver.hpp b/test/unit/math/rev/functor/util_algebra_solver.hpp index 6035ca4d0d7..0ac3865dc77 100644 --- a/test/unit/math/rev/functor/util_algebra_solver.hpp +++ b/test/unit/math/rev/functor/util_algebra_solver.hpp @@ -518,7 +518,7 @@ inline void max_num_steps_test(Eigen::Matrix& y, std::domain_error); } -Eigen::Matrix variadic_eq_impl_test( +inline Eigen::Matrix variadic_eq_impl_test( const Eigen::Matrix A, const stan::math::var& y_1, const stan::math::var& y_2, const stan::math::var& y_3, const int i, bool is_newton = false, diff --git a/test/unit/math/rev/prob/bernoulli_logit_glm_lpmf_test.cpp b/test/unit/math/rev/prob/bernoulli_logit_glm_lpmf_test.cpp index cb8c2a16395..9a89fc2e1ed 100644 --- a/test/unit/math/rev/prob/bernoulli_logit_glm_lpmf_test.cpp +++ b/test/unit/math/rev/prob/bernoulli_logit_glm_lpmf_test.cpp @@ -573,6 +573,8 @@ TYPED_TEST(ProbDistributionsBernoulliLogitGLM, for (int n = 0; n < N; n++) { yw2[n] = -(Eigen::Matrix::Random(1, 1)[0] % 2); } + // Random draws can all be zero, so guarantee an out-of-domain label. + yw2[N - 1] = -1; Eigen::Matrix yw3(N, 1); for (int n = 0; n < N; n++) { yw3[n] = 42 + (Eigen::Matrix::Random(1, 1)[0] % 2); @@ -600,7 +602,8 @@ TYPED_TEST(ProbDistributionsBernoulliLogitGLM, EXPECT_THROW(stan::math::bernoulli_logit_glm_lpmf(yw1, x, alpha, beta), std::invalid_argument); EXPECT_THROW(stan::math::bernoulli_logit_glm_lpmf(yw2, x, alpha, beta), - std::domain_error); + std::domain_error) + << "Negative-label fixture: " << yw2.transpose(); EXPECT_THROW(stan::math::bernoulli_logit_glm_lpmf(yw3, x, alpha, beta), std::domain_error); EXPECT_THROW(stan::math::bernoulli_logit_glm_lpmf(y, xw1, alpha, beta), diff --git a/test/unit/math/test_ad.hpp b/test/unit/math/test_ad.hpp index 6336f1b03d0..78c8aecf15a 100644 --- a/test/unit/math/test_ad.hpp +++ b/test/unit/math/test_ad.hpp @@ -59,7 +59,7 @@ auto eval(const std::complex& x) { * @param[in] x value * @return value */ -auto eval(const stan::math::var& x) { return x; } +inline auto eval(const stan::math::var& x) { return x; } /** * Evaluates all matrix expression templates, which is a no-op for @@ -974,7 +974,7 @@ inline void expect_ad_vvv(const ad_tolerances& tols, const F& f, int x1, int x2, * * @return non-zero arguments */ -const std::vector& common_nonzero_args() { +inline const std::vector& common_nonzero_args() { static const std::vector common_nz_args{ -1.3, 0.49, diff --git a/test/unit/multiple_translation_units1.cpp b/test/unit/multiple_translation_units1.cpp index 3e9a69ef42d..c1904fc32bd 100644 --- a/test/unit/multiple_translation_units1.cpp +++ b/test/unit/multiple_translation_units1.cpp @@ -1,3 +1,11 @@ #include inline stan::math::var function1() { return 0; } + +#ifdef STAN_OPENCL +double opencl_cached_kernel1() { + stan::math::matrix_cl matrix = stan::math::constant(1.0, 2, 2); + stan::math::matrix_cl result = matrix * matrix; + return stan::math::from_matrix_cl(result).sum(); +} +#endif diff --git a/test/unit/multiple_translation_units2.cpp b/test/unit/multiple_translation_units2.cpp index 8958b90aaa0..dd8135795d3 100644 --- a/test/unit/multiple_translation_units2.cpp +++ b/test/unit/multiple_translation_units2.cpp @@ -1,3 +1,16 @@ #include inline stan::math::var function2() { return 0; } + +#ifdef STAN_OPENCL +double opencl_cached_kernel2() { + stan::math::matrix_cl matrix = stan::math::constant(2.0, 2, 2); + stan::math::matrix_cl result = matrix + matrix; + return stan::math::from_matrix_cl(result).sum(); +} + +void select_opencl_device2() { + stan::math::opencl_context.select_device(OPENCL_PLATFORM_ID, + OPENCL_DEVICE_ID); +} +#endif diff --git a/test/unit/multiple_translation_units_test.cpp b/test/unit/multiple_translation_units_test.cpp index 8894851aba2..4178babaad3 100644 --- a/test/unit/multiple_translation_units_test.cpp +++ b/test/unit/multiple_translation_units_test.cpp @@ -4,3 +4,20 @@ TEST(multiple_translation_units, compile) { SUCCEED() << "this test compiling indicates that compiling the math library " << "public umbrella headers with multiple translation units is ok."; } + +#ifdef STAN_OPENCL +double opencl_cached_kernel1(); +double opencl_cached_kernel2(); +void select_opencl_device2(); + +TEST(multiple_translation_units, opencl_cached_kernels_after_device_switch) { + EXPECT_DOUBLE_EQ(8.0, opencl_cached_kernel1()); + EXPECT_DOUBLE_EQ(16.0, opencl_cached_kernel2()); + + // Switching in another translation unit must invalidate both kernel caches. + select_opencl_device2(); + + EXPECT_DOUBLE_EQ(8.0, opencl_cached_kernel1()); + EXPECT_DOUBLE_EQ(16.0, opencl_cached_kernel2()); +} +#endif diff --git a/test/unit/util.hpp b/test/unit/util.hpp index 6d13989d579..a0343c1a111 100644 --- a/test/unit/util.hpp +++ b/test/unit/util.hpp @@ -167,7 +167,7 @@ * @param s string to match count occurrences * @return number of found occurrences of target in s */ -int count_matches(const std::string& target, const std::string& s) { +inline int count_matches(const std::string& target, const std::string& s) { if (target.size() == 0) return -1; // error int count = 0; @@ -212,7 +212,7 @@ int count_matches(const std::string& target, const std::string& s) { namespace stan { namespace test { -auto make_sparse_matrix_random(int rows, int cols) { +inline auto make_sparse_matrix_random(int rows, int cols) { using eigen_triplet = Eigen::Triplet; boost::mt19937 gen; boost::random::uniform_real_distribution dist(0.0, 1.0); diff --git a/test/varmat_compatibility.py b/test/varmat_compatibility.py index cfd4100430e..62d693db31b 100755 --- a/test/varmat_compatibility.py +++ b/test/varmat_compatibility.py @@ -4,6 +4,8 @@ import json from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser import os +from pathlib import Path +import shutil import queue as Queue import subprocess import re @@ -11,7 +13,7 @@ import tempfile import threading -from sig_utils import make, handle_function_list, get_signatures +from sig_utils import handle_function_list, get_signatures from signature_parser import SignatureParser from code_generator import CodeGenerator @@ -19,10 +21,39 @@ TEST_FOLDER = os.path.abspath(os.path.join(HERE, "..", "test")) sys.path.append(TEST_FOLDER) WORKING_FOLDER = "test/varmat-compatibility" - +_compile_worker = threading.local() + + +def compile_signature(cpp_path): + if not hasattr(_compile_worker, "directory"): + _compile_worker.directory = tempfile.TemporaryDirectory(prefix="cmake_", dir=WORKING_FOLDER) + directory = Path(_compile_worker.directory.name).resolve() + _compile_worker.source = directory / "check.cpp" + _compile_worker.build = directory / "build" + _compile_worker.source.write_text("\n") + successful, stdout, stderr = run_command([ + "cmake", "-S", str(Path(HERE).parent), "-B", str(_compile_worker.build), + "-DBUILD_TESTING=OFF", "-DSTAN_TEST_OPTIMIZATION=0", + "-DSTAN_COMPILE_TEST_SOURCE=" + str(_compile_worker.source)]) + if successful: + successful, stdout, stderr = run_command([ + "cmake", "--build", str(_compile_worker.build), + "--parallel", "1", "--target", "stan_compile_test"]) + if not successful: + _compile_worker.directory.cleanup() + del _compile_worker.directory + raise RuntimeError("CMake compilation setup failed:\n" + + stdout.decode("utf-8") + stderr.decode("utf-8")) + shutil.copyfile(cpp_path, _compile_worker.source) + return run_command(["cmake", "--build", str(_compile_worker.build), + "--parallel", "1", "--target", "stan_compile_test"]) + +# Returning the result and external linkage keep -Wall -Werror builds free of +# unused variable and unused function warnings. TEST_TEMPLATE = """ -static void {test_name}() {{ +auto {test_name}() {{ {code} +return {result}; }} """ @@ -61,7 +92,7 @@ def build_signature(prefix, cpp_code, debug): stdout_path = cpp_path.replace(".cpp", ".stdout") stderr_path = cpp_path.replace(".cpp", ".stderr") - successful, stdout, stderr = run_command([make, object_path]) + successful, stdout, stderr = compile_signature(cpp_path) if successful or not debug: try: @@ -156,11 +187,12 @@ def main(functions_or_sigs, results_file, cores, debug): arg_list.append(arg) - cg.function_call_assign("stan::math::" + sp.function_name, *arg_list) + result = cg.function_call_assign("stan::math::" + sp.function_name, *arg_list) cpp_code += TEST_TEMPLATE.format( test_name = sp.function_name + repr(m), code=cg.cpp(), + result=result.name, ) if any_overload_uses_varmat: @@ -175,6 +207,7 @@ def main(functions_or_sigs, results_file, cores, debug): os.mkdir(WORKING_FOLDER) work_queue_original_length = work_queue.qsize() + compilation_errors = [] # Test if each cpp file builds and update the output file # This part is done in parallel @@ -189,7 +222,13 @@ def worker(): prefix = re.sub('[^0-9a-zA-Z]+', '_', signature.strip()) # Test the signature - successful = build_signature(prefix, cpp_code, debug) + try: + successful = build_signature(prefix, cpp_code, debug) + except Exception as error: + with output_lock: + compilation_errors.append(error) + work_queue.task_done() + continue # Acquire a lock to do I/O with output_lock: @@ -208,6 +247,8 @@ def worker(): threading.Thread(target = worker).start() work_queue.join() + if compilation_errors: + raise RuntimeError("CMake compilation setup failed") from compilation_errors[0] with open(results_file, "w") as f: json.dump({ "compatible_signatures" : list(compatible_signatures), diff --git a/test/varmat_compatibility_test.py b/test/varmat_compatibility_test.py index e14c46ee48a..a161d3aacdd 100644 --- a/test/varmat_compatibility_test.py +++ b/test/varmat_compatibility_test.py @@ -3,9 +3,30 @@ import tempfile import unittest import os +import threading +from unittest.mock import patch class VarmatCompatibilityMainTest(unittest.TestCase): + def test_initial_build_failure_propagates_compiler_output(self): + with tempfile.TemporaryDirectory() as directory, patch.object( + varmat_compatibility, "WORKING_FOLDER", directory), patch.object( + varmat_compatibility, "_compile_worker", threading.local()), patch.object( + varmat_compatibility, "run_command", side_effect=[ + (True, b"configured", b""), + (False, b"PCH build failed", b"compiler diagnostic")]) as command: + with self.assertRaisesRegex(RuntimeError, "PCH build failedcompiler diagnostic"): + varmat_compatibility.compile_signature("unused.cpp") + self.assertEqual(command.call_count, 2) + self.assertEqual(command.call_args.args[0][-1], "stan_compile_test") + + def test_configuration_failure_propagates(self): + with tempfile.TemporaryDirectory() as directory, patch.object( + varmat_compatibility, "get_signatures", return_value=["sum(matrix) => real\n"]), patch.object( + varmat_compatibility, "build_signature", side_effect=RuntimeError("configuration failed")): + with self.assertRaisesRegex(RuntimeError, "CMake compilation setup failed"): + varmat_compatibility.main(["sum"], os.path.join(directory, "results.json"), 1, False) + def test_sum_works(self): f = tempfile.NamedTemporaryFile("w", suffix="_test.json", delete=False) f.close()