diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1f3d20349..3728d99b7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -61,6 +61,7 @@ jobs: compiler: ${{ matrix.toolchain.compiler }} version: ${{ matrix.toolchain.version }} + # Build and test with built-in BLAS and LAPACK - name: Configure with CMake if: ${{ contains(matrix.build, 'cmake') }} run: >- @@ -68,6 +69,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAXIMUM_RANK:String=4 -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DFIND_BLAS:STRING=FALSE -S . -B ${{ env.BUILD_DIR }} - name: Build and compile @@ -75,8 +77,8 @@ jobs: run: cmake --build ${{ env.BUILD_DIR }} --parallel - name: catch build fail - run: cmake --build ${{ env.BUILD_DIR }} --verbose --parallel 1 if: ${{ failure() && contains(matrix.build, 'cmake') }} + run: cmake --build ${{ env.BUILD_DIR }} --verbose --parallel 1 - name: test if: ${{ contains(matrix.build, 'cmake') }} diff --git a/.github/workflows/ci_BLAS.yml b/.github/workflows/ci_BLAS.yml new file mode 100644 index 000000000..7195c1d61 --- /dev/null +++ b/.github/workflows/ci_BLAS.yml @@ -0,0 +1,136 @@ +name: CI_BLAS + +on: [push, pull_request] + +env: + CTEST_TIME_TIMEOUT: "5" # some failures hang forever + CMAKE_GENERATOR: Ninja + +jobs: + msys2-build: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: [{ msystem: UCRT64, arch: x86_64 }] + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v2 + + - name: Setup MinGW native environment + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + update: false + install: >- + git + mingw-w64-ucrt-${{ matrix.arch }}-gcc + mingw-w64-ucrt-${{ matrix.arch }}-gcc-fortran + mingw-w64-ucrt-${{ matrix.arch }}-python + mingw-w64-ucrt-${{ matrix.arch }}-python-fypp + mingw-w64-ucrt-${{ matrix.arch }}-cmake + mingw-w64-ucrt-${{ matrix.arch }}-ninja + mingw-w64-ucrt-${{ matrix.arch }}-openblas + + # Build and test with external BLAS and LAPACK (OpenBLAS on UCRT64) + - name: Configure with CMake and OpenBLAS + run: >- + PATH=$PATH:/ucrt64/bin/ cmake + -Wdev + -B build + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_Fortran_FLAGS_DEBUG="-Wall -Wextra -Wimplicit-interface -fPIC -g -fcheck=all -fbacktrace" + -DCMAKE_MAXIMUM_RANK:String=4 + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DFIND_BLAS:STRING=TRUE + env: + FC: gfortran + CC: gcc + CXX: g++ + + - name: CMake build with OpenBLAS + run: PATH=$PATH:/ucrt64/bin/ cmake --build build --parallel + + - name: catch build fail + if: failure() + run: PATH=$PATH:/ucrt64/bin/ cmake --build build --verbose --parallel 1 + + - name: CTest with OpenBLAS + run: PATH=$PATH:/ucrt64/bin/ ctest --test-dir build --output-on-failure --parallel -V -LE quadruple_precision + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: WindowsCMakeTestlog_openblas + path: build/Testing/Temporary/LastTest.log + + - name: Install project with OpenBLAS + run: PATH=$PATH:/ucrt64/bin/ cmake --install build + + Build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: + - { compiler: intel, version: "2024.1" } + build: [cmake] + env: + BUILD_DIR: ${{ matrix.build == 'cmake' && 'build' || '.' }} + APT_PACKAGES: >- + intel-oneapi-mkl + intel-oneapi-mkl-devel + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.x + uses: actions/setup-python@v5 # Use pip to install latest CMake, & FORD/Jin2For, etc. + with: + python-version: 3.x + + - name: Install fypp + run: pip install --upgrade fypp ninja + + - name: Setup Fortran compiler + uses: fortran-lang/setup-fortran@v1.6.1 + id: setup-fortran + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install Intel oneAPI MKL + run: | + sudo apt-get install ${APT_PACKAGES} + source /opt/intel/oneapi/mkl/latest/env/vars.sh + printenv >> $GITHUB_ENV + + # Build and test with external BLAS and LAPACK (MKL on Ubuntu with Intel compilers) + - name: Configure with CMake and MKL + run: >- + cmake -Wdev -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_MAXIMUM_RANK:String=4 + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DFIND_BLAS:STRING=TRUE + -S . -B ${{ env.BUILD_DIR }} + + - name: Build and compile with MKL + run: cmake --build ${{ env.BUILD_DIR }} --parallel + + - name: catch build fail with MKL + if: failure() + run: cmake --build ${{ env.BUILD_DIR }} --verbose --parallel 1 + + - name: test with MKL + run: >- + ctest + --test-dir ${{ env.BUILD_DIR }} + --parallel + --output-on-failure + --no-tests=error + + - name: Install project with MKL + run: cmake --install ${{ env.BUILD_DIR }} diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index ead22a775..562da87fe 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -43,6 +43,7 @@ jobs: -DCMAKE_Fortran_FLAGS_DEBUG="-Wall -Wextra -Wimplicit-interface -fPIC -g -fcheck=all -fbacktrace" -DCMAKE_MAXIMUM_RANK:String=4 -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DFIND_BLAS:STRING=FALSE env: FC: gfortran CC: gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index 09a1753e0..3511a4d6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,32 @@ if(NOT DEFINED CMAKE_MAXIMUM_RANK) set(CMAKE_MAXIMUM_RANK 4 CACHE STRING "Maximum array rank for generated procedures") endif() +option(FIND_BLAS "Find external BLAS and LAPACK" ON) + +# --- find BLAS and LAPACK +if(FIND_BLAS) + if(NOT BLAS_FOUND) + #Required for MKL + if(DEFINED ENV{MKLROOT} OR "${BLA_VENDOR}" MATCHES "^Intel") + enable_language("C") + endif() + find_package("BLAS") + endif() + if(BLAS_FOUND) + add_compile_definitions(STDLIB_EXTERNAL_BLAS) + endif() + if(NOT LAPACK_FOUND) + #Required for MKL + if(DEFINED ENV{MKLROOT} OR "${BLA_VENDOR}" MATCHES "^Intel") + enable_language("C") + endif() + find_package("LAPACK") + endif() + if(LAPACK_FOUND) + add_compile_definitions(STDLIB_EXTERNAL_LAPACK) + endif() +endif() + # --- find preprocessor find_program(FYPP fypp) if(NOT FYPP) diff --git a/README.md b/README.md index fb0ab1cb3..1a249cec6 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ Important options are - `-DBUILD_TESTING` set to `off` in case you want to disable the stdlib tests (default: `on`). - `-DCMAKE_VERBOSE_MAKEFILE` is by default set to `Off`, but if set to `On` will show commands used to compile the code. - `-DCMAKE_BUILD_TYPE` is by default set to `RelWithDebInfo`, which uses compiler flags suitable for code development (but with only `-O2` optimization). Beware the compiler flags set this way will override any compiler flags specified via `FFLAGS`. To prevent this, use `-DCMAKE_BUILD_TYPE=NoConfig` in conjunction with `FFLAGS`. +- `-DFIND_BLAS` set to `off` in case you want to disable finding the external BLAS/LAPACK dependency (default: `on`). For example, to configure a build using the Ninja backend while specifying compiler optimization via `FFLAGS`, generating procedures up to rank 7, installing to your home directory, using the `NoConfig` compiler flags, and printing the compiler commands, use diff --git a/config/fypp_deployment.py b/config/fypp_deployment.py index 47459de64..aa44b1df0 100644 --- a/config/fypp_deployment.py +++ b/config/fypp_deployment.py @@ -6,21 +6,8 @@ C_PREPROCESSED = ( "stdlib_linalg_constants" , "stdlib_linalg_blas" , - "stdlib_linalg_blas_aux", - "stdlib_linalg_blas_s", - "stdlib_linalg_blas_d", - "stdlib_linalg_blas_q", - "stdlib_linalg_blas_c", - "stdlib_linalg_blas_z", - "stdlib_linalg_blas_w", "stdlib_linalg_lapack", - "stdlib_linalg_lapack_aux", - "stdlib_linalg_lapack_s", - "stdlib_linalg_lapack_d", - "stdlib_linalg_lapack_q", - "stdlib_linalg_lapack_c", - "stdlib_linalg_lapack_z", - "stdlib_linalg_lapack_w" + "test_blas_lapack" ) def pre_process_fypp(args): @@ -105,7 +92,7 @@ def recursive_copy(folder): for root, _, files in os.walk(folder): for file in files: if file not in prune: - if file.endswith((".f90", ".F90", ".dat", ".npy", ".c")): + if file.endswith((".f90", ".F90", ".dat", ".npy", ".c", ".h")): shutil.copy2(os.path.join(root, file), base_folder+os.sep+folder+os.sep+file) recursive_copy('src') recursive_copy('test') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d82aae118..af67d91e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,6 +126,14 @@ set(SRC add_library(${PROJECT_NAME} ${SRC}) +# Link to BLAS and LAPACK +if(BLAS_FOUND) + target_link_libraries(${PROJECT_NAME} "BLAS::BLAS") +endif() +if(LAPACK_FOUND) + target_link_libraries(${PROJECT_NAME} "LAPACK::LAPACK") +endif() + set_target_properties( ${PROJECT_NAME} PROPERTIES diff --git a/src/blas/CMakeLists.txt b/src/blas/CMakeLists.txt index ad2ee4c27..5099b909e 100644 --- a/src/blas/CMakeLists.txt +++ b/src/blas/CMakeLists.txt @@ -4,9 +4,6 @@ set(dir "${CMAKE_CURRENT_SOURCE_DIR}") list(APPEND fppFiles blas/stdlib_blas_constants.fypp -) - -list(APPEND cppFiles blas/stdlib_blas.fypp blas/stdlib_blas_level1.fypp blas/stdlib_blas_level2_ban.fypp @@ -21,4 +18,3 @@ list(APPEND cppFiles ) set(fppFiles "${fppFiles}" PARENT_SCOPE) -set(cppFiles "${cppFiles}" PARENT_SCOPE) diff --git a/src/lapack/CMakeLists.txt b/src/lapack/CMakeLists.txt index 6aa0a44ec..a8f3445ce 100644 --- a/src/lapack/CMakeLists.txt +++ b/src/lapack/CMakeLists.txt @@ -1,4 +1,4 @@ -list(APPEND cppFiles +list(APPEND fppFiles lapack/stdlib_lapack_base.fypp lapack/stdlib_lapack_solve.fypp lapack/stdlib_lapack_others.fypp @@ -55,4 +55,4 @@ list(APPEND cppFiles lapack/stdlib_lapack_svd_comp2.fypp ) -set(cppFiles "${cppFiles}" PARENT_SCOPE) +set(fppFiles "${fppFiles}" PARENT_SCOPE) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 566c0e6d8..e44cb19ce 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,6 +10,15 @@ macro(ADDTEST name) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endmacro(ADDTEST) +macro(ADDTESTPP name) + add_executable(test_${name} test_${name}.F90) + target_link_libraries(test_${name} "${PROJECT_NAME}" "test-drive::test-drive") + add_test(NAME ${name} + COMMAND $ ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endmacro(ADDTESTPP) + + add_subdirectory(array) add_subdirectory(ascii) add_subdirectory(bitsets) @@ -31,4 +40,4 @@ add_subdirectory(system) add_subdirectory(quadrature) add_subdirectory(math) add_subdirectory(stringlist) -add_subdirectory(terminal) \ No newline at end of file +add_subdirectory(terminal) diff --git a/test/linalg/CMakeLists.txt b/test/linalg/CMakeLists.txt index c496bd2b3..0745aa3cb 100644 --- a/test/linalg/CMakeLists.txt +++ b/test/linalg/CMakeLists.txt @@ -1,8 +1,6 @@ set( fppFiles "test_linalg.fypp" - "test_blas_lapack.fypp" - "test_linalg_cholesky.fypp" "test_linalg_eigenvalues.fypp" "test_linalg_solve.fypp" "test_linalg_inverse.fypp" @@ -16,8 +14,17 @@ set( "test_linalg_svd.fypp" "test_linalg_matrix_property_checks.fypp" "test_linalg_sparse.fypp" + "test_linalg_cholesky.fypp" +) + +# Preprocessed files to contain preprocessor directives -> .F90 +set( + cppFiles + "test_blas_lapack.fypp" ) + fypp_f90("${fyppFlags}" "${fppFiles}" outFiles) +fypp_f90pp("${fyppFlags}" "${cppFiles}" outPreprocFiles) ADDTEST(linalg) ADDTEST(linalg_cholesky) @@ -33,5 +40,5 @@ ADDTEST(linalg_lstsq) ADDTEST(linalg_qr) ADDTEST(linalg_schur) ADDTEST(linalg_svd) -ADDTEST(blas_lapack) ADDTEST(linalg_sparse) +ADDTESTPP(blas_lapack) diff --git a/test/linalg/test_blas_lapack.fypp b/test/linalg/test_blas_lapack.fypp index 4df7f4736..ad7132551 100644 --- a/test/linalg/test_blas_lapack.fypp +++ b/test/linalg/test_blas_lapack.fypp @@ -23,7 +23,9 @@ contains new_unittest("test_gemv${t1[0]}$${k1}$", test_gemv${t1[0]}$${k1}$), & new_unittest("test_getri${t1[0]}$${k1}$", test_getri${t1[0]}$${k1}$), & #:endfor - new_unittest("test_idamax", test_idamax) & + new_unittest("test_idamax", test_idamax), & + new_unittest("test_external_blas",external_blas_test), & + new_unittest("test_external_lapack",external_lapack_test) & ] end subroutine collect_blas_lapack @@ -104,6 +106,75 @@ contains end subroutine test_idamax + !> Test availability of the external BLAS interface + subroutine external_blas_test(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + +#ifdef STDLIB_EXTERNAL_BLAS + interface + subroutine saxpy(n,sa,sx,incx,sy,incy) + import sp,ilp + implicit none(type,external) + real(sp), intent(in) :: sa,sx(*) + integer(ilp), intent(in) :: incx,incy,n + real(sp), intent(inout) :: sy(*) + end subroutine saxpy + end interface + + integer(ilp), parameter :: n = 5, inc=1 + real(sp) :: a,x(n),y(n) + + x = 1.0_sp + y = 2.0_sp + a = 3.0_sp + + call saxpy(n,a,x,inc,y,inc) + call check(error, all(abs(y-5.0_sp) Test availability of the external BLAS interface + subroutine external_lapack_test(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + +#ifdef STDLIB_EXTERNAL_LAPACK + interface + subroutine dgetrf( m, n, a, lda, ipiv, info ) + import dp,ilp + implicit none(type,external) + integer(ilp), intent(out) :: info,ipiv(*) + integer(ilp), intent(in) :: lda,m,n + real(dp), intent(inout) :: a(lda,*) + end subroutine dgetrf + end interface + + integer(ilp), parameter :: n = 3 + real(dp) :: A(n,n) + integer(ilp) :: ipiv(n),info + + + A = eye(n) + info = 123 + + ! Factorize matrix + call dgetrf(n,n,A,n,ipiv,info) + + call check(error, info==0, "dgetrf: check result") + if (allocated(error)) return + +#else + call skip_test(error, "Not using an external LAPACK") +#endif + + end subroutine external_lapack_test + end module test_blas_lapack diff --git a/test/linalg/test_linalg_svd.fypp b/test/linalg/test_linalg_svd.fypp index 03886ab69..f91a87322 100644 --- a/test/linalg/test_linalg_svd.fypp +++ b/test/linalg/test_linalg_svd.fypp @@ -86,7 +86,7 @@ module test_linalg_svd if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(u-u_sol)<=tol) .or. all(abs(u+u_sol)<=tol), test//': U') + call check(error, all(abs(abs(u)-abs(u_sol))<=tol), test//': U') if (allocated(error)) return !> [S, U]. Overwrite A matrix @@ -97,7 +97,7 @@ module test_linalg_svd if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(u-u_sol)<=tol) .or. all(abs(u+u_sol)<=tol), test//': U') + call check(error, all(abs(abs(u)-abs(u_sol))<=tol), test//': U') if (allocated(error)) return !> [S, U, V^T] @@ -109,9 +109,9 @@ module test_linalg_svd if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(u-u_sol)<=tol) .or. all(abs(u+u_sol)<=tol), test//': U') + call check(error, all(abs(abs(u)-abs(u_sol))<=tol), test//': U') if (allocated(error)) return - call check(error, all(abs(vt-vt_sol)<=tol) .or. all(abs(vt+vt_sol)<=tol), test//': V^T') + call check(error, all(abs(abs(vt)-abs(vt_sol))<=tol), test//': V^T') if (allocated(error)) return !> [S, V^T]. Do not overwrite A matrix @@ -123,7 +123,7 @@ module test_linalg_svd if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(vt-vt_sol)<=tol) .or. all(abs(vt+vt_sol)<=tol), test//': V^T') + call check(error, all(abs(abs(vt)-abs(vt_sol))<=tol), test//': V^T') if (allocated(error)) return !> [S, V^T]. Overwrite A matrix @@ -134,7 +134,7 @@ module test_linalg_svd if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(vt-vt_sol)<=tol) .or. all(abs(vt+vt_sol)<=tol), test//': V^T') + call check(error, all(abs(abs(vt)-abs(vt_sol))<=tol), test//': V^T') if (allocated(error)) return !> [U, S, V^T]. @@ -144,11 +144,11 @@ module test_linalg_svd test = '[U, S, V^T]' call check(error,state%ok(),test//': '//state%print()) if (allocated(error)) return - call check(error, all(abs(u-u_sol)<=tol) .or. all(abs(u+u_sol)<=tol), test//': U') + call check(error, all(abs(abs(u)-abs(u_sol))<=tol), test//': U') if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(vt-vt_sol)<=tol) .or. all(abs(vt+vt_sol)<=tol), test//': V^T') + call check(error, all(abs(abs(vt)-abs(vt_sol))<=tol), test//': V^T') if (allocated(error)) return !> [U, S, V^T]. Partial storage -> compare until k=2 columns of U rows of V^T @@ -160,11 +160,11 @@ module test_linalg_svd test = '[U, S, V^T], partial storage' call check(error,state%ok(),test//': '//state%print()) if (allocated(error)) return - call check(error, all(abs(u(:,:2)-u_sol(:,:2))<=tol) .or. all(abs(u(:,:2)+u_sol(:,:2))<=tol), test//': U(:,:2)') + call check(error, all(abs(abs(u(:,:2))-abs(u_sol(:,:2)))<=tol), test//': U(:,:2)') if (allocated(error)) return call check(error, all(abs(s-s_sol)<=tol), test//': S') if (allocated(error)) return - call check(error, all(abs(vt(:2,:)-vt_sol(:2,:))<=tol) .or. all(abs(vt(:2,:)+vt_sol(:2,:))<=tol), test//': V^T(:2,:)') + call check(error, all(abs(abs(vt(:2,:))-abs(vt_sol(:2,:)))<=tol), test//': V^T(:2,:)') if (allocated(error)) return end subroutine test_svd_${ri}$