diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cc51517d2..4c2b4e7a2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,35 +2,20 @@ name: CI on: [push, pull_request] + jobs: Build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: + matrix: + os: [macOS-latest] fail-fast: true - env: - FC: gfortran - GCC_V: 10 - steps: - name: Checkout code uses: actions/checkout@v2 - - name: Install Dependencies - run: | - sudo apt install -y gfortran-${GCC_V} cmake mpich - sudo update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-${GCC_V} 100 - git clone https://github.com/sourceryinstitute/opencoarrays - mkdir -p opencoarrays/build - cd opencoarrays/build - cmake .. - sudo make -j $(nproc) install - cd - - git clone https://github.com/fortran-lang/fpm - cd fpm - ./install.sh - - name: Build and Test run: | export PATH="${HOME}/.local/bin:$PATH" - fpm test + ./setup.sh diff --git a/example/netCDF_IO.f90 b/example/netCDF_IO.f90 new file mode 100644 index 000000000..4cdfd5ad0 --- /dev/null +++ b/example/netCDF_IO.f90 @@ -0,0 +1,114 @@ +program netCDF_IO + use netcdf, only : & + nf90_create, nf90_def_dim, nf90_def_var, nf90_enddef, nf90_put_var, nf90_inquire_dimension, & ! functions + nf90_close, nf90_open, nf90_inq_varid, nf90_get_var, nf90_inquire_variable, & + nf90_clobber, nf90_noerr, nf90_strerror, nf90_int, nf90_nowrite ! constants + use assert_m, only : assert + implicit none + + integer i, j + integer, parameter :: ny = 12, nx = 6 + integer, parameter :: data_written(*,*) = reshape([((i*j, i=1,nx), j=1,ny)], [ny,nx]) + integer, allocatable :: data_read(:,:) + character(len=*), parameter :: file_name = "netCDF_example.nc" + + call netCDF_write(file_name, data_written) + call netCDF_read(file_name, data_read) + + call assert(all(data_written == data_read) , "netCDF_IO: all(data_written == data_read)") + + print *, "-----> netCDF file '" // file_name // "' written and read without error <-----" + +contains + + subroutine netCDF_write(file_name_, data_out) + character(len=*), intent(in) :: file_name_ + integer, intent(in) :: data_out(:,:) + + integer ncid, varid, x_dimid, y_dimid + + associate(nf_status => nf90_create(file_name_, nf90_clobber, ncid)) ! create or ovewrite file + call assert(nf_status == nf90_noerr, "nf90_create(file_name, nf90_clobber, ncid) succeeds",trim(nf90_strerror(nf_status))) + end associate + associate(nf_status => nf90_def_dim(ncid, "x", size(data_out,2), x_dimid)) ! define x dimension & get its ID + call assert(nf_status == nf90_noerr,'nf90_def_dim(ncid,"x",size(data_out,2),x_dimid) succeeds',trim(nf90_strerror(nf_status))) + end associate + associate(nf_status => nf90_def_dim(ncid, "y", size(data_out,1), y_dimid)) ! define y dimension & get its ID + call assert(nf_status==nf90_noerr, 'nf90_def_dim(ncid,"y",size(data_out,2),y_dimid) succeeds', trim(nf90_strerror(nf_status))) + end associate + associate(nf_status => nf90_def_var(ncid, "data", nf90_int, [y_dimid, x_dimid], varid))!define integer 'data' variable & get ID + call assert(nf_status == nf90_noerr, 'nf90_def_var(ncid,"data",nf90_int,[y_dimid,x_dimid],varid) succeds', & + trim(nf90_strerror(nf_status))) + end associate + associate(nf_status => nf90_enddef(ncid)) ! exit define mode: tell netCDF we are done defining metadata + call assert(nf_status == nf90_noerr, 'nff90_noerr == nf90_enddef(ncid)', trim(nf90_strerror(nf_status))) + end associate + associate(nf_status => nf90_put_var(ncid, varid, data_out)) ! write all data to file + call assert(nf_status == nf90_noerr, 'nff90_noerr == nf90_put_var(ncid, varid, data_out)', trim(nf90_strerror(nf_status))) + end associate + associate(nf_status => nf90_close(ncid)) ! close file to free associated netCDF resources and flush buffers + call assert(nf_status == nf90_noerr, 'nff90_noerr == nf90_close(ncid)', trim(nf90_strerror(nf_status))) + end associate + + end subroutine + + subroutine netCDF_read(file_name_, data_in) + character(len=*), intent(in) :: file_name_ + integer, intent(inout), allocatable :: data_in(:,:) + integer ncid, varid, data_in_rank + + associate( nf_status => nf90_open(file_name_, nf90_nowrite, ncid) ) ! open file with read-only acces + call assert(nf_status == nf90_noerr, "nf90_open(file_name_, NF90_NOWRITE, ncid) succeeds", trim(nf90_strerror(nf_status))) + end associate + + associate( nf_status => nf90_inq_varid(ncid, "data", varid)) ! Get data variable's ID + call assert(nf_status == nf90_noerr, 'nf90_inq_varid(ncid, "data", varid) succeeds', trim(nf90_strerror(nf_status))) + end associate + + associate(data_in_shape => get_shape(ncid, "data")) + allocate(data_in(data_in_shape(1), data_in_shape(2))) + end associate + + associate( nf_status => nf90_get_var(ncid, varid, data_in)) ! Read data + call assert(nf_status == nf90_noerr, "nf90_get_var(ncid, varid, data_in) succeeds", trim(nf90_strerror(nf_status))) + end associate + + end subroutine + + function get_shape(ncid, varname) result(array_shape) + implicit none + character(len=*), intent(in) :: varname + integer, intent(in) :: ncid + integer, allocatable :: array_shape(:) + character(len=32) varid_string + + integer varid, dimlen, i, var_rank + integer, parameter :: max_rank=15 + integer,dimension(max_rank+1) :: dims, dimIds + + associate(nf_status => nf90_inq_varid(ncid, varname, varid)) + write(varid_string, *) varid + call assert(nf_status == nf90_noerr, "nf90_noerr == nf90_inq_varid(ncid, varname, varid) (" // & + trim(nf90_strerror(nf_status)) // "(" // trim(varid_string)// ")") + end associate + associate(nf_status => nf90_inquire_variable(ncid, varid, ndims = var_rank)) + call assert(nf_status == nf90_noerr, "nf90_noerr == nf90_inquire_variable(ncid, varid, ndims = var_rank) (" // & + trim(nf90_strerror(nf_status)) // "(" // varname // ")") + end associate + associate(nf_status => nf90_inquire_variable(ncid, varid, dimids = dimIds(:var_rank))) + call assert(nf_status == nf90_noerr, "nf90_noerr == nf90_inquire_variable(ncid, varid, dimids = dimIds(:var_rank))", & + trim(nf90_strerror(nf_status)) // "(" // varname // ")") + end associate + + do i=1,var_rank + associate(nf_status => nf90_inquire_dimension(ncid, dimIds(i), len = dimlen)) + call assert(nf_status == nf90_noerr, "nf90_noerr == nf90_inquire_dimension(ncid, dimIds(i), len = dimlen)", & + trim(nf90_strerror(nf_status)) // "(" // varname // ")") + end associate + dims(i+1)=dimlen + end do + + array_shape = dims(2:var_rank+1) + end function + +end program netCDF_IO diff --git a/fpm.toml b/fpm.toml index 79cd413b9..cd4e17768 100644 --- a/fpm.toml +++ b/fpm.toml @@ -7,3 +7,4 @@ maintainer = "rouson@lbl.gov" [dependencies] assert = {git = "https://github.com/sourceryinstitute/assert", tag = "1.4.0"} sourcery = {git = "https://github.com/sourceryinstitute/sourcery", tag = "3.5.0"} +netcdf-interfaces = {git = "https://github.com/LKedward/netcdf-interfaces.git"} diff --git a/setup.sh b/setup.sh new file mode 100755 index 000000000..2b8de7542 --- /dev/null +++ b/setup.sh @@ -0,0 +1,165 @@ +#!/bin/sh + +set -e # exit on error + +usage() +{ + echo "Inference Engine Setup Script" + echo "" + echo "USAGE:" + echo "./setup.sh [--help|-h] | [-p|--prefix=PREFIX]" + echo "" + echo " --help Display this help text" + echo " --prefix=PREFIX Install binary in 'PREFIX/bin'" + echo " Default prefix='\$HOME/.local/bin'" + echo "" +} + +PREFIX="$HOME/.local" + +while [ "$1" != "" ]; do + PARAM=$(echo "$1" | awk -F= '{print $1}') + VALUE=$(echo "$1" | awk -F= '{print $2}') + case $PARAM in + -h | --help) + usage + exit + ;; + -p | --prefix) + PREFIX=$VALUE + ;; + *) + echo "ERROR: unknown parameter \"$PARAM\"" + usage + exit 1 + ;; + esac + shift +done + +set -u # error on use of undefined variable + +if ! command -v brew > /dev/null ; then + if ! command -v curl > /dev/null ; then + echo "Please install curl and then rerun ./install.sh" + exit 1 + fi + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + if [ $(uname) = "Linux" ]; then + if [ -z "$PATH" ]; then + PATH=/home/linuxbrew/.linuxbrew/bin/ + else + PATH=/home/linuxbrew/.linuxbrew/bin/:"$PATH" + fi + fi +fi + + +brew tap fortran-lang/fortran # required for building fpm +brew install fpm cmake netcdf pkg-config coreutils # coreutils supports `realpath` below + +PREFIX=`realpath $PREFIX` + +mkdir -p build/dependencies +if [ ! -d ./build/dependencies/netcdf-fortran ]; then + git clone https://github.com/Unidata/netcdf-fortran.git build/dependencies/netcdf-fortran +fi +mkdir -p build/dependencies/netcdf-fortran/build + +CI=${CI:-"false"} # GitHub Actions workflows set CI=true + +cd build/dependencies/netcdf-fortran/build + GCC_VER="12" # This should be replaced with code extracting the version number from Homebrew + export FC=gfortran-${GCC_VER} CC=gcc-${GCC_VER} CXX=g++-${GCC_VER} + if [ $CI = true ]; then + NETCDFF_PREFIX="/usr/local" + else + NETCDFF_PREFIX="$PREFIX" + fi + NETCDF_PREFIX="`brew --prefix netcdf`" + cmake .. \ + -DNETCDF_C_LIBRARY="$NETCDF_PREFIX/lib" \ + -DNETCDF_C_INCLUDE_DIR="$NETCDF_PREFIX/include" \ + -DCMAKE_INSTALL_PREFIX="$NETCDFF_PREFIX" + make -j4 + sudo make install +cd - + +GIT_VERSION=`git describe --long --dirty --all --always | sed -e's/heads\///'` +NETCDF_LIB_PATH="`brew --prefix netcdf`/lib" +HDF5_LIB_PATH="`brew --prefix hdf5`/lib" +NETCDFF_LIB_PATH="$NETCDFF_PREFIX/lib" + +FPM_FLAG="-cpp -DUSE_ASSERTIONS=.true." +FPM_FLAG=" $FPM_FLAG -fallow-argument-mismatch -ffree-line-length-none" +FPM_FLAG=" $FPM_FLAG -DVERSION=\\\'$GIT_VERSION\\\'" +FPM_FLAG=" $FPM_FLAG -L$NETCDF_LIB_PATH -L$HDF5_LIB_PATH -L$NETCDFF_LIB_PATH" +FPM_FC="$FC" +FPM_CC="$CC" + +if [ $CI = true ]; then + PKG_CONFIG_PATH=`realpath ./build/pkgconfig` + echo "---------------" + echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH" + echo "---------------" +else + PKG_CONFIG_PATH=`realpath "$PREFIX"/lib/pkgconfig` +fi + +if [ ! -d $PKG_CONFIG_PATH ]; then + mkdir -p $PKG_CONFIG_PATH +fi + +INFERENCE_ENGINE_PC="$PKG_CONFIG_PATH/inference-engine.pc" +echo "INFERENCE_ENGINE_FPM_CXX=\"$CXX\"" > $INFERENCE_ENGINE_PC +echo "INFERENCE_ENGINE_FPM_CC=\"$FPM_CC\"" >> $INFERENCE_ENGINE_PC +echo "INFERENCE_ENGINE_FPM_FC=\"$FPM_FC\"" >> $INFERENCE_ENGINE_PC +echo "INFERENCE_ENGINE_FPM_FLAG=\"$FPM_FLAG\"" >> $INFERENCE_ENGINE_PC +echo "Name: inference-engine" >> $INFERENCE_ENGINE_PC +echo "Description: Inference Engine" >> $INFERENCE_ENGINE_PC +echo "URL: https://github.com/berkeleylab/inference-engine" >> $INFERENCE_ENGINE_PC +echo "Version: 0.1.0" >> $INFERENCE_ENGINE_PC +if [ $CI = true ]; then + echo "---------------" + echo "cat $INFERENCE_ENGINE_PC" + cat $INFERENCE_ENGINE_PC + echo "---------------" +fi + +export PKG_CONFIG_PATH +RUN_FPM_SH="`realpath ./build/run-fpm.sh`" +echo "#!/bin/sh" > $RUN_FPM_SH +echo "#-- DO NOT EDIT -- created by inference-engine/install.sh" >> $RUN_FPM_SH +echo "export PKG_CONFIG_PATH" >> $RUN_FPM_SH +echo "`brew --prefix fpm`/bin/fpm \$@ --verbose \\" >> $RUN_FPM_SH +echo "--profile debug \\" >> $RUN_FPM_SH +echo "--c-compiler \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_CC`\" \\" >> $RUN_FPM_SH +echo "--compiler \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_FC`\" \\" >> $RUN_FPM_SH +echo "--flag \"`pkg-config inference-engine --variable=INFERENCE_ENGINE_FPM_FLAG`\"" >> $RUN_FPM_SH +chmod u+x $RUN_FPM_SH +if [ $CI = true ]; then + echo "---------------" + echo "cat $RUN_FPM_SH" + cat $RUN_FPM_SH + echo "---------------" +fi + +if command -v fpm > /dev/null 2>&1; then + brew tap fortran-lang/fortran + brew install fpm +fi + +echo "$RUN_FPM_SH test" +$RUN_FPM_SH test + +echo "" +echo "______________ Inference-Engine has been installed! ______________" +echo "" +echo "To use use or test with Inference-Engine with the Fortran Package" +echo "Manager (fpm) and using the required compiler/linker flags, pass" +echo "a fpm command to the build/run-fpm.sh script. For example, to" +echo "run one of the provided example programs with a path of the form" +echo "example/.f90, enter a command of the following form at " +echo "a shell command prompt:" +echo "" +echo "./build/run-fpm.sh run --example "