def get_wrf_fitness(param_ids, start_date='Jan 15 2011', end_date='Jan 16 2011',
                    bc_data='ERA', n_domains=1, correction_factor=0.0004218304553577255,
                    setup_yaml='dirpath.yml', disable_timeout=False, verbose=False):
    """
    Using the input physics parameters, date, boundary condition, and domain data,
    this function runs the WRF model and computes the error between WRF and ERA5.

    :param param_ids: list of integers
        corresponding to each WRF physics parameterization.
    :param start_date: string
        specifying a desired start date.
    :param end_date: string
        specifying a desired end date.
    :param bc_data: string
        specifying the boundary condition data to be used for the WRF forecast.
        Currently, only ERA data (ds627.0) is supported.
    :param n_domains: integer
        specifing the number of WRF model domains. 1 - 3 domains are currently supported.
    :param correction_factor: float
        capuring the relationship between GHI and wind power density (WPD) errors averaged
        across an entrie year. Calculated using opwrf/examples/Fitness_correction_factor.py.
    :param setup_yaml: string
        defining the path to the yaml file where input directory paths are specified.
    :param disable_timeout: boolean (default = False)
        telling runwrf if subprogram timeouts are allowed or not.
    :param verbose: boolean (default = False)
        instructing the program to print everything or just key information to the screen.
    :return fitness: float
        value denoting how well the WRF model run performed.
        Fitness is a measure of accumlated error, so a lower value is better.

    """
    if verbose:
        print('- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -')
        print('\nCalculating fitness for: {}'.format(param_ids))

    # Create a WRFModel instance
    wrf_sim = WRFModel(param_ids, start_date, end_date,
                       bc_data=bc_data, n_domains=n_domains, setup_yaml=setup_yaml, verbose=verbose)

    # Check to see if WRFModel instance exists; if not, run the WRF model.
    wrfout_file_path = wrf_sim.DIR_WRFOUT + 'wrfout_d01.nc'
    orig_wrfout_file_path = wrf_sim.DIR_WRFOUT + 'wrfout_d01_' \
                       + wrf_sim.forecast_start.strftime('%Y') + '-' \
                       + wrf_sim.forecast_start.strftime('%m') + '-' \
                       + wrf_sim.forecast_start.strftime('%d') + '_00:00:00'
    if [os.path.exists(file) for file in [wrfout_file_path, orig_wrfout_file_path]].count(True) is 0:
        # Next, get boundary condition data for the simulation
        # ERA is the only supported data type right now.
        vtable_sfx = wrf_sim.get_bc_data()

        # Setup the working directory to run the simulation
        success = wrf_sim.wrfdir_setup(vtable_sfx)

        # Prepare the namelists
        if success:
            success = wrf_sim.prepare_namelists()

        # Run WPS
        if success:
            success = wrf_sim.run_wps(disable_timeout)
            if verbose:
                print(f'WPS ran successfully? {success}')

        # Run REAL
        if success:
            success = wrf_sim.run_real(disable_timeout)
            if verbose:
                print(f'Real ran successfully? {success}')

        # RUN WRF
        if success:
            success, runtime = wrf_sim.run_wrf(disable_timeout)
            if verbose:
                print(f'WRF ran successfully? {success}')
        else:
            runtime = '00h 00m 00s'
    else:
        success = True
        runtime = '00h 00m 00s'

    # Postprocess wrfout file and ERA5 data
    if success:
        proc_wrfout_file_path = wrf_sim.DIR_WRFOUT + 'wrfout_processed_d01.nc'
        if not os.path.exists(proc_wrfout_file_path):
            if verbose:
                print(f'Postprocessing wrfout file...')
            success = wrf_sim.process_wrfout_data()
        wrf_sim.process_era5_data()

    # Compute the error between WRF run and ERA5 dataset and return fitness
    if success:
        mae = wrf_sim.wrf_era5_diff()
        ghi_total_error = mae[1]
        wpd_total_error = mae[2]
        daylight_factor = hf.daylight_frac(start_date)  # daylight fraction
        fitness = daylight_factor * ghi_total_error + correction_factor * wpd_total_error
        if verbose:
            print(f'!!! Physics options set {param_ids} has fitness {fitness}')

    else:
        ghi_total_error = 6.022 * 10 ** 23
        wpd_total_error = 6.022 * 10 ** 23
        fitness = 6.022 * 10 ** 23

    return fitness, ghi_total_error, wpd_total_error, runtime
def test_daylight_frac():
    frac = daylight_frac('Jul 1, 2020')
    print(f'The daylight fraction is: {frac}')
    assert 0 <= frac <= 1
