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..1602133d1f3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,153 @@ +cmake_minimum_required(VERSION 3.16.3) +include(cmake/MakeLocal.cmake) +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) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +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) +set(STAN_EFFECTIVE_TEST_PCH ${STAN_TEST_PCH}) +set(stan_pch_reason "") +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) + set(stan_pch_reason " (Clang 9/10 PCH bit_cast defect; fixed in Clang 11)") +endif() +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) +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") +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() + +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 "${STAN_OPENCL_ROOT}") + 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: threads=${STAN_THREADS}, OpenCL=${STAN_OPENCL}, MPI=${STAN_MPI}") +set(STAN_EFFECTIVE_MPI "${STAN_MPI}" CACHE INTERNAL "MPI setting used by test executables" FORCE) +message(STATUS "Test PCH: requested=${STAN_TEST_PCH}, effective=${STAN_EFFECTIVE_TEST_PCH}${stan_pch_reason}") +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() + +set(STAN_COMPILE_TEST_SOURCE "" CACHE FILEPATH "Source file for compile-only compatibility checks") +if(STAN_COMPILE_TEST_SOURCE) + add_library(stan_compile_test OBJECT EXCLUDE_FROM_ALL "${STAN_COMPILE_TEST_SOURCE}") + set(compile_test_targets stan_compile_test) + if(STAN_EFFECTIVE_TEST_PCH) + add_library(stan_compile_test_pch OBJECT EXCLUDE_FROM_ALL cmake/empty.cpp) + list(APPEND compile_test_targets stan_compile_test_pch) + target_precompile_headers(stan_compile_test_pch PRIVATE ) + target_precompile_headers(stan_compile_test REUSE_FROM stan_compile_test_pch) + endif() + foreach(target IN LISTS compile_test_targets) + target_link_libraries(${target} PRIVATE stanmath) + target_include_directories(${target} SYSTEM PRIVATE + "${STAN_BENCHMARK_ROOT}/googletest/googletest/include") + target_compile_options(${target} PRIVATE ${STAN_TEST_OPTIMIZATION_FLAGS}) + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE FALSE) + endforeach() +endif() diff --git a/Jenkinsfile b/Jenkinsfile index b194b911beb..ff7240e5397 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,30 +5,84 @@ properties([ string(defaultValue: 'develop', name: 'cmdstan_pr', description: 'PR to test CmdStan upstream against e.g. PR-630'), string(defaultValue: 'develop', name: 'stan_pr', description: 'PR to test Stan upstream against e.g. PR-630'), booleanParam(defaultValue: false, name: 'withRowVector', description: 'Run additional distribution tests on RowVectors (takes 5x as long)'), - booleanParam(defaultValue: false, name: 'disableJumbo', description: 'Disable Jumbo tests. This takes longer and should only be used for debugging if it is believed that the jumbo tests are causing failures.'), - booleanParam(defaultValue: false, name: 'optimizeUnitTests', description: 'Use O=3 for unit tests (takex ~3x as long)'), + booleanParam(defaultValue: false, name: 'optimizeUnitTests', description: 'Use O=3 for unit tests (takes ~3x as long)'), booleanParam(defaultValue: false, name: 'runAllDistributions', description: 'Run all distribution tests, even ones which are unchanged compared to develop'), - booleanParam(defaultValue: false, name: 'run_all', description: 'Pretend all files changes'), + booleanParam(defaultValue: false, name: 'run_all', description: 'Pretend all files changed'), ]) ]) -def image = 'stanorg/ci:v1' +def image = 'stanorg/ci:gpu-cmake3.30.5' def runRemainingStages = false -def CLANG_CXX = 'clang++-7' -def GCC = 'g++' +def CLANG_CXX = 'clang++' +def CLANG_CC = 'clang' 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 regexes and compiler flags containing spaces. +def shellQuote(String value) { + return "'" + value.replace("'", "'\"'\"'") + "'" +} + +def containerJobs() { + // runPod sets PARALLEL in the container, outside Groovy's env binding. + def jobs = sh(returnStdout: true, script: 'printf "%s" "${PARALLEL:-1}"').trim() + return Math.max(1, jobs.toInteger()) +} + +def configureCmake(List arguments) { + def flags = ['-S', '.', '-B', 'build'] + arguments + if (fileExists('build/CMakeCache.txt')) { + flags += ['--fresh'] + } + sh 'cmake ' + flags.collect { shellQuote(it.toString()) }.join(' ') +} + +def runCmakeTests(Map config) { + def buildDir = 'build' + def reportDir = "test-results/${config.name}" + def jobs = containerJobs() + def flags = [ + '-DCMAKE_BUILD_TYPE=Release', + '-DSTAN_TEST_HEADERS=OFF', '-DSTAN_BUILD_PROB_TESTS=OFF', + "-DSTAN_TEST_OPTIMIZATION=${config.optimization ?: '1'}" + ] + (config.flags ?: []) + try { + sh "cmake -E rm -rf ${shellQuote(buildDir + '/test-results')} " + + "${shellQuote(buildDir + '/ctest-results.xml')} ${shellQuote(buildDir + '/Testing')} " + + shellQuote(reportDir) + configureCmake(flags) + sh "python3 runTests.py build-tests ${shellQuote(buildDir)} " + + "--source-regex ${shellQuote(config.filter)} --jobs ${jobs}" + sh "cmake -E make_directory ${shellQuote(buildDir + '/test-results')}" + withEnv(["GTEST_OUTPUT=xml:${pwd()}/${buildDir}/test-results/"]) { + sh "ctest --test-dir ${shellQuote(buildDir)} -L ${shellQuote(config.filter)} " + + "--parallel ${config.testJobs ?: jobs} " + + "--no-tests=error --output-on-failure " + + "--output-junit ${shellQuote(pwd() + '/' + buildDir + '/ctest-results.xml')}" + } + } finally { + junit(testResults: "${buildDir}/test-results/*.xml", allowEmptyResults: true) + sh "cmake -E make_directory ${shellQuote(reportDir)}" + if (fileExists(buildDir + '/test-results')) { + sh "cmake -E copy_directory ${shellQuote(buildDir + '/test-results')} ${shellQuote(reportDir + '/gtest')}" + } + for (file in ['ctest-results.xml', 'Testing/Temporary/LastTest.log']) { + if (fileExists(buildDir + '/' + file)) { + sh "cmake -E copy ${shellQuote(buildDir + '/' + file)} ${shellQuote(reportDir)}" + } + } + archiveArtifacts(artifacts: "${reportDir}/**", allowEmptyArchive: true) + } +} + +def runFrameworkTests(String compiler) { + writeFile(file: 'make/local', text: "CXX=${compiler}\nO=0\n") + withEnv(["CXX=${compiler}"]) { + sh 'python3 test/varmat_compatibility_test.py' + sh 'python3 test/expressions/test_expression_testing_framework.py' + } } catchError { @@ -42,7 +96,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") { @@ -89,7 +143,13 @@ up the autoformatter locally. (Check console output at ${env.BUILD_URL}) parallel( CppLint: { sh "make cpplint" }, Dependencies: { sh "make test-math-dependencies" }, - Documentation: { sh "make doxygen" }, + CMakeSelection: { + sh 'python3 test/cmake.py' + }, + Documentation: { + configureCmake(['-DBUILD_TESTING=OFF', '-DSTAN_BUILD_DOCS=ON']) + sh "cmake --build build --parallel ${containerJobs()} --target doxygen" + }, ) } } @@ -99,16 +159,25 @@ 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_BUILD_TYPE=Release', + "-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_BUILD_PROB_TESTS=OFF', + '-DSTAN_TEST_HEADERS=ON']) + sh "cmake --build build --parallel ${containerJobs()} --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' + def filter = sh(returnStdout: true, + script: 'python3 runTests.py changed-unit-filter').trim() + if (filter) { + runCmakeTests(name: 'changed', filter: filter, optimization: '3', + flags: ['-DCMAKE_CXX_FLAGS=-march=native -mtune=native']) + } else { + echo 'No priority unit tests selected.' + } } } } @@ -120,49 +189,44 @@ 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") + runCmakeTests(name: 'revfwd', filter: '^test/unit/math/(rev|fwd)/', + optimization: noOptimize ? '0' : '3', + flags: ['-DCMAKE_CXX_FLAGS=-fsanitize=address', '-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address', + '-DCMAKE_SHARED_LINKER_FLAGS=-fsanitize=address']) } } }, mix: { - runPod(image: image, cpus: 8, memory: '192Gi') { + runPod(image: image, cpus: 6, 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) + runCmakeTests(name: 'mix', filter: '^test/unit/math/mix/', + optimization: noOptimize ? '1' : '3', + flags: ['-DCMAKE_CXX_FLAGS=-fsanitize=address', '-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address', + '-DCMAKE_SHARED_LINKER_FLAGS=-fsanitize=address']) } } }, 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") + runCmakeTests(name: 'prim', + filter: '^test/unit/([^/]+_test[.]cpp$|math/([^/]+_test[.]cpp$|prim/|memory/))', + optimization: noOptimize ? '0' : '3', + flags: ['-DCMAKE_CXX_FLAGS=-fsanitize=address', '-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address', + '-DCMAKE_SHARED_LINKER_FLAGS=-fsanitize=address']) } } }, laplace: { runPod(image: image, memory: '32Gi') { stage('Laplace Unit Tests') { - def local = 'CXXFLAGS+= -march=native -mtune=native\nO=3\n' - - runTests(local, "test/unit/math/laplace/*_test.cpp") - + runCmakeTests(name: 'laplace', filter: '^test/unit/math/laplace/', + optimization: '3', flags: ['-DCMAKE_CXX_FLAGS=-march=native -mtune=native']) 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") + runCmakeTests(name: 'laplace-asan', optimization: '3', + filter: '^test/unit/math/laplace/laplace_marginal_lpdf_moto_test[.]cpp$', + flags: ['-DCMAKE_CXX_FLAGS=-march=native -mtune=native -fsanitize=address', + '-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address']) } } } @@ -170,89 +234,81 @@ 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") + runCmakeTests(name: 'opencl', + filter: '^test/unit/(math/opencl/|multiple_translation_units_test[.]cpp$)', + optimization: noOptimize ? '1' : '3', + flags: ["-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']) } } }, 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") + runCmakeTests(name: 'mpi', + filter: '^test/unit/math/(prim|rev)/functor/', testJobs: 1, + flags: ["-DCMAKE_CXX_COMPILER=${MPICXX}", '-DSTAN_MPI=ON']) } } }, expr: { runPod(image: image, memory: '64Gi') { stage('Expressions test') { - def local = "CXX=$CLANG_CXX -Werror\nO=0\n" - writeFile(file: 'make/local', text: local) - 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']) { - 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') + sh 'python3 test/code_generator_test.py' + sh 'python3 test/signature_parser_test.py' + sh 'python3 test/statement_types_test.py' + sh 'python3 test/varmat_compatibility_summary_test.py' + runFrameworkTests(CLANG_CXX) + sh 'python3 runTests.py generate-expressions build' + runCmakeTests(name: 'expressions', filter: '^test/expressions/', optimization: '0', + flags: ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_BUILD_EXPRESSION_TESTS=ON']) + sh 'python3 runTests.py generate-expressions build --functions reduce_sum map_rect' + withEnv(['STAN_NUM_THREADS=4']) { + runCmakeTests(name: 'expressions-threads', filter: '^test/expressions/', + optimization: '0', testJobs: 1, + flags: ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_BUILD_EXPRESSION_TESTS=ON', '-DSTAN_THREADS=ON']) } } } }, thread: { - runPod(image: image, cpus: 8, memory: '128Gi') { + runPod(image: image, cpus: 6, memory: '128Gi') { stage('Threading tests') { - def local = "CXX=$CLANG_CXX -Werror\nSTAN_THREADS=true\n" - withEnv(['STAN_NUM_THREADS=4', 'PARALLEL=8']) { // runTests currently only runs 1 test at a time post-build - if (mainBranch) { - runTests(local, "test/unit") - } else { - runTests(local, "test/unit -f thread") - runTests(local, "test/unit -f map_rect") - runTests(local, "test/unit -f reduce_sum") - } + withEnv(['STAN_NUM_THREADS=4']) { + runCmakeTests(name: 'threads', testJobs: 1, + filter: mainBranch ? '^test/unit/' : '^test/unit/.*(thread|map_rect|reduce_sum)', + flags: ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DCMAKE_CXX_FLAGS=-Werror', '-DSTAN_THREADS=ON']) } } } }, dist: { - def cores_per_test = 6 - 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" - if (params.withRowVector || mainBranch) { - local += "CXXFLAGS+= -DSTAN_TEST_ROW_VECTORS -DSTAN_PROB_TEST_ALL\n" - } - writeFile(file: 'make/local', text: local) - - def cmd = 'python3 test/prob/getDependencies.py' - if (params.runAllDistributions || mainBranch) { - cmd += ' --pretend-all' + runPod(image: image, cpus: 6, memory: '128Gi') { + stage('Distribution tests') { + def command = 'python3 runTests.py changed-distribution-filter' + if (params.runAllDistributions || params.run_all || mainBranch) { + command += ' --all' + } + def filter = sh(returnStdout: true, script: command).trim() + if (filter) { + def flags = ["-DCMAKE_C_COMPILER=${CLANG_CC}", "-DCMAKE_CXX_COMPILER=${CLANG_CXX}", + '-DSTAN_BUILD_PROB_TESTS=ON', '-DSTAN_PROB_TEST_BATCH_SIZE=100'] + if (params.withRowVector || mainBranch) { + flags += ['-DSTAN_TEST_ROW_VECTORS=ON', '-DSTAN_PROB_TEST_ALL=ON'] + } + runCmakeTests(name: 'distributions', filter: filter, + optimization: '0', flags: flags) + } else { + echo 'No distribution tests affected by this change.' + } } - sh """ - $cmd > changed-tests - if [ -s changed-tests ] ; then - ./runTests.py -j${cores_per_test*parallel_tests} --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 - fi - """ } } - } } if (env.CHANGE_TARGET) { @@ -271,7 +327,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 --parallel ${containerJobs()} --target doxygen" dir('gh-pages') { def scm = scmGit( branches: [[name: "refs/heads/$branch"]], diff --git a/README.md b/README.md index 600f5712ad0..517715d2352 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,456 @@ -
- -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` passes CMake's native `--fresh` option during configuration. +CMake removes `CMakeCache.txt` and the top-level `CMakeFiles` directory. +CMake then configures the build with the current arguments and `make/local` settings. +Use `--fresh` when cached settings no longer match the intended build. +Pass required custom settings again because `--fresh` removes cached overrides. +`--fresh` requires CMake 3.24 or newer. +`--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` or `Release`. Setting a configuration changes the default behavior of the build system such as compiler flags and debug information. +The default is `Release`. + +```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 +``` + +- `--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 +``` + +`` 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 +``` + +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. +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. +Example: Reset cached settings and configure a release build. + +```sh +cmake --fresh -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..3a20cff1d2b --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,172 @@ +# 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) + +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(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$") + 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}) + 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/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..1351c4493ba 100755 --- a/runTests.py +++ b/runTests.py @@ -1,76 +1,17 @@ #!/usr/bin/env python3 - -""" -Replacement for runtest target in Makefile. - -Call script with '-h' as an option to see a helpful message. -""" - -from __future__ import print_function +"""Build and run Stan Math tests with CMake.""" import glob +import json import os -import os.path -import platform +from pathlib import Path import re +import shlex 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 +24,28 @@ 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", "changed-distribution-filter", + "generate-expressions", "build-tests"): + commands = parser.add_subparsers(dest="command", required=True) + commands.add_parser("changed-unit-filter") + distributions = commands.add_parser("changed-distribution-filter") + distributions.add_argument("--all", action="store_true") + 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="number of cores for make to use" + "-j", metavar="N", type=int, default=1, help="parallel build jobs (default: 1)" ) parser.add_argument( @@ -140,323 +100,258 @@ 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'" 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 (requires CMake 3.24 or newer)") + parser.add_argument("--cmake-arg", action="append", default=[], + help="extra configure argument, e.g. --cmake-arg=-DSTAN_THREADS=ON") + parser.add_argument("--config", default="Release", help="CMake build configuration") # 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: + parser.error("-j must be positive") + if inputs.make_only and inputs.test_only: + parser.error("--make-only and --test-only cannot be combined") + 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 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"): + raise ValueError("Expected a test directory or *_test.cpp: " + 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) + configure = ["cmake", "-S", str(root), "-B", str(build_dir), + "-DCMAKE_BUILD_TYPE=" + inputs.config, "-DBUILD_TESTING=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)] + if expressions: + configure.append("-DSTAN_EXPRESSION_TEST_DIR=" + str(build_dir / "expression-sources")) + if inputs.fresh: + configure.append("--fresh") + build = ["cmake", "--build", str(build_dir), "--config", inputs.config, + "--parallel", str(inputs.j), "--target"] + [target_for(path)[0] for path in paths] + 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 = [test["name"] for test in inventory["tests"] + if any(pattern.search(label) for entry in test.get("properties", []) + if entry["name"] == "LABELS" for label in entry["value"])] + if not selected: + raise ValueError("No registered tests match: " + source_pattern) + return [["cmake", "--build", str(build_dir), "--parallel", str(jobs), "--target"] + + selected[offset:offset + batch_size] for offset in range(0, len(selected), batch_size)] + + +def changed_distributions(files, run_all=False): + # Every distribution fixture includes the Stan umbrella headers. Library, + # build-system, and shared test-header changes can affect all distributions. + if run_all or any( + path.startswith(("stan/", "lib/", "make/", "cmake/")) + or path in ("CMakeLists.txt", "Jenkinsfile", "makefile") + or (path.startswith("test/") and path.endswith(".hpp") + and not re.fullmatch(r"test/prob/[^/]+/[^/]+_test\.hpp", path)) + or (path.startswith("test/prob/") and not path.endswith("_test.hpp")) + for path in files + ): + return "^test/prob/" + fixtures = [path for path in files + if re.fullmatch(r"test/prob/[^/]+/[^/]+_test\.hpp", path)] + return ("^(" + "|".join(re.escape(path[:-len("_test.hpp")]) + for path in sorted(fixtures)) + ")_[0-9]+_generated_" + if fixtures else "") + + +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 + files = [] + if getattr(inputs, "changed", False) or inputs.command == "changed-unit-filter" or ( + inputs.command == "changed-distribution-filter" and not inputs.all): + 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) + if inputs.command == "changed-distribution-filter": + print(changed_distributions(files, inputs.all)) + return 0 + if getattr(inputs, "changed", False) or inputs.command == "changed-unit-filter": + 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: + expanded = glob.glob(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", "--test-dir", str(build_dir), "--show-only=json-v1"], + cwd=root, 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: + 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 + batches = inputs.batches if inputs.command else (inputs.e if inputs.e > 0 else inputs.j) + 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) + 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: + return 0 + 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 + for path in paths: + command = test_command(path, build_dir, inputs.config, cache, windows) + 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/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/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/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/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..07454a8ffe7 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,217 @@ +# A single set of flags and dependencies keeps PCH producers and consumers +# compatible. Do not enable IPO or add target-specific optimization flags. +# Stan's vendored gtest_main initializes the MPI cluster and includes Stan +# headers even when MPI is disabled. +target_link_libraries(gtest_main PRIVATE stanmath) +# Stan's customized main includes Stan headers. Use the test flags below +# instead of GoogleTest's internal warning policy for its own sources. +set_property(TARGET gtest_main PROPERTY COMPILE_FLAGS "") +add_library(stan_test_options INTERFACE) +target_link_libraries(stan_test_options INTERFACE stanmath gtest_main) +if(MSVC) + target_compile_options(stan_test_options INTERFACE ${STAN_TEST_OPTIMIZATION_FLAGS} /W3) +else() + target_compile_options(stan_test_options INTERFACE + ${STAN_TEST_OPTIMIZATION_FLAGS} -Wall -Wno-deprecated-declarations -Wno-sign-compare) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(stan_test_options INTERFACE + -Werror=invalid-pch -Wno-misleading-indentation) + endif() +endif() +target_compile_options(gtest_main PRIVATE + "$") + +function(stan_add_pch target) + if(STAN_EFFECTIVE_TEST_PCH) + add_library(${target} OBJECT EXCLUDE_FROM_ALL "${PROJECT_SOURCE_DIR}/cmake/empty.cpp") + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE FALSE) + target_link_libraries(${target} PRIVATE stan_test_options) + target_precompile_headers(${target} PRIVATE ${ARGN}) + endif() +endfunction() + +function(stan_configure_test target pch) + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE FALSE) + target_link_libraries(${target} PRIVATE stan_test_options) + if(STAN_EFFECTIVE_TEST_PCH AND pch) + if(TARGET ${pch}) + target_precompile_headers(${target} REUSE_FROM ${pch}) + endif() + endif() +endfunction() + +function(stan_add_test_object target source pch) + add_library(${target} OBJECT EXCLUDE_FROM_ALL "${source}") + stan_configure_test(${target} "${pch}") + get_source_file_property(generator_target "${source}" STAN_GENERATOR_TARGET) + if(generator_target) + # Every consuming executable needs generation before object compilation. + add_dependencies(${target} ${generator_target}) + endif() +endfunction() + +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} "") + set_property(TARGET ${target} PROPERTY STAN_TEST_OBJECTS "${ARGN}") + if(WIN32) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" "$" + VERBATIM) + endif() +endfunction() + +# Directory and per-file executables share objects with per-source PCH choices. +function(add_gtest_grouped_test test_directory folder_test_name target_pch) + set(sources ${ARGN}) + if(NOT sources) + file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${test_directory}/*test.cpp") + endif() + set(directory_keys "") + foreach(src IN LISTS sources) + file(RELATIVE_PATH relative_source "${test_directory}" "${src}") + set(repo_relative "test/${folder_test_name}/${relative_source}") + if(folder_test_name STREQUAL "unit_math") + set(repo_relative "test/unit/math/${relative_source}") + elseif(folder_test_name STREQUAL "prob_math") + set(repo_relative "test/prob/${relative_source}") + elseif(folder_test_name STREQUAL "expressions") + set(repo_relative "test/expressions/${relative_source}") + endif() + set(matches_name_filters TRUE) + foreach(filter IN LISTS STAN_TEST_NAME_FILTERS) + string(FIND "${repo_relative}" "${filter}" match_index) + if(match_index EQUAL -1) + set(matches_name_filters FALSE) + endif() + endforeach() + 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 "${target_pch}") + if(folder_test_name STREQUAL "unit_math") + string(REGEX MATCH "^[^/]+" layer "${relative_source}") + if(TARGET unit_${layer}_pch) + set(pch unit_${layer}_pch) + endif() + # Precompile the AD helpers only for sources that already include them; + # their global aliases must not leak into unrelated tests. + file(STRINGS "${src}" ad_include REGEX "^#include ") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${src}") + if(ad_include AND TARGET unit_ad_pch) + set(pch unit_ad_pch) + endif() + file(STRINGS "${src}" configuration_macros + REGEX "^#[ \t]*(define|undef)[ \t]+(STAN_|EIGEN_)") + if(configuration_macros) + # Such a source must set its macros before including Stan/Eigen. + set_source_files_properties("${src}" PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + set(pch "") + endif() + endif() + stan_add_test_object(${name}_objects "${src}" "${pch}") + set(object "$") + stan_add_test_binary(${name} "${object}") + 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() + get_filename_component(relative_dir "${relative_source}" DIRECTORY) + string(REPLACE "/" ";" labels "${relative_dir}") + set_tests_properties(${name} PROPERTIES + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + LABELS "${folder_test_name};${labels};${repo_relative}") + + if(NOT matches_name_filters) + continue() + endif() + + set(key "${relative_dir}") + while(TRUE) + if(key STREQUAL "") + set(key ROOT) + endif() + string(REPLACE "/" "_" key "${key}") + list(APPEND directory_keys "${key}") + list(APPEND subtests_${key} "${name}") + list(APPEND objects_${key} "${object}") + 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() + list(REMOVE_DUPLICATES directory_keys) + foreach(key IN LISTS directory_keys) + if(key STREQUAL "ROOT") + set(prefix "${folder_test_name}") + else() + set(prefix "${folder_test_name}_${key}") + endif() + add_custom_target(${prefix}_subtests DEPENDS ${subtests_${key}}) + stan_add_test_binary(${prefix}_all_tests ${objects_${key}}) + set_target_properties(${prefix}_all_tests PROPERTIES EXCLUDE_FROM_ALL TRUE) + endforeach() +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 ) + add_gtest_grouped_test("${STAN_EXPRESSION_TEST_DIR}" expressions expression_pch ${expression_sources}) +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..3b260eae764 --- /dev/null +++ b/test/cmake.py @@ -0,0 +1,90 @@ +"""Check CMake command planning. Run with python3 test/cmake.py.""" + +from pathlib import Path +import re +import sys +import unittest +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +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.assertIn("--fresh", configure) + 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_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)) + for flags in (("-j0",), ("--fresh", "--test-only"), ("--make-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_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_distribution_selection(self): + self.assertEqual(runner.changed_distributions(["test/unit/math/prim/fun/sum_test.cpp"]), "") + self.assertEqual(runner.changed_distributions(["stan/math/prim/fun/sum.hpp"]), "^test/prob/") + selected = runner.changed_distributions(["test/prob/bernoulli/bernoulli_test.hpp"]) + for mode in ("v", "ffv", "vv", "fd", "fv", "ffd"): + self.assertRegex(f"test/prob/bernoulli/bernoulli_00000_generated_{mode}_test.cpp", selected) + self.assertIsNone(re.search(selected, "test/prob/bernoulli/bernoulli_cdf_00000_generated_v_test.cpp")) + + +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..eb90dbcae9c --- /dev/null +++ b/test/prob/CMakeLists.txt @@ -0,0 +1,86 @@ +# 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() +stan_add_pch(prob_pch + + ) + +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}") + # 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(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 "") + set(modes v ffv vv) + if(STAN_PROB_TEST_ALL) + list(APPEND modes fd fv ffd) + endif() + 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}) + set(padded "00000${batch}") + string(LENGTH "${padded}" length) + math(EXPR start "${length} - 5") + string(SUBSTRING "${padded}" ${start} 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("${CMAKE_CURRENT_BINARY_DIR}" prob_math prob_pch ${generated_sources}) +endif() 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/sig_utils.py b/test/sig_utils.py index 69d91b2cf13..03e9cce3c67 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 @@ -276,25 +277,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..0c7dc5d5d28 --- /dev/null +++ b/test/unit/CMakeLists.txt @@ -0,0 +1,52 @@ +# 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 ) +endforeach() +stan_add_pch(unit_ad_pch ) +add_gtest_grouped_test("${CMAKE_CURRENT_SOURCE_DIR}/math" unit_math unit_mix_pch) + +# Umbrella and multiple-translation-unit checks must compile without a PCH. +foreach(source math_c11_test.cpp math_include_test.cpp multiple_translation_units_test.cpp) + set(matches_name_filters TRUE) + foreach(filter IN LISTS STAN_TEST_NAME_FILTERS) + string(FIND "test/unit/${source}" "${filter}" match_index) + if(match_index EQUAL -1) + set(matches_name_filters FALSE) + endif() + endforeach() + set_source_files_properties("${source}" PROPERTIES STAN_INCLUDE_IN_GROUP ${matches_name_filters}) + get_filename_component(name "${source}" NAME_WE) + set_source_files_properties("${source}" PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + stan_add_test_object(test_unit_${name}_objects "${source}" "") + stan_add_test_binary(test_unit_${name} "$") + add_test(NAME test_unit_${name} COMMAND test_unit_${name}) + set_tests_properties(test_unit_${name} PROPERTIES LABELS "unit;test/unit/${source}" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") +endforeach() +if(TARGET test_unit_multiple_translation_units_test) + 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) +endif() + +# The public runner also accepts test/unit, including the umbrella checks. +set(unit_objects "") +if(TARGET unit_math_all_tests) + get_target_property(unit_objects unit_math_all_tests STAN_TEST_OBJECTS) +endif() +foreach(source math_c11_test.cpp math_include_test.cpp multiple_translation_units_test.cpp) + get_filename_component(name "${source}" NAME_WE) + get_source_file_property(include_in_group "${source}" STAN_INCLUDE_IN_GROUP) + if(TARGET test_unit_${name} AND include_in_group) + list(APPEND unit_objects "$") + endif() +endforeach() +if(unit_objects) + stan_add_test_binary(unit_all_tests ${unit_objects}) + set_target_properties(unit_all_tests PROPERTIES EXCLUDE_FROM_ALL TRUE) + if(TARGET stan_multiple_translation_units) + target_link_libraries(unit_all_tests PRIVATE stan_multiple_translation_units) + endif() +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/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/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/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/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/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..fa5c338bf64 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([]() { + EXPECT_EQ(num_threads - 1, 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/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/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/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/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/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..f50c93c8698 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,6 +21,32 @@ 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"]) TEST_TEMPLATE = """ static void {test_name}() {{ @@ -61,7 +89,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: @@ -175,6 +203,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 +218,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 +243,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()