def test_getNotRectangle_lon1leLon2Lat1leLat2(self): """ Tests that not_rectangle is True and False in the grid cells expected according to the user-specified lon_1, lon_2, lat_1, lat_2 When lon_1 <= lon_2 and lat_1 <= lat_2, expect not_rectangle to be False in a rectangle bounded by these lon/lat values Work with integer lon/lat values to keep the testing simple """ # get longxy, latixy that would normally come from an fsurdat file # self._get_longxy_latixy will convert -180 to 180 to 0-360 longitudes # get cols, rows also min_lon = 2 # expects min_lon < max_lon min_lat = 3 # expects min_lat < max_lat longxy, latixy, cols, rows = self._get_longxy_latixy( _min_lon=min_lon, _max_lon=7, _min_lat=min_lat, _max_lat=8) # get not_rectangle from user-defined lon_1, lon_2, lat_1, lat_2 lon_1 = 3 lon_2 = 5 # lon_1 < lon_2 lat_1 = 6 lat_2 = 8 # lat_1 < lat_2 not_rectangle = ModifyFsurdat._get_not_rectangle( lon_1=lon_1, lon_2=lon_2, lat_1=lat_1, lat_2=lat_2, longxy=longxy, latixy=latixy) compare = np.ones((rows,cols)) # assert this to confirm intuitive understanding of these matrices self.assertEqual(np.size(not_rectangle), np.size(compare)) # Hardwire where I expect not_rectangle to be False (0) # I have chosen the lon/lat ranges to match their corresponding index # values to keep this simple compare[lat_1-min_lat:lat_2-min_lat+1, lon_1-min_lon:lon_2-min_lon+1] = 0 np.testing.assert_array_equal(not_rectangle, compare)
def test_getNotRectangle_latsOutOfBounds(self): """ Tests that out-of-bound latitude values abort with message Out-of-bound longitudes already tested in test_unit_utils.py """ # get longxy, latixy that would normally come from an fsurdat file # self._get_longxy_latixy will convert -180 to 180 to 0-360 longitudes # get cols, rows also min_lon = 0 # expects min_lon < max_lon min_lat = -5 # expects min_lat < max_lat longxy, latixy, _, _ = self._get_longxy_latixy( _min_lon=min_lon, _max_lon=359, _min_lat=min_lat, _max_lat=5) # get not_rectangle from user-defined lon_1, lon_2, lat_1, lat_2 # I have chosen the lon/lat ranges to match their corresponding index # values to keep this simple (see usage below) lon_1 = 355 lon_2 = 5 lat_1 = -91 lat_2 = 91 with self.assertRaisesRegex(SystemExit, "lat_1 and lat_2 need to be in the range -90 to 90"): _ = ModifyFsurdat._get_not_rectangle( lon_1=lon_1, lon_2=lon_2, lat_1=lat_1, lat_2=lat_2, longxy=longxy, latixy=latixy)
def test_setvarLev(self): """ Tests that setvar_lev0, setvar_lev1, and setvar_lev2 update values of variables within a rectangle defined by user-specified lon_1, lon_2, lat_1, lat_2 """ # get longxy, latixy that would normally come from an fsurdat file # self._get_longxy_latixy will convert -180 to 180 to 0-360 longitudes # get cols, rows also min_lon = 2 # expects min_lon < max_lon min_lat = 3 # expects min_lat < max_lat longxy, latixy, cols, rows = self._get_longxy_latixy( _min_lon=min_lon, _max_lon=10, _min_lat=min_lat, _max_lat=12) # get not_rectangle from user-defined lon_1, lon_2, lat_1, lat_2 lon_1 = 3 lon_2 = 5 # lon_1 < lon_2 lat_1 = 5 lat_2 = 7 # lat_1 < lat_2 # create xarray dataset containing lev0, lev1, and lev2 variables; # the fsurdat_modify tool reads variables like this from fsurdat file var_1d = np.arange(cols) var_lev2 = var_1d * np.ones((rows,cols,rows,cols)) var_lev1 = var_1d * np.ones((cols,rows,cols)) my_data = xr.Dataset(data_vars=dict( LONGXY=(["x", "y"], longxy), # use LONGXY as var_lev0 LATIXY=(["x", "y"], latixy), # __init__ expects LONGXY, LATIXY var_lev1=(["w", "x", "y"], var_lev1), var_lev2=(["v", "w", "x", "y"], var_lev2))) # create ModifyFsurdat object modify_fsurdat = ModifyFsurdat(my_data=my_data, lon_1=lon_1, lon_2=lon_2, lat_1=lat_1, lat_2=lat_2, landmask_file=None) # initialize and then modify the comparison matrices comp_lev0 = modify_fsurdat.file.LONGXY comp_lev1 = modify_fsurdat.file.var_lev1 comp_lev2 = modify_fsurdat.file.var_lev2 val_for_rectangle = 1.5 comp_lev0[lat_1-min_lat:lat_2-min_lat+1, lon_1-min_lon:lon_2-min_lon+1] = val_for_rectangle comp_lev1[...,lat_1-min_lat:lat_2-min_lat+1, lon_1-min_lon:lon_2-min_lon+1] = val_for_rectangle comp_lev2[...,lat_1-min_lat:lat_2-min_lat+1, lon_1-min_lon:lon_2-min_lon+1] = val_for_rectangle # test setvar modify_fsurdat.setvar_lev0('LONGXY', val_for_rectangle) np.testing.assert_array_equal(modify_fsurdat.file.LONGXY, comp_lev0) modify_fsurdat.setvar_lev1('var_lev1', val_for_rectangle, cols-1) np.testing.assert_array_equal(modify_fsurdat.file.var_lev1, comp_lev1) modify_fsurdat.setvar_lev2('var_lev2', val_for_rectangle, cols-1, rows-1) np.testing.assert_array_equal(modify_fsurdat.file.var_lev2, comp_lev2)
def test_getNotRectangle_lonsStraddle0deg(self): """ Tests that not_rectangle is True and False in the grid cells expected according to the user-specified lon_1, lon_2, lat_1, lat_2 When lon_1 > lon_2 and lat_1 > lat_2, expect not_rectangle to be False in four rectangles bounded by these lon/lat values, in the top left, top right, bottom left, and bottom right of the domain Work with integer lon/lat values to keep the testing simple """ # get longxy, latixy that would normally come from an fsurdat file # self._get_longxy_latixy will convert -180 to 180 to 0-360 longitudes # get cols, rows also min_lon = 0 # expects min_lon < max_lon min_lat = -5 # expects min_lat < max_lat longxy, latixy, cols, rows = self._get_longxy_latixy(_min_lon=min_lon, _max_lon=359, _min_lat=min_lat, _max_lat=5) # get not_rectangle from user-defined lon_1, lon_2, lat_1, lat_2 # I have chosen the lon/lat ranges to match their corresponding index # values to keep this simple (see usage below) lon_1 = 355 lon_2 = 5 # lon_1 > lon_2 lat_1 = -4 lat_2 = -6 # lat_1 > lat_2 not_rectangle = ModifyFsurdat._get_not_rectangle( lon_1=lon_1, lon_2=lon_2, lat_1=lat_1, lat_2=lat_2, longxy=longxy, latixy=latixy, ) compare = np.ones((rows, cols)) # assert this to confirm intuitive understanding of these matrices self.assertEqual(np.size(not_rectangle), np.size(compare)) # Hardwire where I expect not_rectangle to be False (0) # I have chosen the lon/lat ranges to match their corresponding index # values to keep this simple compare[:lat_2 - min_lat + 1, :lon_2 - min_lon + 1] = 0 compare[:lat_2 - min_lat + 1, lon_1 - min_lon:] = 0 compare[lat_1 - min_lat:, :lon_2 - min_lon + 1] = 0 compare[lat_1 - min_lat:, lon_1 - min_lon:] = 0 np.testing.assert_array_equal(not_rectangle, compare)
def fsurdat_modifier(cfg_path): """Implementation of fsurdat_modifier command""" # read the .cfg (config) file config = ConfigParser() config.read(cfg_path) section = config.sections()[0] # name of the first section # required: user must set these in the .cfg file fsurdat_in = get_config_value(config=config, section=section, item='fsurdat_in', file_path=cfg_path) fsurdat_out = get_config_value(config=config, section=section, item='fsurdat_out', file_path=cfg_path) # required but fallback values available for variables omitted # entirely from the .cfg file idealized = get_config_value(config=config, section=section, item='idealized', file_path=cfg_path, convert_to_type=bool) zero_nonveg = get_config_value(config=config, section=section, item='zero_nonveg', file_path=cfg_path, convert_to_type=bool) lnd_lat_1 = get_config_value(config=config, section=section, item='lnd_lat_1', file_path=cfg_path, convert_to_type=float) lnd_lat_2 = get_config_value(config=config, section=section, item='lnd_lat_2', file_path=cfg_path, convert_to_type=float) lnd_lon_1 = get_config_value(config=config, section=section, item='lnd_lon_1', file_path=cfg_path, convert_to_type=float) lnd_lon_2 = get_config_value(config=config, section=section, item='lnd_lon_2', file_path=cfg_path, convert_to_type=float) landmask_file = get_config_value(config=config, section=section, item='landmask_file', file_path=cfg_path, can_be_unset=True) # not required: user may set these in the .cfg file dom_nat_pft = get_config_value(config=config, section=section, item='dom_nat_pft', file_path=cfg_path, allowed_values=range(15), # integers from 0 to 14 convert_to_type=int, can_be_unset=True) lai = get_config_value(config=config, section=section, item='lai', file_path=cfg_path, is_list=True, convert_to_type=float, can_be_unset=True) sai = get_config_value(config=config, section=section, item='sai', file_path=cfg_path, is_list=True, convert_to_type=float, can_be_unset=True) hgt_top = get_config_value(config=config, section=section, item='hgt_top', file_path=cfg_path, is_list=True, convert_to_type=float, can_be_unset=True) hgt_bot = get_config_value(config=config, section=section, item='hgt_bot', file_path=cfg_path, is_list=True, convert_to_type=float, can_be_unset=True) soil_color = get_config_value(config=config, section=section, item='soil_color', file_path=cfg_path, allowed_values=range(1, 21), # integers from 1 to 20 convert_to_type=int, can_be_unset=True) std_elev = get_config_value(config=config, section=section, item='std_elev', file_path=cfg_path, convert_to_type=float, can_be_unset=True) max_sat_area = get_config_value(config=config, section=section, item='max_sat_area', file_path=cfg_path, convert_to_type=float, can_be_unset=True) # Create ModifyFsurdat object modify_fsurdat = ModifyFsurdat.init_from_file(fsurdat_in, lnd_lon_1, lnd_lon_2, lnd_lat_1, lnd_lat_2, landmask_file) # ------------------------------ # modify surface data properties # ------------------------------ # Set fsurdat variables in a rectangle that could be global (default). # Note that the land/ocean mask gets specified in the domain file for # MCT or the ocean mesh files for NUOPC. Here the user may specify # fsurdat variables inside a box but cannot change which points will # run as land and which as ocean. if idealized: modify_fsurdat.set_idealized() # set 2D variables # set 3D and 4D variables pertaining to natural vegetation modify_fsurdat.set_dom_nat_pft(dom_nat_pft=0, lai=[], sai=[], hgt_top=[], hgt_bot=[]) if dom_nat_pft is not None: # overwrite "idealized" value modify_fsurdat.set_dom_nat_pft(dom_nat_pft=dom_nat_pft, lai=lai, sai=sai, hgt_top=hgt_top, hgt_bot=hgt_bot) if max_sat_area is not None: # overwrite "idealized" value modify_fsurdat.setvar_lev0('FMAX', max_sat_area) if std_elev is not None: # overwrite "idealized" value modify_fsurdat.setvar_lev0('STD_ELEV', std_elev) if soil_color is not None: # overwrite "idealized" value modify_fsurdat.setvar_lev0('SOIL_COLOR', soil_color) if zero_nonveg: modify_fsurdat.zero_nonveg() # ---------------------------------------------- # Output the now modified CTSM surface data file # ---------------------------------------------- modify_fsurdat.write_output(fsurdat_in, fsurdat_out)