Exemple #3
0
def wrf_errorandfitness_plot(wrfds,
                             paramstr,
                             save_fig=False,
                             wrf_dir='./',
                             era_dir='./',
                             fig_path='./',
                             verbose=False,
                             fitness_short_title='Model Fitness',
                             ghi_error_short_title='GHI Error (kWh m-2 day-1)',
                             wpd_error_short_title='WPD Error (kWh m-2 day-1)',
                             **kwargs):
    """
    Plots the GHI error, WPD error, and fitness maps all within one 3-panel plot.

    :param wrfds:
    :param paramstr:
    :param save_fig:
    :param wrf_dir:
    :param era_dir:
    :param fig_path:
    :param verbose:
    :param fitness_short_title:
    :param ghi_error_short_title:
    :param wpd_error_short_title:
    :param kwargs:

    :return: None
    """
    # Get the start_date and create the date string
    start_date = str(wrfds.Time.dt.strftime('%b %d %Y')[0].values)
    datestr = str(wrfds.Time.dt.strftime('%Y-%m-%d')[0].values)

    # To start, we need to get the WRF map projection information (a Lambert Conformal grid),
    # and find the domain boundaries in this projection.
    # NOTE: this task MUST occurr before we regrid the WRF variables or the coordinates change and become incompatible.
    wrf_cartopy_proj = get_wrf_proj(wrfds, 'dni')
    proj_bounds = get_domain_boundary(wrfds, wrf_cartopy_proj)
    if verbose:
        print(f'WRF Projection:\n{wrf_cartopy_proj}')
        print(f'\nDomain Boundaries:\n{proj_bounds}')

    # Regrid the wrf GHI and WPD
    # Due to some obnioxious xarry nuance, the easiest way to keep the regridding function from adding additional
    # variables and coordinates to the xarray dataset in the outter scope, is just to open new datasets within
    # wrf_era5_regrid_xesmf; so that's why we must again specify the wrf and era5 file names below.
    wrf_file = f'wrfout_processed_d01_{datestr}_{paramstr}.nc'
    era_file = f'ERA5_EastUS_WPD-GHI_{datestr.split("-")[0]}-{datestr.split("-")[1]}.nc'
    wrfds, eradata = optwrf.regridding.wrf_era5_regrid_xesmf(wrfdir=wrf_dir,
                                                             wrffile=wrf_file,
                                                             eradir=era_dir,
                                                             erafile=era_file)

    # Calculate the error in GHI and WPD
    wrfds = optwrf.regridding.wrf_era5_error(wrfds, eradata)

    # Calculate the fitness
    correction_factor = 0.0004218304553577255
    daylight_factor = helper_functions.daylight_frac(
        start_date)  # daylight fraction
    wrfds[
        'fitness'] = daylight_factor * wrfds.total_ghi_error + correction_factor * wrfds.total_wpd_error

    # Create a figure
    fig = plt.figure(figsize=(9.5, 3))

    # Set the GeoAxes to the projection used by WRF
    ax_fitness = fig.add_subplot(1, 3, 1, projection=wrf_cartopy_proj)
    ax_ghierr = fig.add_subplot(1,
                                3,
                                2,
                                projection=wrf_cartopy_proj,
                                sharey=ax_fitness)
    ax_wpderr = fig.add_subplot(1,
                                3,
                                3,
                                projection=wrf_cartopy_proj,
                                sharey=ax_ghierr)

    # Create the filled contour levels
    fitness_cn = ax_fitness.contourf(
        wrfpy.to_np(wrfds.lon),
        wrfpy.to_np(wrfds.lat),
        wrfpy.to_np(wrfds['fitness']),
        # np.linspace(0, np.amax(wrfds['fitness']), 22),
        np.linspace(0, 7, 22),
        transform=ccrs.PlateCarree(),
        cmap=get_cmap("Greys"))
    ghierr_cn = ax_ghierr.contourf(
        wrfpy.to_np(wrfds.lon),
        wrfpy.to_np(wrfds.lat),
        wrfpy.to_np(wrfds['total_ghi_error']),
        # np.linspace(0, np.amax(wrfds['total_ghi_error']), 22),
        np.linspace(0, 2.5, 22),
        transform=ccrs.PlateCarree(),
        cmap=get_cmap("hot_r"))
    wpderr_cn = ax_wpderr.contourf(
        wrfpy.to_np(wrfds.lon),
        wrfpy.to_np(wrfds.lat),
        wrfpy.to_np(wrfds['total_wpd_error']),
        # np.linspace(0, np.amax(wrfds['total_wpd_error']), 22),
        # np.linspace(0, 5000, 22),
        locator=ticker.LogLocator(subs='all'),
        norm=LogNorm(vmax=5000),
        transform=ccrs.PlateCarree(),
        cmap=get_cmap("Greens"))

    # Format the axes
    time_string_f = wrfds.Time[0].dt.strftime('%b %d, %Y')
    format_cnplot_axis(
        ax_fitness,
        fitness_cn,
        proj_bounds,
        title_str=f'{fitness_short_title}\n{time_string_f.values}',
        cbar_ticks=[0, 1, 2, 3, 4, 5, 6, 7],
        cbar_tick_labels=['0', '1', '2', '3', '4', '5', '6', '7'])
    format_cnplot_axis(
        ax_ghierr,
        ghierr_cn,
        proj_bounds,
        title_str=f'{ghi_error_short_title}\n{time_string_f.values}',
        cbar_ticks=[0, 0.5, 1.0, 1.5, 2.0, 2.5],
        cbar_tick_labels=['0', '0.5', '1.0', '1.5', '2.0', '2.5'])
    format_cnplot_axis(
        ax_wpderr,
        wpderr_cn,
        proj_bounds,
        title_str=f'{wpd_error_short_title}\n{time_string_f.values}',
        cbar_ticks=ticker.LogLocator())
    if save_fig:
        file_type = kwargs.get('file_type', '.pdf')
        plt.savefig(fig_path + file_type,
                    dpi=300,
                    transparent=True,
                    bbox_inches='tight')

    plt.show()
