clm5/python/ctsm/test/test_unit_fsurdat_modifier.py
2024-05-09 15:14:01 +08:00

343 lines
15 KiB
Python

#!/usr/bin/env python3
"""
Unit tests for fsurdat_modifier subroutines:
"""
import unittest
import os
import sys
import shutil
import tempfile
from configparser import ConfigParser
import xarray as xr
from ctsm import unit_testing
from ctsm.path_utils import path_to_ctsm_root
from ctsm.modify_input_files.fsurdat_modifier import fsurdat_modifier_arg_process
from ctsm.modify_input_files.fsurdat_modifier import read_cfg_subgrid
from ctsm.modify_input_files.fsurdat_modifier import read_cfg_option_control
from ctsm.modify_input_files.fsurdat_modifier import read_cfg_var_list
from ctsm.modify_input_files.fsurdat_modifier import check_no_subgrid_section
from ctsm.modify_input_files.fsurdat_modifier import check_no_varlist_section
from ctsm.modify_input_files.modify_fsurdat import ModifyFsurdat
# Allow test names that pylint doesn't like; otherwise hard to make them
# readable
# pylint: disable=invalid-name
# pylint: disable=protected-access
# Allow as many public methods as needed...
# pylint: disable=too-many-public-methods
# Allow all the instance attributes that we need
# pylint: disable=too-many-instance-attributes
class TestFSurdatModifier(unittest.TestCase):
"""Tests the fsurdat_modifier subroutines"""
def setUp(self):
"""Setup for trying out the methods"""
testinputs_path = os.path.join(path_to_ctsm_root(), "python/ctsm/test/testinputs")
self._cfg_file_path = os.path.join(testinputs_path, "modify_fsurdat_opt_sections.cfg")
self._testinputs_path = testinputs_path
self._fsurdat_in = os.path.join(
testinputs_path,
"surfdata_5x5_amazon_hist_16pfts_CMIP6_2000_c231031.nc",
)
self._previous_dir = os.getcwd()
self._tempdir = tempfile.mkdtemp()
self._fsurdat_out = os.path.join(self._tempdir, "fsurdat_out.nc")
sys.argv = [
"fsurdat_modifier",
self._cfg_file_path,
"-i",
self._fsurdat_in,
"-o",
self._fsurdat_out,
]
parser = fsurdat_modifier_arg_process()
self.cfg_path = str(parser.cfg_path)
self.config = ConfigParser()
self.config.read(self.cfg_path)
my_data = xr.open_dataset(self._fsurdat_in)
self.modify_fsurdat = ModifyFsurdat(
my_data=my_data,
lon_1=0.0,
lon_2=360.0,
lat_1=90.0,
lat_2=90.0,
landmask_file=None,
lat_dimname=None,
lon_dimname=None,
)
def tearDown(self):
"""
Remove temporary directory
"""
os.chdir(self._previous_dir)
shutil.rmtree(self._tempdir, ignore_errors=True)
def test_subgrid_and_idealized_fails(self):
"""test that subgrid and idealized fails gracefully"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "True")
self.config.set(section, "include_nonveg", "False")
self.config.set(section, "process_subgrid_section", "True")
self.config.set(section, "dom_pft", "UNSET")
with self.assertRaisesRegex(
SystemExit,
"idealized AND process_subgrid_section can NOT both be on, pick one or the other",
):
read_cfg_option_control(self.modify_fsurdat, self.config, section, self.cfg_path)
def test_dompft_and_splitcropland_fails(self):
"""test that setting dompft crop with evenly_split_cropland True fails gracefully"""
section = "modify_fsurdat_basic_options"
crop_pft = max(self.modify_fsurdat.file.natpft.values) + 1
self.config.set(section, "dom_pft", str(crop_pft))
self.config.set(section, "evenly_split_cropland", "True")
with self.assertRaisesRegex(
SystemExit,
"dom_pft must not be set to a crop PFT when evenly_split_cropland is True",
):
read_cfg_option_control(self.modify_fsurdat, self.config, section, self.cfg_path)
def test_optional_only_true_and_false(self):
"""test that optional settings can only be true or false"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "dom_pft", "1")
varlist = (
"idealized",
"include_nonveg",
"process_subgrid_section",
"process_var_list_section",
)
for var in varlist:
self.config.set(section, var, "True")
self.config.set(section, "idealized", "False")
read_cfg_option_control(self.modify_fsurdat, self.config, section, self.cfg_path)
for var in varlist:
self.config.set(section, var, "False")
read_cfg_option_control(self.modify_fsurdat, self.config, section, self.cfg_path)
self.config.set(section, "dom_pft", "UNSET")
read_cfg_option_control(self.modify_fsurdat, self.config, section, self.cfg_path)
varlist = (
"idealized",
"evenly_split_cropland",
)
for var in varlist:
orig_value = self.config.get(section, var)
self.config.set(section, var, "Thing")
with self.assertRaisesRegex(
SystemExit, "Non-boolean value found for .cfg file variable: " + var
):
read_cfg_option_control(self.modify_fsurdat, self.config, section, self.cfg_path)
self.config.set(section, var, orig_value)
def test_read_subgrid(self):
"""test a simple read of subgrid"""
read_cfg_subgrid(self.config, self.cfg_path)
def test_read_subgrid_allglacier(self):
"""test a read of subgrid that's for all glacier"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "0. 0. 0.")
self.config.set(section, "pct_lake", "0.")
self.config.set(section, "pct_wetland", "0.")
self.config.set(section, "pct_ocean", "0.")
self.config.set(section, "pct_glacier", "100.")
self.config.set(section, "pct_natveg", "0.")
self.config.set(section, "pct_crop", "0.")
read_cfg_subgrid(self.config, self.cfg_path)
def test_read_subgrid_allspecial(self):
"""test a read of subgrid that's all special landunits"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "0. 0. 0.")
self.config.set(section, "pct_lake", "25.")
self.config.set(section, "pct_wetland", "35.")
self.config.set(section, "pct_ocean", "0.")
self.config.set(section, "pct_glacier", "40.")
self.config.set(section, "pct_natveg", "0.")
self.config.set(section, "pct_crop", "0.")
read_cfg_subgrid(self.config, self.cfg_path)
def test_read_subgrid_allurban(self):
"""test a read of subgrid that's all urban"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "100.0 0.0 0.0")
self.config.set(section, "pct_lake", "0.")
self.config.set(section, "pct_wetland", "0.")
self.config.set(section, "pct_ocean", "0.")
self.config.set(section, "pct_glacier", "0.")
self.config.set(section, "pct_natveg", "0.")
self.config.set(section, "pct_crop", "0.")
read_cfg_subgrid(self.config, self.cfg_path)
def test_read_subgrid_split_cropland(self):
"""
test a read of subgrid that's 50/50 natural and
cropland, with cropland split evenly among
crop types
"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
self.config.set(section, "evenly_split_cropland", "True")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "0.0 0.0 0.0")
self.config.set(section, "pct_lake", "0.")
self.config.set(section, "pct_wetland", "0.")
self.config.set(section, "pct_glacier", "0.")
self.config.set(section, "pct_natveg", "50.")
self.config.set(section, "pct_crop", "50.")
read_cfg_subgrid(self.config, self.cfg_path)
def test_read_var_list(self):
"""test a simple read of var_list"""
read_cfg_var_list(self.config, idealized=True)
def test_subgrid_outofrange(self):
"""test a read of subgrid that's out of range"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "101. 0. 0.")
with self.assertRaisesRegex(SystemExit, "is out of range of 0 to 100 ="):
read_cfg_subgrid(self.config, self.cfg_path)
def test_subgrid_pct_urban_toosmall(self):
"""test a read of subgrid for PCT_URBAN that's an array too small"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "100. 0.")
with self.assertRaisesRegex(
SystemExit, "PCT_URBAN is not a list of the expected size of 3"
):
read_cfg_subgrid(self.config, self.cfg_path)
def test_subgrid_pct_urban_toobig(self):
"""test a read of subgrid for PCT_URBAN that's an array too big"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "100. 0. 0. 0.")
with self.assertRaisesRegex(
SystemExit, "PCT_URBAN is not a list of the expected size of 3"
):
read_cfg_subgrid(self.config, self.cfg_path)
def test_subgrid_pct_urban_singlevalue(self):
"""test a read of subgrid for PCT_URBAN that's a single value"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "100.")
with self.assertRaisesRegex(
SystemExit, "PCT_URBAN is not a list of the expected size of 3"
):
read_cfg_subgrid(self.config, self.cfg_path)
def test_subgrid_notsumtohundred(self):
"""test a read of subgrid that's doesn't sum to a hundred"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "pct_urban", "0. 0. 0.")
self.config.set(section, "pct_lake", "0.")
self.config.set(section, "pct_wetland", "0.")
self.config.set(section, "pct_ocean", "0.")
self.config.set(section, "pct_glacier", "0.")
self.config.set(section, "pct_natveg", "0.")
self.config.set(section, "pct_crop", "0.")
with self.assertRaisesRegex(
SystemExit, "PCT fractions in subgrid section do NOT sum to a hundred as they should"
):
read_cfg_subgrid(self.config, self.cfg_path)
def test_subgrid_badvar(self):
"""test a read of subgrid for a variable thats not in the list"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_subgrid_fractions"
self.config.set(section, "badvariable", "100.")
with self.assertRaisesRegex(SystemExit, "is not a valid variable name. Valid vars ="):
read_cfg_subgrid(self.config, self.cfg_path)
def test_varlist_varinidealized(self):
"""test a read of varlist for a variable thats in the idealized list,
when idealized is on"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "True")
section = "modify_fsurdat_variable_list"
self.config.set(section, "PCT_SAND", "100.")
with self.assertRaisesRegex(
SystemExit,
"is a special variable handled in the idealized section."
+ " This should NOT be handled in the variable list section. Special idealized vars =",
):
read_cfg_var_list(self.config, idealized=True)
def test_varlist_varinsubgrid(self):
"""test a read of varlist for a variable thats in the subgrid list"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "idealized", "False")
section = "modify_fsurdat_variable_list"
self.config.set(section, "PCT_GLACIER", "100.")
with self.assertRaisesRegex(
SystemExit,
"is a variable handled in the subgrid section."
+ " This should NOT be handled in the variable list section. Subgrid vars =",
):
read_cfg_var_list(self.config, idealized=False)
def test_varlist_monthlyvar(self):
"""test a read of varlist for a variable thats one of the monthly
variables handled in the dom_pft section"""
section = "modify_fsurdat_variable_list"
self.config.set(section, "MONTHLY_LAI", "100.")
with self.assertRaisesRegex(
SystemExit,
"is a variable handled as part of the dom_pft handling."
+ " This should NOT be handled in the variable list section."
+ " Monthly vars handled this way =",
):
read_cfg_var_list(self.config, idealized=False)
def test_subgrid_remove(self):
"""test a read of subgrid when it's section has been removed"""
section = "modify_fsurdat_subgrid_fractions"
self.config.remove_section(section)
with self.assertRaisesRegex(SystemExit, "Config file does not have the expected section"):
read_cfg_subgrid(self.config, self.cfg_path)
def test_subgrid_not_thereifoff(self):
"""test that a graceful error happens if subgrid section is off,
but it appears in the file"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "process_subgrid_section", "False")
with self.assertRaisesRegex(SystemExit, "Config file does have a section"):
check_no_subgrid_section(self.config)
def test_varlist_not_thereifoff(self):
"""test that a graceful error happens if varlist section is off,
but it appears in the file"""
section = "modify_fsurdat_basic_options"
self.config.set(section, "process_var_list_section", "False")
with self.assertRaisesRegex(SystemExit, "Config file does have a section"):
check_no_varlist_section(self.config)
if __name__ == "__main__":
unit_testing.setup_for_tests()
unittest.main()