Skip to content

Commit

Permalink
Merge pull request #14 from BerkeleyLab/netcdf-read-write-example
Browse files Browse the repository at this point in the history
Netcdf read/write example
  • Loading branch information
rouson authored Nov 16, 2022
2 parents 1136bed + 887677c commit a1089ce
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 20 deletions.
25 changes: 5 additions & 20 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
114 changes: 114 additions & 0 deletions example/netCDF_IO.f90
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions fpm.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ maintainer = "[email protected]"
[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"}
165 changes: 165 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -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/<name>.f90, enter a command of the following form at "
echo "a shell command prompt:"
echo ""
echo "./build/run-fpm.sh run --example <name>"

0 comments on commit a1089ce

Please sign in to comment.