diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 52322ed5ce..54182df66a 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -32,8 +32,8 @@ module FatesFuelMod procedure :: SumLoading procedure :: CalculateFractionalLoading procedure :: UpdateFuelMoisture - procedure :: AverageBulkDensity - procedure :: AverageSAV + procedure :: AverageBulkDensity_NoTrunks + procedure :: AverageSAV_NoTrunks end type fuel_type @@ -302,7 +302,7 @@ real(r8) function MoistureOfExtinction(sav) real(r8), parameter :: MEF_b = 0.066_r8 if (sav <= 0.0_r8) then - write(fates_log(), *) 'SAV cannot be negative - SAV = ' // sav + write(fates_log(), *) 'SAV cannot be negative - SAV' call endrun(msg=errMsg(__FILE__, __LINE__)) else MoistureOfExtinction = MEF_a - MEF_b*log(sav) diff --git a/fire/SFParamsMod.F90 b/fire/SFParamsMod.F90 index d688f1976c..65d87e5c6d 100644 --- a/fire/SFParamsMod.F90 +++ b/fire/SFParamsMod.F90 @@ -32,7 +32,7 @@ module SFParamsMod real(r8),protected, public :: SF_val_SAV(num_fuel_classes) real(r8),protected, public :: SF_val_FBD(num_fuel_classes) real(r8),protected, public :: SF_val_min_moisture(num_fuel_classes) - real(r8),protected, public :: SF_val_mid_moisture(NFSC) + real(r8),protected, public :: SF_val_mid_moisture(num_fuel_classes) real(r8),protected, public :: SF_val_low_moisture_Coeff(num_fuel_classes) real(r8),protected, public :: SF_val_low_moisture_Slope(num_fuel_classes) real(r8),protected, public :: SF_val_mid_moisture_Coeff(num_fuel_classes) diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 71960cc587..7c19f7cc99 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -3,8 +3,8 @@ ## Functional tests add_subdirectory(functional_testing/allometry fates_allom_ftest) add_subdirectory(functional_testing/math_utils fates_math_ftest) -add_subdirectory(functional_testing/fire fates_fuel_test) +add_subdirectory(functional_testing/fire fates_fuel_ftest) ## Unit tests add_subdirectory(unit_testing/fire_weather_test fates_fire_weather_utest) -add_subdirectory(unit_testing/fire_fuel_test fates_fire_fuel_utest) \ No newline at end of file +add_subdirectory(unit_testing/fire_fuel_test fates_fire_fuel_utest) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index cd2621c449..6684ba8ee1 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -91,8 +91,8 @@ program FatesTestFuel call fuel(f)%CalculateFractionalLoading() ! calculate geometric properties - call fuel(f)%AverageBulkDensity(SF_val_FBD) - call fuel(f)%AverageSAV(SF_val_SAV) + call fuel(f)%AverageBulkDensity_NoTrunks(SF_val_FBD) + call fuel(f)%AverageSAV_NoTrunks(SF_val_SAV) ! save values fuel_loading(:,f) = fuel(f)%loading(:) diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py deleted file mode 100644 index b1c2ffef1d..0000000000 --- a/testing/functional_testing/fire/fuel_plotting.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Utility functions for fuel functional unit tests -""" -import os -import math -import pandas as pd -import numpy as np -import xarray as xr -import matplotlib -import matplotlib.pyplot as plt - -def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): - """Plot output associated with fuel tests - - Args: - run_dir (str): run directory - out_file (str): output file - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - fuel_dat = xr.open_dataset(os.path.join(run_dir, out_file)) - - plot_NI_dat(fuel_dat, save_figs, plot_dir) - plot_moisture_dat(fuel_dat, save_figs, plot_dir) - plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) - plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) - plot_barchart(fuel_dat, 'bulk_density', 'Fuel bulk density', 'kg m$^{-3}$', save_figs, plot_dir, by_litter_type=False) - plot_barchart(fuel_dat, 'SAV', 'Fuel surface area to volume ratio', 'cm$^{-1}$', save_figs, plot_dir, by_litter_type=False) - -def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir, by_litter_type=True): - - litter_classes = ['twigs', 'small branches', 'large branches', 'trunks', 'dead leaves', 'live grass'] - colors = ['darksalmon', 'peru', 'saddlebrown', 'black', 'moccasin', 'yellowgreen'] - fuel_models = [str(f) for f in fuel_dat.fuel_model.values] - - if by_litter_type: - data_dict = {lc: fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} - else: - data_dict = fuel_dat[var].values - - _, ax = plt.subplots() - if by_litter_type: - bottom = np.zeros(len(fuel_models)) - for i, (litter_class, dat) in enumerate(data_dict.items()): - ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom, color=colors[i]) - bottom += dat - plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) - else: - ax.bar(fuel_models, data_dict, color='darkcyan') - - box = ax.get_position() - ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) - plt.ylabel(f'{varname} ({units})', fontsize=11) - plt.xticks(rotation=90) - plt.xlabel('Fuel Model') - - if save_figs: - fig_name = os.path.join(plot_dir, f"{varname}_plot.png") - plt.savefig(fig_name) - -def plot_NI_dat(fuel_dat, save_figs, plot_dir): - """Plot output for Nesterov index - - Args: - fuel_dat (Xarray Dataset): output fuel data - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - plt.figure() - fuel_dat.NI.plot() - plt.xlabel('Time', fontsize=11) - plt.ylabel('Nesterov Index', fontsize=11) - - if save_figs: - fig_name = os.path.join(plot_dir, "Nesterov_plot.png") - plt.savefig(fig_name) - -def plot_moisture_dat(fuel_dat, save_figs, plot_dir): - """Plot output for fuel moisture - - Args: - fuel_dat (Xarray Dataset): output fuel data - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - plt.figure() - fuel_dat.fuel_moisture.plot(hue='fuel_model') - plt.xlabel('Time', fontsize=11) - plt.ylabel('Fuel Moisture', fontsize=11) - - if save_figs: - fig_name = os.path.join(plot_dir, "fuel_moisture_plot.png") - plt.savefig(fig_name) - \ No newline at end of file diff --git a/testing/functional_testing/fire/fuel_test.py b/testing/functional_testing/fire/fuel_test.py new file mode 100644 index 0000000000..b9cd151621 --- /dev/null +++ b/testing/functional_testing/fire/fuel_test.py @@ -0,0 +1,187 @@ +""" +Concrete class for running the fuel functional test for FATES. +""" +import os +import numpy as np +import xarray as xr +import matplotlib.pyplot as plt +from functional_class import FunctionalTest + + +class FuelTest(FunctionalTest): + """Fuel test class""" + + name = "fuel" + + def __init__(self, test_dict): + super().__init__( + FuelTest.name, + test_dict["test_dir"], + test_dict["test_exe"], + test_dict["out_file"], + test_dict["use_param_file"], + test_dict["other_args"], + ) + self.plot = True + + def plot_output(self, run_dir: str, save_figs: bool, plot_dir: str): + """Plot output associated with fuel tests + + Args: + run_dir (str): run directory + out_file (str): output file + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + fuel_dat = xr.open_dataset(os.path.join(run_dir, self.out_file)) + + self.plot_NI_dat(fuel_dat, save_figs, plot_dir) + self.plot_moisture_dat(fuel_dat, save_figs, plot_dir) + self.plot_barchart( + fuel_dat, + "fuel_loading", + "Fuel loading", + "kgC m$^{-2}$", + save_figs, + plot_dir, + ) + self.plot_barchart( + fuel_dat, + "frac_loading", + "Fractional fuel loading", + "0-1", + save_figs, + plot_dir, + ) + self.plot_barchart( + fuel_dat, + "bulk_density", + "Fuel bulk density", + "kg m$^{-3}$", + save_figs, + plot_dir, + by_litter_type=False, + ) + self.plot_barchart( + fuel_dat, + "SAV", + "Fuel surface area to volume ratio", + "cm$^{-1}$", + save_figs, + plot_dir, + by_litter_type=False, + ) + + @staticmethod + def plot_barchart( + fuel_dat: xr.Dataset, + var: str, + varname: str, + units: str, + save_figs: bool, + plot_dir: bool, + by_litter_type: bool = True, + ): + """Plots fuel data output as a bar chart + + Args: + fuel_dat (xr.Dataset): fuel data output + var (str): variable to plot + varname (str): variable name for x axis + units (str): units description + save_figs (bool): whether or not to save figure + plot_dir (bool): where to save figure + by_litter_type (bool, optional): whether the bar chart is by litter type. Defaults to True. + """ + + litter_classes = [ + "twigs", + "small branches", + "large branches", + "trunks", + "dead leaves", + "live grass", + ] + colors = [ + "darksalmon", + "peru", + "saddlebrown", + "black", + "moccasin", + "yellowgreen", + ] + fuel_models = [str(f) for f in fuel_dat.fuel_model.values] + + if by_litter_type: + data_dict = { + lc: fuel_dat.isel(litter_class=i)[var].values + for i, lc in enumerate(litter_classes) + } + else: + data_dict = fuel_dat[var].values + + _, ax = plt.subplots() + if by_litter_type: + bottom = np.zeros(len(fuel_models)) + for i, (litter_class, dat) in enumerate(data_dict.items()): + ax.bar( + fuel_models, + dat, + 0.5, + label=litter_class, + bottom=bottom, + color=colors[i], + ) + bottom += dat + plt.legend(loc="center left", bbox_to_anchor=(1, 0.5)) + else: + ax.bar(fuel_models, data_dict, color="darkcyan") + + box = ax.get_position() + ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) + plt.ylabel(f"{varname} ({units})", fontsize=11) + plt.xticks(rotation=90) + plt.xlabel("Fuel Model") + + if save_figs: + fig_name = os.path.join(plot_dir, f"{varname}_plot.png") + plt.savefig(fig_name) + + @staticmethod + def plot_NI_dat(fuel_dat: xr.Dataset, save_figs: bool, plot_dir: str): + """Plot output for Nesterov index + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.NI.plot() + plt.xlabel("Time", fontsize=11) + plt.ylabel("Nesterov Index", fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "Nesterov_plot.png") + plt.savefig(fig_name) + + @staticmethod + def plot_moisture_dat(fuel_dat: xr.Dataset, save_figs: bool, plot_dir: str): + """Plot output for fuel moisture + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.fuel_moisture.plot(hue="fuel_model") + plt.xlabel("Time", fontsize=11) + plt.ylabel("Fuel Moisture", fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "fuel_moisture_plot.png") + plt.savefig(fig_name) diff --git a/testing/functional_tests.cfg b/testing/functional_tests.cfg index 0c859e1559..46924c307c 100644 --- a/testing/functional_tests.cfg +++ b/testing/functional_tests.cfg @@ -13,4 +13,8 @@ use_param_file = False other_args = [] [fuel] -test_dir = fates_fuel_ftest \ No newline at end of file +test_dir = fates_fuel_ftest +test_exe = FATES_fuel_exe +out_file = fuel_out.nc +use_param_file = True +other_args = ['../testing/test_data/BONA_datm.nc'] diff --git a/testing/run_functional_tests.py b/testing/run_functional_tests.py index 5f80a3f095..8a2078628a 100755 --- a/testing/run_functional_tests.py +++ b/testing/run_functional_tests.py @@ -36,12 +36,19 @@ # add testing subclasses here from functional_class import FunctionalTest -from functional_testing.allometry.allometry_test import AllometryTest # pylint: disable=unused-import -from functional_testing.math_utils.math_utils_test import QuadraticTest # pylint: disable=unused-import +from functional_testing.allometry.allometry_test import ( + AllometryTest, +) # pylint: disable=unused-import +from functional_testing.math_utils.math_utils_test import ( + QuadraticTest, +) # pylint: disable=unused-import +from functional_testing.fire.fuel_test import FuelTest # pylint: disable=unused-import add_cime_lib_to_path() -from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.utils import ( + run_cmd_no_fail, +) # pylint: disable=wrong-import-position,import-error,wrong-import-order # constants for this script _DEFAULT_CONFIG_FILE = "functional_tests.cfg" diff --git a/testing/unit_tests.cfg b/testing/unit_tests.cfg index 3e5ee536f4..179b924735 100644 --- a/testing/unit_tests.cfg +++ b/testing/unit_tests.cfg @@ -1,2 +1,5 @@ [fire_weather] test_dir = fates_fire_weather_utest + +[fire_fuel] +test_dir = fates_fire_fuel_utest diff --git a/testing/utils.py b/testing/utils.py index eaafe9f110..06fd74db0b 100644 --- a/testing/utils.py +++ b/testing/utils.py @@ -10,7 +10,9 @@ add_cime_lib_to_path() -from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.utils import ( + run_cmd_no_fail, +) # pylint: disable=wrong-import-position,import-error,wrong-import-order def round_up(num: float, decimals: int = 0) -> float: @@ -119,48 +121,6 @@ def get_color_palette(number: int) -> list: ] return colors[:number] -<<<<<<< HEAD - -def blank_plot(x_max, x_min, y_max, y_min, draw_horizontal_lines=False): - """Generate a blank plot with set attributes - - Args: - x_max (float): maximum x value - x_min (float): minimum x value - y_max (float): maximum y value - y_min (float): minimum y value - draw_horizontal_lines (bool, optional): whether or not to draw horizontal - lines across plot. Defaults to False. - """ - - plt.figure(figsize=(7, 5)) - axis = plt.subplot(111) - axis.spines["top"].set_visible(False) - axis.spines["bottom"].set_visible(False) - axis.spines["right"].set_visible(False) - axis.spines["left"].set_visible(False) - - axis.get_xaxis().tick_bottom() - axis.get_yaxis().tick_left() - - plt.xlim(0.0, x_max) - plt.ylim(0.0, y_max) - - plt.yticks(fontsize=10) - plt.xticks(fontsize=10) - - if draw_horizontal_lines: - inc = (int(y_max) - y_min)/20 - for i in range(0, 20): - plt.plot(range(math.floor(x_min), math.ceil(x_max)), - [0.0 + i*inc] * len(range(math.floor(x_min), math.ceil(x_max))), - "--", lw=0.5, color="black", alpha=0.3) - - plt.tick_params(bottom=False, top=False, left=False, right=False) - - return plt -||||||| 825579d0 -======= def config_to_dict(config_file: str) -> dict: @@ -297,4 +257,3 @@ def blank_plot( ) plt.tick_params(bottom=False, top=False, left=False, right=False) ->>>>>>> main