diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6988259d3..ad21c3278 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -60,6 +60,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: >- @@ -67,6 +68,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 @@ -74,8 +76,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') }} @@ -89,3 +91,69 @@ jobs: - name: Install project if: ${{ contains(matrix.build, 'cmake') }} run: cmake --install ${{ env.BUILD_DIR }} + + Build-with-MKL: + 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 27d49f6c5..fd1f9c8cb 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -56,7 +56,8 @@ jobs: - name: Install fypp run: pip install fypp - + + # Build and test with built-in BLAS and LAPACK - run: >- PATH=$PATH:/mingw64/bin/ cmake -Wdev @@ -65,6 +66,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 @@ -88,3 +90,71 @@ jobs: - name: Install project run: PATH=$PATH:/mingw64/bin/ cmake --install build + + msys2-build-with-OpenBLAS: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: [ + { msystem: MINGW64, 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-${{ matrix.arch }}-gcc + mingw-w64-${{ matrix.arch }}-gcc-fortran + mingw-w64-${{ matrix.arch }}-python + mingw-w64-${{ matrix.arch }}-python-pip + mingw-w64-${{ matrix.arch }}-python-setuptools + mingw-w64-${{ matrix.arch }}-cmake + mingw-w64-${{ matrix.arch }}-ninja + mingw-w64-${{ matrix.arch }}-openblas + + - name: Install fypp + run: pip install fypp + + # Build and test with external BLAS and LAPACK (OpenBLAS on MINGW64) + - name: Configure with CMake and OpenBLAS + run: >- + PATH=$PATH:/mingw64/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:/mingw64/bin/ cmake --build build --parallel + + - name: catch build fail + if: failure() + run: PATH=$PATH:/mingw64/bin/ cmake --build build --verbose --parallel 1 + + - name: CTest with OpenBLAS + run: PATH=$PATH:/mingw64/bin/ ctest --test-dir build --output-on-failure --parallel -V -LE quadruple_precision + + - uses: actions/upload-artifact@v1 + if: failure() + with: + name: WindowsCMakeTestlog_openblas + path: build/Testing/Temporary/LastTest.log + + - name: Install project with OpenBLAS + run: PATH=$PATH:/mingw64/bin/ cmake --install build diff --git a/CMakeLists.txt b/CMakeLists.txt index b10e1f73d..37ecae37e 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 b329d2713..e799eb7bd 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/src/CMakeLists.txt b/src/CMakeLists.txt index fa1831e98..398935080 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,6 +120,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/test/CMakeLists.txt b/test/CMakeLists.txt index 4d83548db..92d2675e4 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) @@ -30,4 +39,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 cff60532d..af0e30966 100644 --- a/test/linalg/CMakeLists.txt +++ b/test/linalg/CMakeLists.txt @@ -1,7 +1,6 @@ set( fppFiles "test_linalg.fypp" - "test_blas_lapack.fypp" "test_linalg_eigenvalues.fypp" "test_linalg_solve.fypp" "test_linalg_lstsq.fypp" @@ -9,7 +8,15 @@ set( "test_linalg_svd.fypp" "test_linalg_matrix_property_checks.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_determinant) @@ -18,4 +25,4 @@ ADDTEST(linalg_matrix_property_checks) ADDTEST(linalg_solve) ADDTEST(linalg_lstsq) ADDTEST(linalg_svd) -ADDTEST(blas_lapack) +ADDTESTPP(blas_lapack) diff --git a/test/linalg/test_blas_lapack.fypp b/test/linalg/test_blas_lapack.fypp index e36ac2717..0c1d0592c 100644 --- a/test/linalg/test_blas_lapack.fypp +++ b/test/linalg/test_blas_lapack.fypp @@ -30,7 +30,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 @@ -117,6 +119,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 d5a01d123..fdb5d5a0e 100644 --- a/test/linalg/test_linalg_svd.fypp +++ b/test/linalg/test_linalg_svd.fypp @@ -93,7 +93,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 @@ -104,7 +104,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] @@ -116,9 +116,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 @@ -130,7 +130,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 @@ -141,7 +141,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]. @@ -151,11 +151,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 @@ -167,11 +167,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}$