From 9c152e45f12c5d080af6d694cb858e36347d3fcd Mon Sep 17 00:00:00 2001
From: thopri <thopri@144-148.noc.soton.ac.uk>
Date: Fri, 6 Mar 2020 13:44:01 +0000
Subject: [PATCH] added first stage unit testing code

---
 pynemo/gui/nemo_bdy_mask.py         |  29 ++++++--
 pynemo/nemo_bdy_ncpop.py            |   2 +-
 pynemo/profile.py                   |   6 --
 pynemo_37.yml                       |   2 +
 unit_tests/namelist_unit_test.bdy   | 109 ++++++++++++++++++++++++++++
 unit_tests/src_data_unit_tests.ncml |  16 ++++
 unit_tests/unit_test.py             |  45 ++++++++++++
 7 files changed, 196 insertions(+), 13 deletions(-)
 create mode 100644 unit_tests/namelist_unit_test.bdy
 create mode 100644 unit_tests/src_data_unit_tests.ncml
 create mode 100644 unit_tests/unit_test.py

diff --git a/pynemo/gui/nemo_bdy_mask.py b/pynemo/gui/nemo_bdy_mask.py
index ea062ab..ef5bd24 100644
--- a/pynemo/gui/nemo_bdy_mask.py
+++ b/pynemo/gui/nemo_bdy_mask.py
@@ -68,15 +68,32 @@ class Mask(object):
             self.bathymetry_file = str(bathy_file)
             #open the bathymetry file
             self.bathy_nc = Dataset(self.bathymetry_file)
-            self.lon = np.asarray(self.bathy_nc.variables['nav_lon'])
-            self.lat = np.asarray(self.bathy_nc.variables['nav_lat'])
-            self.bathy_data = self.bathy_nc.variables['Bathymetry'][:,:]
+            try:
+                self.lon = np.asarray(self.bathy_nc.variables['nav_lon'])
+                self.lat = np.asarray(self.bathy_nc.variables['nav_lat'])
+            except:
+                self.lon = np.asarray(self.bathy_nc.variables['lon'])
+                self.lat = np.asarray(self.bathy_nc.variables['lat'])
+                # expand lat and lon 1D arrays into 2D array matching nav_lat nav_lon
+                self.lon = np.tile(self.lon, (np.shape(self.lat)[0], 1))
+                self.lat = np.tile(self.lat, (np.shape(self.lon)[1], 1))
+                self.lat = np.rot90(self.lat)
+
+            try:
+                self.bathy_data = self.bathy_nc.variables['Bathymetry'][:,:]
+            except:
+                self.bathy_data = self.bathy_nc.variables['deptho'][:,:]
             try: #check if units exists otherwise unknown. TODO
                 self.data_units = self.bathy_nc.variables['Bathymetry'].units
-            except AttributeError:
-                self.data_units = "unknown"
+            except:
+                self.data_units = self.bathy_nc.variables['deptho'].units
+#            except AttributeError:
+#                self.data_units = "unknown"
             if self.data is None:
-                self.data = self.bathy_nc.variables['Bathymetry']
+                try:
+                    self.data = self.bathy_nc.variables['Bathymetry']
+                except:
+                    self.data = self.bathy_nc.variables['deptho']
                 self.data = np.asarray(self.data[:, :])
                 self.data = np.around((self.data + .5).clip(0, 1))
                 #apply default 1px border
diff --git a/pynemo/nemo_bdy_ncpop.py b/pynemo/nemo_bdy_ncpop.py
index 45223ab..1f55c5d 100644
--- a/pynemo/nemo_bdy_ncpop.py
+++ b/pynemo/nemo_bdy_ncpop.py
@@ -19,7 +19,7 @@ def write_data_to_file(filename, variable_name, data):
     count = data.shape
 
     three_dim_variables = ['votemper', 'vosaline', 'N1p', 'N3n', 'N5s','thetao','so']
-    two_dim_variables = ['sossheig', 'vobtcrtx', 'vobtcrty', 'iicethic', 'ileadfra', 'isnowthi']
+    two_dim_variables = ['sossheig', 'vobtcrtx', 'vobtcrty', 'iicethic', 'ileadfra', 'isnowthi','zos']
 
     if variable_name in three_dim_variables:
         if len(count) == 3:
diff --git a/pynemo/profile.py b/pynemo/profile.py
index 1dc8b28..7931c68 100644
--- a/pynemo/profile.py
+++ b/pynemo/profile.py
@@ -77,12 +77,6 @@ logging.basicConfig(filename='nrct.log', level=logging.INFO)
 # define a Handler which writes INFO messages or higher to the sys.stderr
 console = logging.StreamHandler()
 console.setLevel(logging.INFO)