Exemple #4
0
def wrf_era5_plot(var,
                  wrfds,
                  erads,
                  paramstr,
                  src='wrf',
                  hourly=False,
                  save_fig=False,
                  wrf_dir='./',
                  era_dir='./',
                  short_title_str='Title',
                  fig_path='./',
                  verbose=False,
                  **kwargs):
    """
    Creates a single WRF or ERA5 plot, using the WRF bounds, producing either a plot every hour
    or a single plot for the day.

    :param var:
    :param wrfds:
    :param erads:
    :param paramstr:
    :param src:
    :param hourly:
    :param save_fig:
    :param wrf_dir:
    :param era_dir:
    :param short_title_str:
    :param fig_path:
    :param verbose:
    :param kwargs:

    :return: None
    """
    # Format the var input
    if type(var) is not str:
        print(f'The var input, {var}, must be a string.')
        raise TypeError
    if var in ['GHI', 'ghi']:
        var = 'ghi'
        wrf_var = 'ghi'
        era_var = 'GHI'
    elif var in ['WPD', 'wpd']:
        var = 'wpd'
        wrf_var = 'wpd'
        era_var = 'WPD'
    elif var in ['ghi_error', 'GHI_ERROR']:
        var = 'ghi_error'
        wrf_var = var
    elif var in ['wpd_error', 'WPD_ERROR']:
        var = 'wpd_error'
        wrf_var = var
    elif var in ['fitness', 'Fitness', 'FITNESS']:
        var = 'fitness'
        wrf_var = var
    else:
        print(f'Variable {var} is not supported.')
        raise KeyError

    # Get the start_date and create the date string
    datestr = str(wrfds.Time.dt.strftime('%Y-%m-%d')[0].values)

    # To start, we need to get the WRF map projection information (a Lambert Conformal grid),
    # and find the domain boundaries in this projection.
    # NOTE: this task MUST occurr before we regrid the WRF variables or the coordinates change and become incompatible.
    wrf_cartopy_proj = get_wrf_proj(wrfds, 'dni')
    proj_bounds = get_domain_boundary(wrfds, wrf_cartopy_proj)

    # We can use a basic Plate Carree projection for ERA5
    era5_cartopy_proj = ccrs.PlateCarree()

    # Now, get the desired variables
    if var in ['ghi_error', 'wpd_error', 'fitness']:
        # Due to some obnioxious xarry nuance, the easiest way to keep the regridding function from adding additional
        # variables and coordinates to the xarray dataset in the outter scope, is just to open new datasets within
        # wrf_era5_regrid_xesmf; so that's why we must again specify the wrf and era5 file names below.
        wrf_file = f'wrfout_processed_d01_{datestr}_{paramstr}.nc'
        era_file = f'ERA5_EastUS_WPD-GHI_{datestr.split("-")[0]}-{datestr.split("-")[1]}.nc'
        wrfdat_proc, eradat_proc = optwrf.regridding.wrf_era5_regrid_xesmf(
            wrfdir=wrf_dir, wrffile=wrf_file, eradir=era_dir, erafile=era_file)

        # Calculate the error in GHI and WPD
        wrfdat_proc = optwrf.regridding.wrf_era5_error(wrfdat_proc,
                                                       eradat_proc)
        if verbose:
            print(wrfdat_proc)

        # Calculate the fitness
        correction_factor = 0.0004218304553577255
        daylight_factor = helper_functions.daylight_frac(
            datestr)  # daylight fraction
        wrfdat_proc['fitness'] = daylight_factor * wrfdat_proc.ghi_error \
                                 + correction_factor * wrfdat_proc.wpd_error

    # Define the time indicies from the times variable
    time_indicies = range(0, len(wrfds.Time))
    # Format the times for title slides
    times_strings_f = wrfds.Time.dt.strftime('%b %d, %Y %H:%M')
    # Get the desired variable(s)
    for tidx in time_indicies:
        timestr = wrfds.Time[tidx].values
        timestr_f = times_strings_f[tidx].values
        if hourly:
            title_str = f'{short_title_str}\n{timestr_f} (UTC)'
        else:
            time_string_f = wrfds.Time[0].dt.strftime('%b %d, %Y')
            title_str = f'{short_title_str}\n{time_string_f.values}'

        # WRF Variable (divide by 1000 to convert from W to kW or Wh to kWh)
        if not hourly and tidx != 0:
            if src == 'wrf':
                if var in ['ghi', 'wpd']:
                    plot_var = plot_var + (wrfds[wrf_var].sel(
                        Time=np.datetime_as_string(timestr)) / 1000)
                else:
                    plot_var = plot_var + wrfdat_proc[wrf_var].sel(
                        Time=np.datetime_as_string(timestr))
            elif src == 'era5':
                if var in ['ghi', 'wpd']:
                    plot_var = plot_var + (erads[era_var].sel(
                        Time=np.datetime_as_string(timestr)) / 1000)
                else:
                    print(f'Variable {var} is not provided by {src}')
                    raise ValueError
        else:
            if src == 'wrf':
                if var in ['ghi', 'wpd']:
                    plot_var = wrfds[wrf_var].sel(
                        Time=np.datetime_as_string(timestr)) / 1000
                else:
                    plot_var = wrfdat_proc[wrf_var].sel(
                        Time=np.datetime_as_string(timestr))
            elif src == 'era5':
                if var in ['ghi', 'wpd']:
                    plot_var = erads[era_var].sel(
                        Time=np.datetime_as_string(timestr)) / 1000
                else:
                    print(f'Variable {var} is not provided by {src}')
                    raise ValueError

        if hourly:
            # Create a figure
            fig = plt.figure(figsize=(4, 4))

            # Set the GeoAxes to the projection used by WRF
            ax = fig.add_subplot(1, 1, 1, projection=wrf_cartopy_proj)

            # Make the countour lines for filled contours for the GHI
            contour_levels = specify_contour_levels(var, hourly=True, **kwargs)

            # Add the filled contour levels
            color_map = specify_clormap(var)
            if src == 'wrf' and var in ['ghi', 'wpd']:
                cn = ax.contourf(wrfpy.to_np(wrfds.lon),
                                 wrfpy.to_np(wrfds.lat),
                                 wrfpy.to_np(plot_var),
                                 contour_levels,
                                 transform=ccrs.PlateCarree(),
                                 cmap=color_map)
            else:
                cn = ax.contourf(wrfpy.to_np(erads.longitude),
                                 wrfpy.to_np(erads.latitude),
                                 wrfpy.to_np(plot_var),
                                 contour_levels,
                                 transform=ccrs.PlateCarree(),
                                 cmap=color_map)

            # Format the plot
            format_cnplot_axis(ax, cn, proj_bounds, title_str=title_str)

            # Save the figure(s)
            if save_fig:
                fig_path_temp = fig_path + str(tidx).zfill(2)
                plt.savefig(fig_path_temp + '.png',
                            dpi=300,
                            transparent=True,
                            bbox_inches='tight')

            plt.show()

    if not hourly:
        # Create a figure
        fig = plt.figure(figsize=(4, 4))

        # Set the GeoAxes to the projection used by WRF
        ax = fig.add_subplot(1, 1, 1, projection=wrf_cartopy_proj)

        # Make the countour lines for filled contours
        contour_levels = specify_contour_levels(var, hourly=False, **kwargs)

        # Add the filled contour levels
        color_map = specify_clormap(var)
        if src == 'wrf' and var in ['ghi', 'wpd']:
            cn = ax.contourf(wrfpy.to_np(wrfds.lon),
                             wrfpy.to_np(wrfds.lat),
                             wrfpy.to_np(plot_var),
                             contour_levels,
                             transform=ccrs.PlateCarree(),
                             cmap=color_map)
        else:
            cn = ax.contourf(wrfpy.to_np(erads.longitude),
                             wrfpy.to_np(erads.latitude),
                             wrfpy.to_np(plot_var),
                             contour_levels,
                             transform=ccrs.PlateCarree(),
                             cmap=color_map)

        # Format the plot
        format_cnplot_axis(ax, cn, proj_bounds, title_str=title_str)

        # Save the figure(s)
        if save_fig:
            file_type = kwargs.get('file_type', '.pdf')
            plt.savefig(fig_path + file_type,
                        dpi=300,
                        transparent=True,
                        bbox_inches='tight')

        plt.show()
