# Group hackathon on interpolation code (Fortran) ## Compilation - Code compiled and run on laptop with gfortran on Ubuntu. - But not easy for everyone to compile locally. - Tried on cocal but did not work very well - Im not so familiar with cocalc - so he problem is not cocalc ## Share the code & development in Github ### Github username - annefou - maryjanebopape - pmulovhedzi - leeseetja - NohlahlaDlamini Create a github repository under github.com/annefou https://github.com/annefou/hackathon-south-africa ## Compile and run the code - Open cocalc.com and create a terminal - let us know when you are done. - annefou done. - leeseetja done - NohlahlaDlamini done - maryjanebopape done - console.cloud.google.com seems to be free and we can run - open console and clone repository - annefou done. - leeseetja done. - NohlahlaDlamini done Try out to update the console: ``` sudo apt-get update sudo apt-get install -y gcc gfortran ``` Get the code: ``` git clone https://github.com/annefou/hackathon-south-africa cd hackathon-south-africa ``` Then compile and run the code: ``` gfortran interpolate_sub.f90 -o interpolate.x ``` And to run ``` ./interpolate.x ``` ## Installation of pFUnit (Fortran unit testing) You may need to install additional packages (on ubuntu): ``` sudo apt-get install cmake make python3 m4 ``` And then ``` cd $HOME git clone https://github.com/Goddard-Fortran-Ecosystem/pFUnit.git cd pFUnit mkdir build cd build cmake .. make make tests make install ``` ## Test pFUnit with "custom" code - Take the test from CodeRefinery test lesson: https://coderefinery.github.io/testing/ Let's try an existing Fortran test with pfUnit: https://coderefinery.github.io/testing/test-design/ We create test-cr.f90 (use any of your favourite editor vi, nano, etc.) with: ``` module factorial_mod contains ! computes the factorial of n integer function factorial(n) implicit none integer, intent(in) :: n integer r integer i if(n < 0) then write(*,*) 'Received negative input' stop end if r = 1 do i = 1,n r = r*i end do factorial=r end function factorial end module factorial_mod ``` Compile: ``` gfortran -c test.cr.f90 ``` Action: Anne to create a makefile to test the code (and that can be reused for interpolate_sub.f90) ## Adding test for interpolate_sub.f90 What kind of tests should we do? - unrealistic temperatures Plan: - implement one simple test - Add github action to automate testing - Create and implement more tests ## Hackathon day - Create mybinder environment & badge snippet: - go to https://mybinder.org/ - and fill information as shown below e.g. fill the **Github repository URL**: https://github.com/annefou/hackathon-south-africa ![](https://i.imgur.com/YNHwK2g.png) - test mybinder to create test environment - Does it work for you? - Open your web browser and go to https://github.com/annefou/hackathon-south-africa ![](https://i.imgur.com/KTck5Hs.png) - mybinder documentation: https://mybinder.readthedocs.io/en/latest/introduction.html When compiling with mybinder terminal/console, we use `$FC` environment variable to compile (because the compiler name is weird/difficult to remember `/srv/conda/envs/notebook/bin/x86_64-conda-linux-gnu-gfortran`) ``` $FC interpolate_sub.f90 -o interpolate.x ``` ## Testing/Using pFUnit There is a very nice repository with examples on how to run/use pFUNIT: https://github.com/Goddard-Fortran-Ecosystem/pFUnit_demos ### Clone the pFUNIT demo repository ``` git clone https://github.com/Goddard-Fortran-Ecosystem/pFUnit_demos.git ``` We will now test one example in Trivial folder: ``` cd pFUnit_demos/Trivial ``` Edit the Makefile and add `-lgomp` in `my_tests_OTHER_LIBRARIES`: ``` SRCS := square.F90 OBJS := $(SRCS:%.F90=%.o) all: my_tests libsut.a: $(OBJS) $(AR) -r $@ $? ifeq (nagfor,$(findstring nagfor,$(FC))) FFLAGS += -fpp endif %.o : %.F90 $(FC) -c $(FFLAGS) $< LATEST_PFUNIT_DIR := $(lastword $(shell echo $(wildcard $(PFUNIT_DIR)/PFUNIT-4.*) | xargs -n1 | sort -V)) include $(LATEST_PFUNIT_DIR)/include/PFUNIT.mk FFLAGS += $(PFUNIT_EXTRA_FFLAGS) test_square.o: square.mod square.mod: square.o my_tests: libsut.a my_tests_TESTS := test_square.pf my_tests_REGISTRY := my_tests_OTHER_SOURCES := my_tests_OTHER_LIBRARIES := -L. -lsut -lgomp my_tests_OTHER_INCS := $(eval $(call make_pfunit_test,my_tests)) clean: $(RM) *.o *.mod *.a *.inc test_square.F90 ``` And then: First we need to set pFUNIT environment variable: ``` export PFUNIT_DIR=$HOME/packages ``` Then we can compile test and run it: ``` make clean make all ./my_tests ``` It should return: ``` Time: 0.001 seconds OK (1 test) ``` ### Exercice: make the test fail For instance, change square.F90: ``` module Square_mod contains pure real function square(x) real, intent(in) :: x square = x**3 end function square end module Square_mod ``` Then without changing the test, I check if my changes are correct: ``` make clean make ./my_tests ``` And it returns: ``` ./my_tests .F Time: 0.001 seconds Failure in: test_square_suite.test_square Location: [test_square.pf:6] square(3) AssertEqual failure: Expected: <9.00000000> Actual: <27.0000000> Difference: <18.0000000> (greater than tolerance of 0.00000000) FAILURES!!! Tests run: 1, Failures: 1, Errors: 0 , Disabled: 0 ERROR STOP *** Encountered 1 or more failures/errors during testing. *** Error termination. Backtrace: #0 0x55b0ec9d4b03 in __funit_MOD_finalize at /home/jovyan/pFUnit/src/funit/FUnit.F90:171 #1 0x55b0ec9e4def in funit_main_ at /home/jovyan/pFUnit/src/funit/funit_main.F90:17 #2 0x55b0ec98a10f in ??? #3 0x7f66166e2bf6 in ??? #4 0x55b0ec98a160 in ??? ``` The expected value is still 9.0 but now I got 27.0. so the changes I implemented in `square.F90` is not correct. ### Testing interpolate_sub.F90 1. Which test to add: we will check the subroutine INTERP and check that the values are "correct" BREAK: 10mn --> 11:45 SA Time Register your changes with git: git add interpolate_sub.f90 git add inter_mod.f90 Then we need to setup username and email address for git: configure git: ``` git config --global user.name "Your name here" git config --global user.email "your_email@example.com" ``` Sor for me: ``` git config --global user.name "Anne Fouilloux" git config --global user.email "annefou@geo.uio.no" ``` Then we "commit": ``` git commit -m "create a new Fortran module for interp subroutine" ``` On github: https://github.com/annefou/hackathon-south-africa we still do not have this change yet so we need to "push" the changes to github: ``` git push ``` Ask you for username (maryjanebopape) and password. Then it is updated on github. For everyone else, we need to update our local copy: ``` git pull ``` Do you get Mary-Jane's changes on your local "copy"? And we can now check our local copy works: Compile: ``` $FC Inter_mod.f90 interpolate_sub.f90 -o interpolate.x ``` And run: ``` ./interpolate.x ``` ### test for interp Values at 4 points: 29, 30, 31 and 32 Answer: 30.5 Latitude: south point: -33, north point: -31 Longitude: west point: 20, east point: 22 Station point: 21; -32 ## Hackathon cont. 30/04/2021 - start software environment with mybinder (takes a few minutes) - Open a terminal and then clone pFUnit_demos: ``` git clone https://github.com/Goddard-Fortran-Ecosystem/pFUnit_demos ``` - modify Inter_mod.f90 and replace subroutine by a function. ``` SUBROUTINE INTERP(I,J,K,TEMP,LONS,LATS,LONSTAT,LATSTAT,NEW) IMPLICIT NONE INTEGER, INTENT(IN) :: I INTEGER, INTENT(IN) :: J INTEGER, INTENT(IN) :: K REAL, INTENT(IN) :: TEMP(I-1:I,J-1:J) REAL, INTENT(IN) :: LONS(I-1:I) REAL, INTENT(IN) :: LATS(J-1:J) REAL, INTENT(IN) :: LONSTAT(K:K) REAL, INTENT(IN) :: LATSTAT(K:K) REAL, INTENT(OUT) :: NEW(K:K) ! REAL, INTENT(INOUT) :: LATSSTAT REAL :: VAR1 REAL :: VAR2 VAR1=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J-1)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I,J-1) VAR2=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I-1,J) NEW(K)=(LATS(J)-LATSTAT(K))/(LATS(J)-LATS(J-1))*VAR1+& (LATSTAT(K)-LATS(J-1))/(LATS(J)-LATS(J-1))*VAR2 END SUBROUTINE INTERP ``` replaced by ``` pure real function INTERP(I,J,K,TEMP,LONS,LATS,LONSTAT,LATSTAT) IMPLICIT NONE INTEGER, INTENT(IN) :: I INTEGER, INTENT(IN) :: J INTEGER, INTENT(IN) :: K REAL, INTENT(IN) :: TEMP(I-1:I,J-1:J) REAL, INTENT(IN) :: LONS(I-1:I) REAL, INTENT(IN) :: LATS(J-1:J) REAL, INTENT(IN) :: LONSTAT(K:K) REAL, INTENT(IN) :: LATSTAT(K:K) ! REAL, INTENT(INOUT) :: LATSSTAT REAL :: VAR1 REAL :: VAR2 VAR1=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J-1)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I,J-1) VAR2=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I-1,J) INTERP=(LATS(J)-LATSTAT(K))/(LATS(J)-LATS(J-1))*VAR1+& (LATSTAT(K)-LATS(J-1))/(LATS(J)-LATS(J-1))*VAR2 END Function INTERP ``` update test_Inter_mod.pf ``` @test subroutine test_Inter_mod() use Inter_mod use funit Implicit None Integer:: I=2 Integer:: J=2 Integer:: K=1 REAL :: TEMP(2,2) REAL :: LON(2) REAL :: LAT(2) REAL :: LONSTAT REAL :: LATSTAT TEMP(1,2)=29. TEMP(2,2)=30. TEMP(1,1)=31. TEMP(2,1)=32. LAT(2) = -31. LAT(1) = -33. LON(2) = 22. LON(1) = 20. LONSTAT = 21. LATSTAT = -32. !Values at 4 points: 29, 30, 31 and 32 !Answer: 30.5 !Latitude: south point: -33, north point: -31 !Longitude: west point: 20, east point: 22 !Station point: 21; -32 @assertEqual(30.5, INTERP(I,J,K,TEMP,LON,LAT,LONSTAT,LATSTAT), 'INTERP(I,J,K,TEMP,LON,LAT,LONSTAT,LATSTAT)') end subroutine test_Inter_mod ``` - copy and existing [makefile](https://raw.githubusercontent.com/Goddard-Fortran-Ecosystem/pFUnit_demos/main/Trivial/Makefile) from pFUNIT_demos ``` SRCS := Inter_mod.F90 OBJS := $(SRCS:%.F90=%.o) all: my_tests libsut.a: $(OBJS) $(AR) -r $@ $? ifeq (nagfor,$(findstring nagfor,$(FC))) FFLAGS += -fpp endif %.o : %.F90 $(FC) -c $(FFLAGS) $< LATEST_PFUNIT_DIR := $(lastword $(shell echo $(wildcard $(PFUNIT_DIR)/PFUNIT-4.*) | xargs -n1 | sort -V)) include $(LATEST_PFUNIT_DIR)/include/PFUNIT.mk FFLAGS += $(PFUNIT_EXTRA_FFLAGS) test_Inter_mod.o: Inter_mod.mod Inter_mod.mod: Inter_mod.o my_tests: libsut.a my_tests_TESTS := test_Inter_mod.pf my_tests_REGISTRY := my_tests_OTHER_SOURCES := my_tests_OTHER_LIBRARIES := -L. -lsut my_tests_OTHER_INCS := $(eval $(call make_pfunit_test,my_tests)) clean: $(RM) *.o *.mod *.a *.inc test_Inter_mod.F90 ``` - Try to compile ``` make ``` but we got an error because we need to define `PFUNIT_DIR` ``` export PFUNIT_DIR=/home/jovyan/packages/ ``` Failed with an error with lonstat because we did not define it as an array of 1. So let's update it: update test_Inter_mod.pf ``` @test subroutine test_Inter_mod() use Inter_mod use funit Implicit None Integer:: I=2 Integer:: J=2 Integer:: K=1 REAL :: TEMP(2,2) REAL :: LON(2) REAL :: LAT(2) REAL :: LONSTAT(1) REAL :: LATSTAT(1) TEMP(1,2)=29. TEMP(2,2)=30. TEMP(1,1)=31. TEMP(2,1)=32. LAT(2) = -31. LAT(1) = -33. LON(2) = 22. LON(1) = 20. LONSTAT = 21. LATSTAT = -32. !Values at 4 points: 29, 30, 31 and 32 !Answer: 30.5 !Latitude: south point: -33, north point: -31 !Longitude: west point: 20, east point: 22 !Station point: 21; -32 @assertEqual(30.5, INTERP(I,J,K,TEMP,LON,LAT,LONSTAT,LATSTAT), 'INTERP(I,J,K,TEMP,LON,LAT,LONSTAT,LATSTAT)') end subroutine test_Inter_mod ``` We also need to update makefile: ``` my_tests_OTHER_LIBRARIES := -L. -lsut -lgomp ``` but then the test is failing so we need to investigate why: ``` jovyan@jupyter-annefou-2dhackathon-2dsouth-2dafrica-2dn80iq21v:~$ ./my_tests .F Time: 0.000 seconds Failure in: test_Inter_mod_suite.test_Inter_mod Location: [test_Inter_mod.pf:33] INTERP(I,J,K,TEMP,LON,LAT,LONSTAT,LATSTAT) AssertEqual failure: Expected: <30.5000000> Actual: <30.2500000> Difference: <-0.250000000> (greater than tolerance of 0.00000000) FAILURES!!! Tests run: 1, Failures: 1, Errors: 0 , Disabled: 0 ERROR STOP *** Encountered 1 or more failures/errors during testing. *** Error termination. Backtrace: #0 0x558a95cfabfc in __funit_MOD_finalize at /home/jovyan/pFUnit/src/funit/FUnit.F90:171 #1 0x558a95d0aee8 in funit_main_ at /home/jovyan/pFUnit/src/funit/funit_main.F90:17 #2 0x558a95cb010f in ??? #3 0x7fa817b28bf6 in ??? #4 0x558a95cb0160 in ??? ``` We chose to test the function itself (it is a bit of a hack e.g. we had to remove pure, etc. but at least we can check the values) ``` MODULE INTER_MOD IMPLICIT NONE CONTAINS real function INTERP(I,J,K,TEMP,LONS,LATS,LONSTAT,LATSTAT) IMPLICIT NONE INTEGER, INTENT(IN) :: I INTEGER, INTENT(IN) :: J INTEGER, INTENT(IN) :: K REAL, INTENT(INOUT) :: TEMP(I-1:I,J-1:J) REAL, INTENT(INOUT) :: LONS(I-1:I) REAL, INTENT(INOUT) :: LATS(J-1:J) REAL, INTENT(INOUT) :: LONSTAT(K:K) REAL, INTENT(INOUT) :: LATSTAT(K:K) ! REAL, INTENT(INOUT) :: LATSSTAT REAL :: VAR1 REAL :: VAR2 TEMP(I-1,J)=29. TEMP(2,J)=30. TEMP(I-1,J-1)=31. TEMP(I,J-1)=32. LATS(J) = -31. LATS(J-1) = -33. LONS(I) = 22. LONS(I-1) = 20. LONSTAT(K) = 21. LATSTAT(K) = -32. VAR1=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J-1)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I,J-1) VAR2=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I-1,J) INTERP=(LATS(J)-LATSTAT(K))/(LATS(J)-LATS(J-1))*VAR1+& (LATSTAT(K)-LATS(J-1))/(LATS(J)-LATS(J-1))*VAR2 END Function INTERP END MODULE INTER_MOD ``` The value returned is still 30.25. Now we see that the interpolation may not be as simple as we though when we built the test. We chose to add a tolerance in our test. When interpolation to a station location, we could use different interpolation method but the value should be withi 0.25 C. ``` !@test subroutine test_Inter_mod() use Inter_mod use funit Implicit None Integer:: I=2 Integer:: J=2 Integer:: K=1 REAL :: TEMP(2,2) REAL :: LON(2) REAL :: LAT(2) REAL :: LONSTAT(1) REAL :: LATSTAT(1) TEMP(1,2)=29. TEMP(2,2)=30. TEMP(1,1)=31. TEMP(2,1)=32. LAT(2) = -31. LAT(1) = -33. LON(2) = 22. LON(1) = 20. LONSTAT = 21. LATSTAT = -32. !Values at 4 points: 29, 30, 31 and 32 !Answer: 30.5 !Latitude: south point: -33, north point: -31 !Longitude: west point: 20, east point: 22 !Station point: 21; -32 #line 33 "/home/jovyan/test_Inter_mod.pf" call assertEqual(30.5, INTERP(I,J,K,TEMP,LON,LAT,LONSTAT,LATSTAT), tolerance=0.25, & & location=SourceLocation( & & 'test_Inter_mod.pf', & & 33) ) if (anyExceptions()) return #line 34 "/home/jovyan/test_Inter_mod.pf" end subroutine test_Inter_mod module Wraptest_Inter_mod use FUnit implicit none private contains end module Wraptest_Inter_mod function test_Inter_mod_suite() result(suite) use FUnit use Wraptest_Inter_mod implicit none type (TestSuite) :: suite class (Test), allocatable :: t external test_Inter_mod suite = TestSuite('test_Inter_mod_suite') if(allocated(t)) deallocate(t) allocate(t, source=TestMethod('test_Inter_mod', test_Inter_mod)) call suite%addTest(t) end function test_Inter_mod_suite ``` Then it worked. When designing your test: - Think about the "impact" of the algorithm you choose on the final result. Here, we take 4 values of a variable and interpolate it to a station location. What interpolation algorithm would be valid? What is the tolerance? etc. - Make sure to comment your functions (add a link to a scientific paper or name the algorithm you use) Now let's clean Inter_mod.F90 ``` MODULE INTER_MOD IMPLICIT NONE CONTAINS pure real function INTERP(I,J,K,TEMP,LONS,LATS,LONSTAT,LATSTAT) IMPLICIT NONE INTEGER, INTENT(IN) :: I INTEGER, INTENT(IN) :: J INTEGER, INTENT(IN) :: K REAL, INTENT(IN) :: TEMP(I-1:I,J-1:J) REAL, INTENT(IN) :: LONS(I-1:I) REAL, INTENT(IN) :: LATS(J-1:J) REAL, INTENT(IN) :: LONSTAT(K:K) REAL, INTENT(IN) :: LATSTAT(K:K) REAL :: VAR1 REAL :: VAR2 VAR1=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J-1)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I,J-1) VAR2=(LONS(I)-LONSTAT(K))/(LONS(I)-LONS(I-1))*TEMP(I-1,J)+& (LONSTAT(K)-LONS(I-1))/(LONS(I)-LONS(I-1))*TEMP(I-1,J) INTERP=(LATS(J)-LATSTAT(K))/(LATS(J)-LATS(J-1))*VAR1+& (LATSTAT(K)-LATS(J-1))/(LATS(J)-LATS(J-1))*VAR2 PRINT*, 'INTERP = ', (LATS(J)-LATSTAT(K))/(LATS(J)-LATS(J-1))*VAR1+& (LATSTAT(K)-LATS(J-1))/(LATS(J)-LATS(J-1))*VAR2 END Function INTERP END MODULE INTER_MOD ```