-# set a format which is simpler for console use
-formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
-# tell the handler to use this format
-console.setFormatter(formatter)
-# add the handler to the root logger
-logging.getLogger('').addHandler(console)
 
 def download_cmems(setup_filepath=0):
     '''
diff --git a/pynemo_37.yml b/pynemo_37.yml
index 8c72840..8d24b7f 100644
--- a/pynemo_37.yml
+++ b/pynemo_37.yml
@@ -9,9 +9,11 @@ dependencies:
   - python=3.7.6
   - pip=20.0.2
   - pandas=1.0.1
+  - pytest=5.3.5
   - pip:
     - idna==2.9
     - lxml==4.5.0
     - pyjnius==1.2.1
     - seawater==3.3.4
     - thredds-crawler==1.5.4
+    - motuclient==1.8.4
\ No newline at end of file
diff --git a/unit_tests/namelist_unit_test.bdy b/unit_tests/namelist_unit_test.bdy
new file mode 100644
index 0000000..48746cf
--- /dev/null
+++ b/unit_tests/namelist_unit_test.bdy
@@ -0,0 +1,109 @@
+!!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+!! NEMO/OPA  : namelist for BDY generation tool
+!!            
+!!             User inputs for generating open boundary conditions
+!!             employed by the BDY module in NEMO. Boundary data
+!!             can be set up for v3.2 NEMO and above.
+!!            
+!!             More info here.....
+!!            
+!!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+!------------------------------------------------------------------------------
+!   vertical coordinate
+!------------------------------------------------------------------------------
+   ln_zco      = .false.   !  z-coordinate - full    steps   (T/F)  
+   ln_zps      = .true.    !  z-coordinate - partial steps   (T/F)
+   ln_sco      = .false.   !  s- or hybrid z-s-coordinate    (T/F)
+   rn_hmin     =   -10     !  min depth of the ocean (>0) or 
+                           !  min number of ocean level (<0)
+
+!------------------------------------------------------------------------------
+!   s-coordinate or hybrid z-s-coordinate
+!------------------------------------------------------------------------------
+   rn_sbot_min =   10.     !  minimum depth of s-bottom surface (>0) (m)
+   rn_sbot_max = 7000.     !  maximum depth of s-bottom surface 
+                           !  (= ocean depth) (>0) (m)
+   ln_s_sigma  = .false.   !  hybrid s-sigma coordinates
+   rn_hc       =  150.0    !  critical depth with s-sigma
+
+!------------------------------------------------------------------------------
+!  grid information 
+!------------------------------------------------------------------------------
+   sn_src_hgr = '/Users/thopri/Projects/PyNEMO/inputs/subset_coordinates.nc'
+   sn_src_zgr = '/Users/thopri/Projects/PyNEMO/inputs/subset_coordinates.nc'
+   sn_dst_hgr = 'http://opendap4gws.jasmin.ac.uk/thredds/noc_msm/dodsC/pynemo_grid_C/mesh_hgr_zps.nc'
+   sn_dst_zgr = 'http://opendap4gws.jasmin.ac.uk/thredds/noc_msm/dodsC/pynemo_grid_C/mesh_zgr_zps.nc'
+   sn_src_msk = '/Users/thopri/Projects/PyNEMO/inputs/subset_bathy.nc'
+   sn_bathy   = 'http://opendap4gws.jasmin.ac.uk/thredds/noc_msm/dodsC/pynemo_grid_C/NNA_R12_bathy_meter_bench.nc'
+
+!------------------------------------------------------------------------------
+!  I/O 
+!------------------------------------------------------------------------------
+   sn_src_dir = '/Users/thopri/Projects/PyNEMO/unit_tests/src_data_unit_tests.ncml' ! src_files/'
+   sn_dst_dir = '/Users/thopri/Projects/PyNEMO/unit_tests/test_outputs'
+   sn_fn      = 'unit_test'             ! prefix for output files
+   nn_fv      = -1e20                 !  set fill value for output files
+   nn_src_time_adj = 0                ! src time adjustment
+   sn_dst_metainfo = 'Benchmarking Data'
+
+!------------------------------------------------------------------------------
+!  CMEMS Data Source Configuration
+!------------------------------------------------------------------------------
+   ln_use_cmems             = .true.
+
+!------------------------------------------------------------------------------
+!  unstructured open boundaries                         
+!------------------------------------------------------------------------------
+    ln_coords_file = .true.               !  =T : produce bdy coordinates files
+    cn_coords_file = 'coordinates.bdy.nc' !  name of bdy coordinates files 
+                                          !  (if ln_coords_file=.TRUE.)
+    ln_mask_file   = .false.              !  =T : read mask from file
+    cn_mask_file   = 'mask.nc'            !  name of mask file 
+                                          !  (if ln_mask_file=.TRUE.)
+    ln_dyn2d       = .false.              !  boundary conditions for 
+                                          !  barotropic fields
+    ln_dyn3d       = .false.              !  boundary conditions for 
+                                          !  baroclinic velocities
+    ln_tra         = .true.               !  boundary conditions for T and S
+    ln_ice         = .false.              !  ice boundary condition   
+    nn_rimwidth    = 9                    !  width of the relaxation zone
+
+!------------------------------------------------------------------------------
+!  unstructured open boundaries tidal parameters                        
+!------------------------------------------------------------------------------
+    ln_tide        = .false.              !  =T : produce bdy tidal conditions
+    sn_tide_model  = 'FES'                !  Name of tidal model (FES|TPXO)
+    clname(1)      = 'M2'                 !  constituent name
+    clname(2)      = 'S2'         
+    clname(3)      = 'K2'        
+    ln_trans       = .true.               !  interpolate transport rather than
+                                          !  velocities
+!------------------------------------------------------------------------------
+!  Time information
+!------------------------------------------------------------------------------
+    nn_year_000     = 2017        !  year start
+    nn_year_end     = 2017        !  year end
+    nn_month_000    = 01          !  month start (default = 1 is years>1)
+    nn_month_end    = 01          !  month end (default = 12 is years>1)
+    sn_dst_calendar = 'gregorian' !  output calendar format
+    nn_base_year    = 1960        !  base year for time counter
+	sn_tide_grid   = './src_data/tide/grid_tpxo7.2.nc'
+	sn_tide_h      = './src_data/tide/h_tpxo7.2.nc'
+	sn_tide_u      = './src_data/tide/u_tpxo7.2.nc'
+	
+!------------------------------------------------------------------------------
+!  Additional parameters
+!------------------------------------------------------------------------------
+    nn_wei  = 1                   !  smoothing filter weights 
+    rn_r0   = 0.041666666         !  decorrelation distance use in gauss
+                                  !  smoothing onto dst points. Need to 
+                                  !  make this a funct. of dlon
+    sn_history  = 'Benchmarking test case'
+                                  !  history for netcdf file
+    ln_nemo3p4  = .true.          !  else presume v3.2 or v3.3
+    nn_alpha    = 0               !  Euler rotation angle
+    nn_beta     = 0               !  Euler rotation angle
+    nn_gamma    = 0               !  Euler rotation angle
+	rn_mask_max_depth = 100.0     !  Maximum depth to be ignored for the mask
+	rn_mask_shelfbreak_dist = 20000.0 !  Distance from the shelf break
diff --git a/unit_tests/src_data_unit_tests.ncml b/unit_tests/src_data_unit_tests.ncml
new file mode 100644
index 0000000..2b91153
--- /dev/null
+++ b/unit_tests/src_data_unit_tests.ncml
@@ -0,0 +1,16 @@
+<ns0:netcdf xmlns:ns0="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2" title="NEMO aggregation">
+  <ns0:aggregation type="union">
+    <ns0:netcdf>
+      <ns0:aggregation dimName="time" name="temperature" type="joinExisting">
+        <ns0:scan location="file://Users/thopri/Projects/PyNEMO/unit_tests/test_data/" regExp=".*T\.nc$" />
+      </ns0:aggregation>
+    </ns0:netcdf>
+    <ns0:netcdf>
+      <ns0:aggregation dimName="time" name="sea_surface_height" type="joinExisting">
+        <ns0:scan location="file://Users/thopri/Projects/PyNEMO/unit_tests/test_data/" regExp=".*T\.nc$" />
+      </ns0:aggregation>
+    </ns0:netcdf>
+  </ns0:aggregation>
+  <ns0:variable name="thetao" orgName="thetao" />
+  <ns0:variable name="zos" orgName="zos" />
+</ns0:netcdf>
diff --git a/unit_tests/unit_test.py b/unit_tests/unit_test.py
new file mode 100644
index 0000000..bbfe4a0
--- /dev/null
+++ b/unit_tests/unit_test.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+"""
+Set of test functions to test PyNEMO functionality.
+
+"""
+from subprocess import Popen, PIPE
+from netCDF4 import Dataset
+import numpy as np
+import glob
+import os
+
+def test_run():
+    stdout, stderr = Popen(['pynemo','-s','unit_tests/namelist_unit_test.bdy'], stdout=PIPE, stderr=PIPE,
+                           universal_newlines=True).communicate()
+    assert 'Execution Time' in stdout
+
+def test_temp():
+    test_files = glob.glob('unit_tests/test_outputs/unit_test*')
+    for t in test_files:
+        results = Dataset(t) # open results
+        temp = results['thetao'][:]
+        results.close()
+        temp_ = np.ma.masked_array(temp,temp == -32767.0)
+        assert abs(temp_[temp_!=0.0].mean() - 15) <= 0.001
+        assert abs(temp_[temp_ != 0.0].max() - 15) <= 0.001
+        assert abs(temp_[temp_ != 0.0].min() - 15) <= 0.001
+
+#def test_salinty():
+#    test_files = glob.glob('unit_tests/test_outputs/unit_test*')
+#    for t in test_files:
+#        results = Dataset(t)  # open results
+#        sal = results['so'][:]
+#        results.close()
+#        sal_ = np.ma.masked_array(sal,sal == -32767.0)
+#        assert abs(sal_[sal_!=0.0].mean() - 35) <= 0.001
+#        assert abs(sal_[sal_ != 0.0].max() - 35) <= 0.001
+#        assert abs(sal_[sal_ != 0.0].min() - 35) <= 0.001
+
+def test_rm_output():
+    files = glob.glob('unit_tests/test_outputs/*')
+    for f in files:
+        os.remove(f)
+    files = glob.glob('unit_tests/test_outputs/*')
+    assert len(files) == 0
+
-- 
GitLab