Exemple #5
0
def wrf_errorandfitness_plot(wrfdata, save_fig=False, wrf_dir='./', era_dir='./',
                             fig_path='./', verbose=False, fitness_short_title='Model Fitness',
                             ghi_error_short_title='GHI Error (kWh m-2 day-1)',
                             wpd_error_short_title='WPD Error (kWh m-2 day-1)'):
    """

    :return:
    """
    # Get the start_date and create the date string
    start_date = str(wrfdata.Time.dt.strftime('%b %d %Y')[0].values)

    # To start, we need to get the WRF map projection information (a Lambert Conformal grid),
    # and find the domain boundaries in this projection.
    # NOTE: this task MUST occurr before we regrid the WRF variables or the coordinates change and become incompatible.
    wrf_cartopy_proj = get_wrf_proj(wrfdata, 'dni')
    proj_bounds = get_domain_boundary(wrfdata, 'ghi', wrf_cartopy_proj)
    if verbose:
        print(f'WRF Projection:\n{wrf_cartopy_proj}')
        print(f'\nDomain Boundaries:\n{proj_bounds}')

    # Regrid the wrf GHI and WPD
    input_year = helper_functions.format_date(start_date).strftime('%Y')
    input_month = helper_functions.format_date(start_date).strftime('%m')
    wrfdata, eradata = optwrf.regridding.wrf_era5_regrid_xesmf(input_year, input_month,
                                                               wrfdir=wrf_dir, eradir=era_dir)
    # Calculate the error in GHI and WPD
    wrfdata = optwrf.regridding.wrf_era5_error(wrfdata, eradata)

    # Calculate the fitness
    correction_factor = 0.0004218304553577255
    daylight_factor = helper_functions.daylight_frac(start_date)  # daylight fraction
    wrfdata['fitness'] = daylight_factor * wrfdata.total_ghi_error + correction_factor * wrfdata.total_wpd_error

    # Create a figure
    fig = plt.figure(figsize=(9.5, 3))

    # Set the GeoAxes to the projection used by WRF
    ax_fitness = fig.add_subplot(1, 3, 1, projection=wrf_cartopy_proj)
    ax_ghierr = fig.add_subplot(1, 3, 2, projection=wrf_cartopy_proj, sharey=ax_fitness)
    ax_wpderr = fig.add_subplot(1, 3, 3, projection=wrf_cartopy_proj, sharey=ax_ghierr)

    # Create the filled contour levels
    fitness_cn = ax_fitness.contourf(wrfpy.to_np(wrfdata.lon), wrfpy.to_np(wrfdata.lat),
                                     wrfpy.to_np(wrfdata['fitness']),
                                     np.linspace(0, np.amax(wrfdata['fitness']), 22),
                                     transform=ccrs.PlateCarree(), cmap=get_cmap("Greys"))
    ghierr_cn = ax_ghierr.contourf(wrfpy.to_np(wrfdata.lon), wrfpy.to_np(wrfdata.lat),
                                   wrfpy.to_np(wrfdata['total_ghi_error']),
                                   np.linspace(0, np.amax(wrfdata['total_ghi_error']), 22),
                                   transform=ccrs.PlateCarree(), cmap=get_cmap("hot_r"))
    wpderr_cn = ax_wpderr.contourf(wrfpy.to_np(wrfdata.lon), wrfpy.to_np(wrfdata.lat),
                                   wrfpy.to_np(wrfdata['total_wpd_error']),
                                   np.linspace(0, np.amax(wrfdata['total_wpd_error']), 22),
                                   transform=ccrs.PlateCarree(), cmap=get_cmap("Greens"))

    # Format the axes
    time_string_f = wrfdata.Time[0].dt.strftime('%b %d, %Y')
    format_cnplot_axis(ax_fitness, fitness_cn, proj_bounds,
                       title_str=f'{fitness_short_title}\n{time_string_f.values}')
    format_cnplot_axis(ax_ghierr, ghierr_cn, proj_bounds,
                       title_str=f'{ghi_error_short_title}\n{time_string_f.values}')
    format_cnplot_axis(ax_wpderr, wpderr_cn, proj_bounds,
                       title_str=f'{wpd_error_short_title}\n{time_string_f.values}')
    if save_fig:
        print('Saving figure...')
        plt.savefig(fig_path + '.pdf', transparent=True, bbox_inches='tight')

    plt.show()
Exemple #6
0
def wrf_era5_plot(var, wrfdat, eradat, datestr, src='wrf', hourly=False, save_fig=False,
                  wrf_dir='./', era_dir='./', short_title_str='Title', fig_path='./'):
    """
    Creates a single WRF or ERA5 plot, using the WRF bounds, producing either a plot every hour
    or a single plot for the day.
    """
    # Format the var input
    if type(var) is not str:
        print(f'The var input, {var}, must be a string.')
        raise TypeError
    if var in ['GHI', 'ghi']:
        var = 'ghi'
        wrf_var = 'ghi'
        era_var = 'GHI'
    elif var in ['WPD', 'wpd']:
        var = 'wpd'
        wrf_var = 'wpd'
        era_var = 'WPD'
    elif var in ['ghi_error', 'GHI_ERROR']:
        var = 'ghi_error'
        wrf_var = var
    elif var in ['wpd_error', 'WPD_ERROR']:
        var = 'wpd_error'
        wrf_var = var
    elif var in ['fitness', 'Fitness', 'FITNESS']:
        var = 'fitness'
        wrf_var = var
    else:
        print(f'Variable {var} is not supported.')
        raise KeyError

    # To start, we need to get the WRF map projection information (a Lambert Conformal grid),
    # and find the domain boundaries in this projection.
    # NOTE: this task MUST occurr before we regrid the WRF variables or the coordinates change and become incompatible.
    wrf_cartopy_proj = get_wrf_proj(wrfdat, 'dni')
    proj_bounds = get_domain_boundary(wrfdat, 'ghi', wrf_cartopy_proj)

    # # Rename the lat-lon corrdinates to get wrf-python to recognize them
    # variables = {'lat': 'XLAT',
    #              'lon': 'XLONG'}
    # try:
    #     wrfdat = xr.Dataset.rename(wrfdat, variables)
    # except ValueError:
    #     print(f'Variables {variables} cannot be renamed, '
    #           f'those on the left are not in this dataset.')
    #
    # # This makes it easy to get the latitude and longitude coordinates with the wrf-python function below
    # lats, lons = wrfpy.latlon_coords(wrfdat['dni'])
    #
    # # I have to do this tedious string parsing below to get the projection from the processed wrfout file.
    # try:
    #     wrf_proj_params = wrfdat.dni.attrs['projection']
    # except AttributeError:
    #     raise ValueError('Variable does not contain projection information')
    # wrf_proj_params = wrf_proj_params.replace('(', ', ')
    # wrf_proj_params = wrf_proj_params.replace(')', '')
    # wrf_proj_params = wrf_proj_params.split(',')
    # wrf_proj = wrf_proj_params[0]
    # stand_lon = float(wrf_proj_params[1].split('=')[1])
    # moad_cen_lat = float(wrf_proj_params[2].split('=')[1])
    # truelat1 = float(wrf_proj_params[3].split('=')[1])
    # truelat2 = float(wrf_proj_params[4].split('=')[1])
    # pole_lat = float(wrf_proj_params[5].split('=')[1])
    # pole_lon = float(wrf_proj_params[6].split('=')[1])
    #
    # # Fortunately, it still apppears to work.
    # if wrf_proj == 'LambertConformal':
    #     wrf_cartopy_proj = ccrs.LambertConformal(central_longitude=stand_lon,
    #                                              central_latitude=moad_cen_lat,
    #                                              standard_parallels=[truelat1, truelat2])
    # else:
    #     print('Your WRF projection is not the expected Lambert Conformal.')
    #     raise ValueError
    #
    # # I need to manually convert the boundaries of the WRF domain into Plate Carree to set the limits.
    # # Get the raw map bounds using a wrf-python utility
    # raw_bounds = wrfpy.util.geo_bounds(wrfdat['dni'])
    # # Get the projected bounds telling cartopy that the input coordinates are lat/lon (Plate Carree)
    # proj_bounds = wrf_cartopy_proj.transform_points(ccrs.PlateCarree(),
    #                                                 np.array([raw_bounds.bottom_left.lon, raw_bounds.top_right.lon]),
    #                                                 np.array([raw_bounds.bottom_left.lat, raw_bounds.top_right.lat]))

    # We can use a basic Plate Carree projection for ERA5
    era5_cartopy_proj = ccrs.PlateCarree()

    # Now, get the desired variables
    if var in ['ghi_error', 'wpd_error', 'fitness']:
        input_year = helper_functions.format_date(datestr).strftime('%Y')
        input_month = helper_functions.format_date(datestr).strftime('%m')
        wrfdat_proc, eradat_proc = optwrf.regridding.wrf_era5_regrid_xesmf(input_year,
                                                                           input_month,
                                                                           wrfdir=wrf_dir,
                                                                           eradir=era_dir)

        # Calculate the error in GHI and WPD
        wrfdat_proc = optwrf.regridding.wrf_era5_error(wrfdat_proc, eradat_proc)

        print(wrfdat_proc)

        # Calculate the fitness
        correction_factor = 0.0004218304553577255
        daylight_factor = helper_functions.daylight_frac(datestr)  # daylight fraction
        wrfdat_proc['fitness'] = daylight_factor * wrfdat_proc.ghi_error \
                                 + correction_factor * wrfdat_proc.wpd_error

    # Define the time indicies from the times variable
    time_indicies = range(0, len(wrfdat.Time))
    # Format the times for title slides
    times_strings_f = wrfdat.Time.dt.strftime('%b %d, %Y %H:%M')
    # Get the desired variable(s)
    for tidx in time_indicies:
        timestr = wrfdat.Time[tidx].values
        timestr_f = times_strings_f[tidx].values
        if hourly:
            title_str = f'{short_title_str}\n{timestr_f} (UTC)'
        else:
            time_string_f = wrfdat.Time[0].dt.strftime('%b %d, %Y')
            title_str = f'{short_title_str}\n{time_string_f.values}'

        # WRF Variable (divide by 1000 to convert from W to kW or Wh to kWh)
        if not hourly and tidx != 0:
            if src == 'wrf':
                if var in ['ghi', 'wpd']:
                    plot_var = plot_var + (wrfdat[wrf_var].sel(Time=np.datetime_as_string(timestr)) / 1000)
                else:
                    plot_var = plot_var + wrfdat_proc[wrf_var].sel(Time=np.datetime_as_string(timestr))
            elif src == 'era5':
                if var in ['ghi', 'wpd']:
                    plot_var = plot_var + (eradat[era_var].sel(Time=np.datetime_as_string(timestr)) / 1000)
                else:
                    print(f'Variable {var} is not provided by {src}')
                    raise ValueError
        else:
            if src == 'wrf':
                if var in ['ghi', 'wpd']:
                    plot_var = wrfdat[wrf_var].sel(Time=np.datetime_as_string(timestr)) / 1000
                else:
                    plot_var = wrfdat_proc[wrf_var].sel(Time=np.datetime_as_string(timestr))
            elif src == 'era5':
                if var in ['ghi', 'wpd']:
                    plot_var = eradat[era_var].sel(Time=np.datetime_as_string(timestr)) / 1000
                else:
                    print(f'Variable {var} is not provided by {src}')
                    raise ValueError

        if hourly:
            # Create a figure
            fig = plt.figure(figsize=(4, 4))

            # Set the GeoAxes to the projection used by WRF
            ax = fig.add_subplot(1, 1, 1, projection=wrf_cartopy_proj)

            # # Get, format, and set the map bounds
            #
            # # Format the projected bounds so they can be used in the xlim and ylim attributes
            # proj_xbounds = [proj_bounds[0, 0], proj_bounds[1, 0]]
            # proj_ybounds = [proj_bounds[0, 1], proj_bounds[1, 1]]
            # # Finally, set the x and y limits
            # ax.set_xlim(proj_xbounds)
            # ax.set_ylim(proj_ybounds)
            #
            # # Download and add the states, coastlines, and lakes
            # states = cfeature.NaturalEarthFeature(category="cultural", scale="50m",
            #                                       facecolor="none",
            #                                       name="admin_1_states_provinces_shp")
            #
            # # Add features to the maps
            # ax.add_feature(states, linewidth=.5, edgecolor="black")
            # ax.add_feature(cfeature.LAKES.with_scale('50m'), alpha=0.9)
            # ax.add_feature(cfeature.OCEAN.with_scale('50m'))

            # Make the countour lines for filled contours for the GHI
            if hourly:
                if var in ['ghi', 'ghi_error']:
                    contour_levels = np.linspace(0, 0.75, 22)
                elif var in ['wpd', 'wpd_error']:
                    contour_levels = np.linspace(0, 2500, 22)
                elif var == 'fitness':
                    contour_levels = np.linspace(0, 1.5, 22)
            else:
                if var in ['ghi', 'ghi_error']:
                    contour_levels = np.linspace(0, 5, 22)
                elif var in ['wpd', 'wpd_error']:
                    contour_levels = np.linspace(0, np.amax(wrfpy.to_np(plot_var)), 22)
                elif var == 'fitness':
                    contour_levels = np.linspace(0, 10, 22)

            # Add the filled contour levels
            if var in ['ghi', 'ghi_error']:
                color_map = get_cmap("hot_r")
            elif var in ['wpd', 'wpd_error']:
                color_map = get_cmap("Greens")
            elif var == 'fitness':
                color_map = get_cmap("Greys")
            if src == 'wrf' and var in ['ghi', 'wpd']:
                cn = ax.contourf(wrfpy.to_np(wrfdat.lat), wrfpy.to_np(wrfdat.lon), wrfpy.to_np(plot_var),
                                 contour_levels, transform=ccrs.PlateCarree(), cmap=color_map)
            else:
                cn = ax.contourf(wrfpy.to_np(eradat.longitude), wrfpy.to_np(eradat.latitude), wrfpy.to_np(plot_var),
                                 contour_levels, transform=ccrs.PlateCarree(), cmap=color_map)

            # Format the plot
            format_cnplot_axis(ax, cn, proj_bounds, title_str=title_str)

            # # Add a color bar
            # plt.colorbar(cn, ax=ax, shrink=.98)

            # # Add the axis title
            # ax.set_title(title_str)

            # Save the figure(s)
            if save_fig:
                fig_path_temp = fig_path + str(tidx).zfill(2)
                plt.savefig(fig_path_temp + '.png', dpi=300, transparent=True, bbox_inches='tight')

            plt.show()

    # Create a figure
    fig = plt.figure(figsize=(4, 4))

    # Set the GeoAxes to the projection used by WRF
    ax = fig.add_subplot(1, 1, 1, projection=wrf_cartopy_proj)

    # # Get, format, and set the map bounds
    #
    # # Format the projected bounds so they can be used in the xlim and ylim attributes
    # proj_xbounds = [proj_bounds[0, 0], proj_bounds[1, 0]]
    # proj_ybounds = [proj_bounds[0, 1], proj_bounds[1, 1]]
    # # Finally, set the x and y limits
    # ax.set_xlim(proj_xbounds)
    # ax.set_ylim(proj_ybounds)
    #
    # # Download and add the states, coastlines, and lakes
    # states = cfeature.NaturalEarthFeature(category="cultural", scale="50m",
    #                                       facecolor="none",
    #                                       name="admin_1_states_provinces_shp")
    #
    # # Add features to the maps
    # ax.add_feature(states, linewidth=.5, edgecolor="black")
    # ax.add_feature(cfeature.LAKES.with_scale('50m'), alpha=0.9)
    # ax.add_feature(cfeature.OCEAN.with_scale('50m'))

    # Make the countour lines for filled contours for the GHI
    if hourly:
        if var in ['ghi', 'ghi_error']:
            contour_levels = np.linspace(0, 0.75, 22)
        elif var in ['wpd', 'wpd_error']:
            contour_levels = np.linspace(0, 25000, 22)
        elif var == 'fitness':
            contour_levels = np.linspace(0, 1.5, 22)
    else:
        if var in ['ghi', 'ghi_error']:
            contour_levels = np.linspace(0, 5, 22)
        elif var in ['wpd', 'wpd_error']:
            contour_levels = np.linspace(0, np.amax(wrfpy.to_np(plot_var)), 22)
        elif var == 'fitness':
            contour_levels = np.linspace(0, 10, 22)

    # Add the filled contour levels
    if var in ['ghi', 'ghi_error']:
        color_map = get_cmap("hot_r")
    elif var in ['wpd', 'wpd_error']:
        color_map = get_cmap("Greens")
    elif var == 'fitness':
        color_map = get_cmap("Greys")
    if src == 'wrf' and var in ['ghi', 'wpd']:
        cn = ax.contourf(wrfpy.to_np(wrfdat.lat), wrfpy.to_np(wrfdat.lat), wrfpy.to_np(plot_var),
                         contour_levels, transform=ccrs.PlateCarree(), cmap=color_map)
    else:
        cn = ax.contourf(wrfpy.to_np(eradat.longitude), wrfpy.to_np(eradat.latitude), wrfpy.to_np(plot_var),
                         contour_levels, transform=ccrs.PlateCarree(), cmap=color_map)

    # Format the plot
    format_cnplot_axis(ax, cn, proj_bounds, title_str=title_str)

    # # Add a color bar
    # plt.colorbar(cn, ax=ax, shrink=.98)
    #
    # # Add the axis title
    # ax.set_title(title_str)

    # Save the figure(s)
    if save_fig:
        plt.savefig(fig_path + '.pdf', transparent=True, bbox_inches='tight')

    plt.show()
total_monthly_wpd_error = []
print('Calculating difference between WRF and ERA5...')
for start_date, end_date in zip(start_dates, end_dates):
    wrf_sim = WRFModel(param_ids, start_date, end_date)
    wrf_sim.DIR_WRFOUT = wrf_sim.DIR_MET4ENE + 'wrfout/ARW/default_all_2011/%s_' % \
                         (wrf_sim.forecast_start.strftime('%Y-%m-%d')) + wrf_sim.paramstr + '/'
    error = wrf_sim.wrf_era5_diff()
    total_monthly_ghi_error.append(error[1])
    total_monthly_wpd_error.append(error[2])
    sys.stdout.flush()
print('!Done!')

# Calculate the annual daily mean error for both GHI and WPD
print('Calulating the annual means...')
total_annual_ghi_error = sum(total_monthly_ghi_error)
daily_mean_ghi_error = total_annual_ghi_error / 365
total_annual_wpd_error = sum(total_monthly_wpd_error)
daily_mean_wpd_error = total_annual_wpd_error / 365
print(f'The annual daily mean GHI error is: {daily_mean_ghi_error} kW m-2')
print(f'The annual daily mean WPD error is: {daily_mean_wpd_error} kW m-2')

# Calculate the annual mean daylight fraction
daylight_factors = []
for jday in range(1, 366):
    daylight_factors.append(hf.daylight_frac(jday))
mean_daylight_factor = sum(daylight_factors) / len(daylight_factors)

# Calculate the correction factor
alpha = (daily_mean_ghi_error / mean_daylight_factor) / daily_mean_wpd_error
print(f'The fitness function correction factor is {alpha}.')