def calc_typical_vmt_per_year(settings, vehicle, alt, model_year, averages_dict): """ This function calculates a typical annual VMT/vehicle over a set number of years as set via the General Inputs workbook. This typical annual VMT/vehicle can then be used to estimate the ages at which warranty and useful life will be reached. When insufficient years are available -- e.g., if the typical_vmt_thru_ageID is set to >5 years and the given vehicle is a MY2041 vintage vehicle and the fleet input file contains data only thru CY2045, then insufficient data exist to calculate the typical VMT for that vehicle -- the typical VMT for that vehicle will be set equal to the last prior MY vintage for which sufficient data were present. Parameters: settings: The SetInputs class.\n vehicle: Tuple; represents a sourcetype_regclass_fueltype vehicle.\n alt: Numeric; the Alternative or option ID.\n model_year: Numeric; the model year of the passed vehicle.\n averages_dict: Dictionary; contains cumulative annual average VMT/vehicle. Returns: A single typical annual VMT/veh value for the passed vehicle of the given model year. """ calcs_avg = FleetAverages(averages_dict) vmt_thru_age_id = settings.repair_inputs_dict['typical_vmt_thru_ageID']['Value'] if model_year + vmt_thru_age_id <= settings.year_max: year = model_year else: year = settings.year_max - vmt_thru_age_id # can't get cumulative VMT when model_year + vmt_thru_age_id exceeds data cumulative_vmt = calcs_avg.get_attribute_value((vehicle, alt, year, vmt_thru_age_id, 0), 'VMT_AvgPerVeh_Cumulative') typical_vmt = cumulative_vmt / (vmt_thru_age_id + 1) return typical_vmt
def calc_direct_costs(totals_dict, averages_dict, sales_arg, program): """ Parameters: totals_dict: Dictionary; into which tech package direct costs will be updated.\n averages_dict: Dictionary; contains tech package direct costs/vehicle.\n sales_arg: String; specifies the sales attribute to use (e.g., "VPOP" or "VPOP_withTech")\n program: String; the program identifier (i.e., 'CAP' or 'GHG'). Returns: The totals_dict dictionary updated with tech package direct costs (package cost * sales). """ if program == 'CAP': arg = 'Direct' else: arg = 'Tech' print(f'\nCalculating {program} {arg} total costs...') calcs = FleetTotals(totals_dict) calcs_avg = FleetAverages(averages_dict) age0_keys = [k for k, v in totals_dict.items() if v['ageID'] == 0] for key in age0_keys: cost_per_veh = calcs_avg.get_attribute_value(key, f'{arg}Cost_AvgPerVeh') sales = calcs.get_attribute_value(key, sales_arg) cost = cost_per_veh * sales temp_dict = {f'{arg}Cost': cost} calcs.update_dict(key, temp_dict) return totals_dict
def calc_tech_costs(totals_dict, averages_dict, sales_arg): """ Parameters:: totals_dict: Dictionary; contains vehicle population (VPOP) data.\n averages_dict: Dictionary; contains average tech costs per vehicle. sales_arg: String; specifies the sales attribute to use (e.g., "VPOP" or "VPOP_withTech") Returns: The totals_dict dictionary updated with annual technology costs for all vehicles. """ print('\nCalculating total technology costs...') calcs_avg = FleetAverages(averages_dict) calcs = FleetTotals(totals_dict) age0_keys = [k for k, v in totals_dict.items() if v['ageID'] == 0] for key in age0_keys: cost_per_veh = calcs_avg.get_attribute_value(key, 'TechCost_AvgPerVeh') sales = calcs.get_attribute_value(key, sales_arg) cost = cost_per_veh * sales temp_dict = {'TechCost': cost} calcs.update_dict(key, temp_dict) return totals_dict
def calc_emission_repair_costs(totals_dict, averages_dict, vpop_arg): """ Parameters: totals_dict: Dictionary; contains annual vehicle populations (VPOP).\n averages_dict: Dictionary; contains annual average emission repair costs/mile.\n vpop_arg: String; specifies the population attribute to use (e.g., "VPOP" or "VPOP_withTech") Returns: The totals_dict dictionary updated with annual emission repair costs for all vehicles. """ print(f'\nCalculating total emission repair costs...') calcs_avg = FleetAverages(averages_dict) calcs = FleetTotals(totals_dict) for key in totals_dict.keys(): cost_per_veh = calcs_avg.get_attribute_value(key, 'EmissionRepairCost_AvgPerVeh') vpop = calcs.get_attribute_value(key, vpop_arg) cost = cost_per_veh * vpop temp_dict = {'EmissionRepairCost': cost} calcs.update_dict(key, temp_dict) return totals_dict
def calc_indirect_costs(settings, totals_dict, averages_dict, sales_arg): """ Parameters: settings: The SetInputs class.\n totals_dict: Dictionary; contains sales data (e.g., sales_arg at age=0).\n averages_dict: Dictionary; contains individual indirect costs per vehicle.\n sales_arg: String; specifies the sales attribute to use (e.g., "VPOP" or "VPOP_withTech") Returns: The totals_dict dictionary updated with total indirect costs for each individual indirect cost property and a summation of those. """ print('\nCalculating CAP total indirect costs...') markup_factors = settings.markup_factors_unique_names.copy() markup_factors.append('Indirect') calcs_avg = FleetAverages(averages_dict) calcs = FleetTotals(totals_dict) age0_keys = [k for k, v in totals_dict.items() if v['ageID'] == 0] for key in age0_keys: temp_dict = dict() for markup_factor in markup_factors: cost_per_veh = calcs_avg.get_attribute_value( key, f'{markup_factor}Cost_AvgPerVeh') sales = calcs.get_attribute_value(key, sales_arg) cost = cost_per_veh * sales temp_dict[f'{markup_factor}Cost'] = cost calcs.update_dict(key, temp_dict) return totals_dict
def calc_average_def_costs(totals_dict, averages_dict, vpop_arg): """ Parameters: totals_dict: Dictionary; provides fleet DEF costs by vehicle. \n averages_dict: Dictionary, into which DEF costs/vehicle will be updated.\n vpop_arg: String; specifies the population attribute to use (e.g., "VPOP" or "VPOP_withTech") Returns: The passed dictionary updated with costs/mile and costs/vehicle associated with DEF consumption. """ print('\nCalculating DEF average costs...') calcs_avg = FleetAverages(averages_dict) calcs = FleetTotals(totals_dict) # get keys where fueltype=2 (Diesel since they are the only vehicles that use DEF) ft2_keys = [k for k, v in averages_dict.items() if v['fuelTypeID'] == 2] for key in ft2_keys: def_cost = calcs.get_attribute_value(key, 'DEFCost') vmt = calcs.get_attribute_value(key, 'VMT') vpop = calcs.get_attribute_value(key, vpop_arg) cost_per_mile = def_cost / vmt cost_per_veh = def_cost / vpop temp_dict = { 'DEFCost_AvgPerMile': cost_per_mile, 'DEFCost_AvgPerVeh': cost_per_veh, } calcs_avg.update_dict(key, temp_dict) return averages_dict
def create_weighted_cost_dict(settings, averages_dict, arg_to_weight, arg_to_weight_by): """ This function weights 'arg_to_weight' attributes by the 'arg_to_weight_by' attribute. Parameters:: settings: The SetInputs class.\n averages_dict: Dictionary; contains fleet average data (e.g., miles/year, cost/year, cost/mile).\n arg_to_weight: String; the attribute to be weighted by the arg_to_weight_by argument.\n arg_to_weight_by: String; the argument to weight by. Returns: A dictionary of arguments weighted by the weight_by argument. Note: The weighting is limited by the number of years (ages) to be included which is set in the general inputs file. The weighting is also limited to model years for which sufficient data exits to include all of those ages. For example, if the maximum calendar year included in the input data is 2045, and the maximum numbers of ages of data to include for each model year is 9 (which would be 10 years of age since year 1 is age 0) then the maximum model year included will be 2035. """ print(f'\nCalculating weighted {arg_to_weight}...') calcs_avg = FleetAverages(averages_dict) wtd_result_dict = dict() weighted_results_dict = dict() for key in averages_dict.keys(): vehicle, alt, model_year, age_id = key[0], key[1], key[2], key[3] st, rc, ft = vehicle if arg_to_weight == 'DEFCost_AvgPerMile' and ft != 2: pass else: if model_year <= (settings.year_max - settings.max_age_included - 1): wtd_result_dict_id = (vehicle, alt, model_year) numerator, denominator = 0, 0 if wtd_result_dict_id in wtd_result_dict: numerator = wtd_result_dict[wtd_result_dict_id]['numerator'] denominator = wtd_result_dict[wtd_result_dict_id]['denominator'] else: pass if age_id <= settings.max_age_included: arg_weight = calcs_avg.get_attribute_value(key, arg_to_weight) arg_weight_by = calcs_avg.get_attribute_value(key, arg_to_weight_by) numerator += arg_weight * arg_weight_by denominator += calcs_avg.get_attribute_value(key, 'VMT_AvgPerVeh') wtd_result_dict[wtd_result_dict_id] = {'numerator': numerator, 'denominator': denominator} for key in wtd_result_dict.keys(): numerator = wtd_result_dict[key]['numerator'] denominator = wtd_result_dict[key]['denominator'] vehicle, alt = key[0], key[1] st, rc, ft = vehicle source_type = Vehicle(st).sourcetype_name() weighted_results_dict[key] = {'optionID': alt, 'sourceTypeName': source_type, 'cents_per_mile': 100 * numerator / denominator} return weighted_results_dict
def calc_per_veh_tech_costs(averages_dict): """ Parameters:: averages_dict: Dictionary; contains average direct and indirect costs per vehicle. Returns: The averages_dict dictionary updated with average tech costs per vehicle (direct plus indirect). Note: Direct and indirect costs apply only for ageID=0 (i.e., new sales). """ print('\nCalculating per vehicle technology costs...') calcs_avg = FleetAverages(averages_dict) age0_keys = [k for k, v in averages_dict.items() if v['ageID'] == 0] for key in age0_keys: cost = calcs_avg.get_attribute_value(key, 'DirectCost_AvgPerVeh') cost += calcs_avg.get_attribute_value(key, 'IndirectCost_AvgPerVeh') temp_dict = {'TechCost_AvgPerVeh': cost} calcs_avg.update_dict(key, temp_dict) return averages_dict
def calc_per_veh_direct_costs(yoy_costs_per_step_dict, cost_steps, averages_dict, program): """ Parameters: yoy_costs_per_step_dict: Dictionary; contains the package cost and cumulative sales used to calculate that package cost (learning effects depend on cumulative sales) for the passed unit in the given model year and complying with the standards set in the given cost step. \n cost_steps: List; provides the cost steps (as strings) associated with the direct costs being calculated.\n averages_dict: Dictionary; into which tech package direct costs/vehicle will be updated.\n program: String; the program identifier (i.e., 'CAP' or 'GHG'). Returns: The averages_dict dictionary updated with tech package costs/vehicle. """ print(f'\nCalculating {program} costs per vehicle...') calcs_avg = FleetAverages(averages_dict) age0_keys = [k for k, v in averages_dict.items() if v['ageID'] == 0] for key in age0_keys: vehicle, alt, model_year, age_id, disc_rate = key st, rc, ft = vehicle engine = (rc, ft) if program == 'CAP': unit = engine else: unit = vehicle if alt == 0: cost = yoy_costs_per_step_dict[(unit, alt, model_year, cost_steps[0])]['Cost_AvgPerVeh'] else: cost = yoy_costs_per_step_dict[(unit, 0, model_year, cost_steps[0])]['Cost_AvgPerVeh'] for step in cost_steps: if model_year >= int(step): cost += yoy_costs_per_step_dict[(unit, alt, model_year, step)]['Cost_AvgPerVeh'] if program == 'GHG': # GHG program costs are to be averaged over all VPOP for the given unit vpop_with_tech = calcs_avg.get_attribute_value(key, 'VPOP_withTech') vpop = calcs_avg.get_attribute_value(key, 'VPOP') cost = cost * vpop_with_tech / vpop temp_dict = {'TechCost_AvgPerVeh': cost} calcs_avg.update_dict(key, temp_dict) else: temp_dict = {'DirectCost_AvgPerVeh': cost} calcs_avg.update_dict(key, temp_dict) return averages_dict
def calc_per_veh_indirect_costs(settings, averages_dict): """ Parameters: settings: The SetInputs class.\n averages_dict: Dictionary; contains tech package direct costs/vehicle. Returns: The averages_dict dictionary updated with indirect costs associated with each markup value along with the summation of those individual indirect costs as "IndirectCost_AvgPerVeh." """ print('\nCalculating CAP per vehicle indirect costs...') calcs_avg = FleetAverages(averages_dict) markup_factors = settings.markup_factors_unique_names.copy() age0_keys = [k for k, v in averages_dict.items() if v['ageID'] == 0] for key in age0_keys: vehicle, alt, model_year, age_id, disc_rate = key st, rc, ft = vehicle engine = (rc, ft) temp_dict = dict() ic_sum = 0 for markup_factor in markup_factors: markup_value = calc_project_markup_value(settings, engine, alt, markup_factor, model_year) per_veh_direct_cost = calcs_avg.get_attribute_value( key, 'DirectCost_AvgPerVeh') cost = markup_value * per_veh_direct_cost temp_dict[f'{markup_factor}Cost_AvgPerVeh'] = cost ic_sum += cost temp_dict['IndirectCost_AvgPerVeh'] = ic_sum calcs_avg.update_dict(key, temp_dict) return averages_dict
def calc_average_fuel_costs(totals_dict, averages_dict, vpop_arg, vmt_arg): """ Parameters: totals_dict: Dictionary; provides fleet fuel costs for all vehicles.\n averages_dict: Dictionary; the destination for fuel costs/vehicle and costs/mile results.\n vpop_arg: String; specifies the population attribute to use (e.g., "VPOP" or "VPOP_withTech")\n vmt_arg: String; specifies the VMT attribute to use (e.g., "VMT" or "VMT_withTech") Returns: The passed averages_dict updated to include fuel costs/vehicle and costs/mile. """ print('\nCalculating average fuel costs...') calcs_avg = FleetAverages(averages_dict) calcs = FleetTotals(totals_dict) for key in averages_dict.keys(): fuel_cost = calcs.get_attribute_value(key, 'FuelCost_Retail') vmt = calcs.get_attribute_value(key, vmt_arg) vpop = calcs.get_attribute_value(key, vpop_arg) # try/except block to protect against divide by 0 error try: cost_per_mile = fuel_cost / vmt cost_per_veh = fuel_cost / vpop except: cost_per_mile = 0 cost_per_veh = 0 temp_dict = {'FuelCost_Retail_AvgPerMile': cost_per_mile, 'FuelCost_Retail_AvgPerVeh': cost_per_veh, } calcs_avg.update_dict(key, temp_dict) return averages_dict
def calc_per_veh_emission_repair_costs(averages_dict): """ Parameters: averages_dict: Dictionary; contains annual emission repair costs/mile. Returns: The passed dictionary updated with annual emission repair costs/vehicle for each dictionary key. """ print('\nCalculating emission repair costs per vehicle...') calcs_avg = FleetAverages(averages_dict) for key in averages_dict.keys(): repair_cpm = calcs_avg.get_attribute_value(key, 'EmissionRepairCost_AvgPerMile') vmt_per_veh = calcs_avg.get_attribute_value(key, 'VMT_AvgPerVeh') cost_per_veh = repair_cpm * vmt_per_veh temp_dict = {'EmissionRepairCost_AvgPerVeh': cost_per_veh} calcs_avg.update_dict(key, temp_dict) return averages_dict
if __name__ == '__main__': import pandas as pd from pathlib import Path from bca_tool_code.tool_setup import SetInputs from bca_tool_code.project_fleet import create_fleet_df settings = SetInputs() path_project = Path(__file__).parent.parent path_dev = path_project / 'dev' path_dev.mkdir(exist_ok=True) # create project fleet DataFrame which will include adjustments to the MOVES input file that are unique to the project. cap_fleet_df = create_fleet_df(settings, settings.moves_cap, settings.options_cap_dict, settings.moves_adjustments_cap_dict, 'VPOP', 'VMT', 'Gallons') # create totals, averages and sales by regclass dictionaries cap_totals_dict, cap_averages_dict, regclass_sales_dict = dict(), dict(), dict() cap_totals_dict = FleetTotals(cap_totals_dict).create_fleet_totals_dict(settings, cap_fleet_df) cap_averages_dict = FleetAverages(cap_averages_dict).create_fleet_averages_dict(settings, cap_fleet_df) regclass_sales_dict = FleetTotals(regclass_sales_dict).create_regclass_sales_dict(cap_fleet_df) # calculate direct costs per reg class based on cumulative regclass sales (learning is applied to cumulative sales) regclass_yoy_costs_per_step = calc_yoy_costs_per_step(settings, regclass_sales_dict, 'VPOP_withTech', 'CAP') df = pd.DataFrame(regclass_yoy_costs_per_step).transpose() df.to_csv(path_dev / 'regclass_yoy_costs_per_step.csv', index=True) print(f'\nOutput files have been saved to {path_dev}\n')
def calc_emission_repair_costs_per_mile(settings, averages_dict): """ Parameters: settings: The SetInputs class.\n averages_dict: Dictionary; contains tech package direct costs/vehicle and cumulative annual average VMT/vehicle. Returns: The averages_dict dictionary updated to include emission repair costs/mile for each dictionary key.\n A repair cost/mile dictionary containing details used in the calculation of repair cost/mile and which is then written to an output file for the given run.\n An 'estimated ages' dictionary containing details behind the calculations and which is then written to an output file for the given run. """ print('\nCalculating emission repair costs per mile...') calcs_avg = FleetAverages(averages_dict) repair_cpm_dict = dict() estimated_ages_dict = dict() for key in averages_dict.keys(): vehicle, alt, model_year, age_id, disc_rate = key reference_direct_cost = calcs_avg.get_attribute_value(((61, 47, 2), 0, model_year, 0, 0), 'DirectCost_AvgPerVeh') # sourcetype here is arbitrary provided it is of diesel regclass 47 direct_cost = calcs_avg.get_attribute_value((vehicle, alt, model_year, 0, 0), 'DirectCost_AvgPerVeh') direct_cost_scaler = direct_cost / reference_direct_cost typical_vmt = calc_typical_vmt_per_year(settings, vehicle, alt, model_year, averages_dict) warranty_estimated_age, estimated_ages_dict = calc_estimated_age(settings, vehicle, alt, model_year, 'Warranty', typical_vmt, estimated_ages_dict) usefullife_estimated_age, estimated_ages_dict = calc_estimated_age(settings, vehicle, alt, model_year, 'Usefullife', typical_vmt, estimated_ages_dict) in_warranty_cpm = settings.repair_inputs_dict['in-warranty_R&M_CPM']['Value'] \ * settings.repair_inputs_dict['emission_repair_share']['Value'] \ * direct_cost_scaler at_usefullife_cpm = settings.repair_inputs_dict['at-usefullife_R&M_CPM']['Value'] \ * settings.repair_inputs_dict['emission_repair_share']['Value'] \ * direct_cost_scaler if usefullife_estimated_age > warranty_estimated_age: slope_within_usefullife = (at_usefullife_cpm - in_warranty_cpm) / (usefullife_estimated_age - warranty_estimated_age) else: slope_within_usefullife = 0 max_cpm = settings.repair_inputs_dict['max_R&M_CPM']['Value'] \ * settings.repair_inputs_dict['emission_repair_share']['Value'] \ * direct_cost_scaler # now calulate the cost per mile if (age_id + 1) < warranty_estimated_age: cpm = in_warranty_cpm elif warranty_estimated_age <= (age_id + 1) < usefullife_estimated_age: cpm = slope_within_usefullife * ((age_id + 1) - warranty_estimated_age) + in_warranty_cpm elif (age_id + 1) == usefullife_estimated_age: cpm = at_usefullife_cpm else: cpm = max_cpm temp_dict = {'EmissionRepairCost_AvgPerMile': cpm} calcs_avg.update_dict(key, temp_dict) repair_cpm_dict[key] = {'reference_direct_cost': reference_direct_cost, 'direct_cost_scaler': direct_cost_scaler, 'warranty_estimated_age': warranty_estimated_age, 'usefullife_estimated_age': usefullife_estimated_age, 'in_warranty_cpm': in_warranty_cpm, 'at_usefullife_cpm': at_usefullife_cpm, 'slope_within_usefullife': slope_within_usefullife, 'max_cpm': max_cpm, 'cpm': cpm } return averages_dict, repair_cpm_dict, estimated_ages_dict
def main(): """ Returns: The results of the current run of the tool. """ start_time_calcs = time.time() set_paths = SetPaths() run_id = set_paths.run_id() settings = SetInputs() print("\nDoing the work...\n") if settings.calc_cap: # create project fleet DataFrame which will include adjustments to the MOVES input file that are unique to the project. cap_fleet_df = create_fleet_df(settings, settings.moves_cap, settings.options_cap_dict, settings.moves_adjustments_cap_dict, 'VPOP', 'VMT', 'Gallons') # create totals, averages and sales by regclass dictionaries cap_totals_dict, cap_averages_dict, regclass_sales_dict = dict(), dict(), dict() cap_totals_dict = FleetTotals(cap_totals_dict).create_fleet_totals_dict(settings, cap_fleet_df) cap_averages_dict = FleetAverages(cap_averages_dict).create_fleet_averages_dict(settings, cap_fleet_df) regclass_sales_dict = FleetTotals(regclass_sales_dict).create_regclass_sales_dict(cap_fleet_df) # calculate direct costs per reg class based on cumulative regclass sales (learning is applied to cumulative sales) regclass_yoy_costs_per_step = calc_yoy_costs_per_step(settings, regclass_sales_dict, 'VPOP_withTech', 'CAP') # calculate average (per vehicle) then total direct costs cap_averages_dict = calc_per_veh_direct_costs(regclass_yoy_costs_per_step, settings.cost_steps_regclass, cap_averages_dict, 'CAP') cap_totals_dict = calc_direct_costs(cap_totals_dict, cap_averages_dict, 'VPOP_withTech', 'CAP') # calculate average then total indirect costs cap_averages_dict = calc_per_veh_indirect_costs(settings, cap_averages_dict) cap_totals_dict = calc_indirect_costs(settings, cap_totals_dict, cap_averages_dict, 'VPOP_withTech') # calculate average then total tech costs (direct + indirect) cap_averages_dict = calc_per_veh_tech_costs(cap_averages_dict) cap_totals_dict = calc_tech_costs(cap_totals_dict, cap_averages_dict, 'VPOP_withTech') # calculate total then average DEF costs cap_totals_dict = calc_def_costs(settings, cap_totals_dict, 'Gallons_withTech') cap_averages_dict = calc_average_def_costs(cap_totals_dict, cap_averages_dict, 'VPOP_withTech') # calculate total then average fuel costs, including adjustments for fuel consumption associated with ORVR cap_totals_dict = calc_fuel_costs(settings, cap_totals_dict, 'Gallons_withTech', 'CAP') cap_averages_dict = calc_average_fuel_costs(cap_totals_dict, cap_averages_dict, 'VPOP_withTech', 'VMT_withTech') # calculate average then total emission repair costs cap_averages_dict, repair_cpm_dict, estimated_ages_dict = calc_emission_repair_costs_per_mile(settings, cap_averages_dict) cap_averages_dict = calc_per_veh_emission_repair_costs(cap_averages_dict) cap_totals_dict = calc_emission_repair_costs(cap_totals_dict, cap_averages_dict, 'VPOP_withTech') # sum operating costs and operating-tech costs into a single key, value # the totals_dict here uses pre-tax fuel price since it serves as the basis for social costs # the averages_dict uses retail fuel prices since it serves as the basis for average operating costs which are relevant to owners cap_totals_dict = calc_sum_of_costs(cap_totals_dict, 'OperatingCost', 'DEFCost', 'FuelCost_Pretax', 'EmissionRepairCost') cap_totals_dict = calc_sum_of_costs(cap_totals_dict, 'TechAndOperatingCost', 'TechCost', 'OperatingCost') cap_averages_dict = calc_sum_of_costs(cap_averages_dict, 'OperatingCost_Owner_AvgPerMile', 'DEFCost_AvgPerMile', 'FuelCost_Retail_AvgPerMile', 'EmissionRepairCost_AvgPerMile') cap_averages_dict = calc_sum_of_costs(cap_averages_dict, 'OperatingCost_Owner_AvgPerVeh', 'DEFCost_AvgPerVeh', 'FuelCost_Retail_AvgPerVeh', 'EmissionRepairCost_AvgPerVeh') # calc emission effects, if applicable if settings.calc_cap_pollution_effects: cap_totals_dict = calc_criteria_emission_costs(settings, cap_totals_dict) # calculate some weighted (wtd) cost per mile (cpm) operating costs wtd_def_cpm_dict = create_weighted_cost_dict(settings, cap_averages_dict, 'DEFCost_AvgPerMile', 'VMT_AvgPerVeh') wtd_repair_cpm_dict = create_weighted_cost_dict(settings, cap_averages_dict, 'EmissionRepairCost_AvgPerMile', 'VMT_AvgPerVeh') wtd_cap_fuel_cpm_dict = create_weighted_cost_dict(settings, cap_averages_dict, 'FuelCost_Retail_AvgPerMile', 'VMT_AvgPerVeh') # discount monetized values cap_totals_dict = discount_values(settings, cap_totals_dict, 'CAP', 'totals') cap_averages_dict = discount_values(settings, cap_averages_dict, 'CAP', 'averages') # calc annual sums, present and annualized values cap_pv_annualized_dict = pv_annualized(settings, cap_totals_dict, 'CAP') # calculate deltas relative to the passed no action alternative ID cap_totals_dict = calc_deltas(settings, cap_totals_dict) cap_averages_dict = calc_deltas(settings, cap_averages_dict) cap_pv_annualized_dict = calc_deltas(settings, cap_pv_annualized_dict) wtd_def_cpm_dict = calc_deltas_weighted(settings, wtd_def_cpm_dict) wtd_repair_cpm_dict = calc_deltas_weighted(settings, wtd_repair_cpm_dict) wtd_cap_fuel_cpm_dict = calc_deltas_weighted(settings, wtd_cap_fuel_cpm_dict) if settings.calc_ghg: # create project fleet DataFrame which will include adjustments to the MOVES input file that are unique to the project. ghg_fleet_df = create_fleet_df(settings, settings.moves_ghg, settings.options_ghg_dict, settings.moves_adjustments_ghg_dict, 'VPOP') # create totals, averages and sales by sourcetype dictionaries sourcetype_sales_dict, ghg_totals_dict, ghg_averages_dict = dict(), dict(), dict() ghg_totals_dict = FleetTotals(ghg_totals_dict).create_fleet_totals_dict(settings, ghg_fleet_df) ghg_averages_dict = FleetAverages(ghg_averages_dict).create_fleet_averages_dict(settings, ghg_fleet_df) sourcetype_sales_dict = FleetTotals(sourcetype_sales_dict).create_sourcetype_sales_dict(ghg_fleet_df) # calculate tech costs per sourcetype based on cumulative sourcetype sales (learning is applied to cumulative sales) sourcetype_yoy_costs_per_step = calc_yoy_costs_per_step(settings, sourcetype_sales_dict, 'VPOP_withTech', 'GHG') # calculate average (per vehicle) then total tech costs ghg_averages_dict = calc_per_veh_direct_costs(sourcetype_yoy_costs_per_step, settings.cost_steps_sourcetype, ghg_averages_dict, 'GHG') ghg_totals_dict = calc_direct_costs(ghg_totals_dict, ghg_averages_dict, 'VPOP', 'GHG') # calculate total then average fuel costs ghg_totals_dict = calc_fuel_costs(settings, ghg_totals_dict, 'Gallons', 'GHG') ghg_averages_dict = calc_average_fuel_costs(ghg_totals_dict, ghg_averages_dict, 'VPOP', 'VMT') # sum operating costs and operating-tech costs into a single key, value # the totals_dict here uses pre-tax fuel price since it serves as the basis for social costs # the averages_dict uses retail fuel prices since it serves as the basis for average operating costs which are relevant to owners ghg_totals_dict = calc_sum_of_costs(ghg_totals_dict, 'OperatingCost', 'FuelCost_Pretax') ghg_totals_dict = calc_sum_of_costs(ghg_totals_dict, 'TechAndOperatingCost', 'TechCost', 'OperatingCost') ghg_averages_dict = calc_sum_of_costs(ghg_averages_dict, 'OperatingCost_Owner_AvgPerVeh', 'FuelCost_Retail_AvgPerVeh') # calc emission effects, if applicable if settings.calc_ghg_pollution_effects: pass # ghg_totals_dict = calc_ghg_emission_costs(settings, ghg_totals_dict) # calculate some weighted (wtd) cost per mile (cpm) operating costs # wtd_ghg_fuel_cpm_dict = create_weighted_cost_dict(settings, ghg_averages_dict, 'FuelCost_Retail_AvgPerMile', 'VMT_AvgPerVeh') # discount monetized values ghg_totals_dict = discount_values(settings, ghg_totals_dict, 'GHG', 'totals') ghg_averages_dict = discount_values(settings, ghg_averages_dict, 'GHG', 'averages') # calc annual sums, present and annualized values ghg_pv_annualized_dict = pv_annualized(settings, ghg_totals_dict, 'GHG') # calculate deltas relative to the passed no action alternative ID ghg_totals_dict = calc_deltas(settings, ghg_totals_dict) ghg_averages_dict = calc_deltas(settings, ghg_averages_dict) ghg_pv_annualized_dict = calc_deltas(settings, ghg_pv_annualized_dict) # wtd_ghg_fuel_cpm_dict = calc_deltas_weighted(settings, wtd_ghg_fuel_cpm_dict, 'FuelCost_Retail_AvgPerMile') elapsed_time_calcs = time.time() - start_time_calcs # determine run output paths if run_id == 'test': path_of_run_results_folder = set_paths.path_test path_of_run_results_folder.mkdir(exist_ok=True) path_of_run_folder = path_of_run_results_folder else: path_of_run_folder, path_of_run_inputs_folder, path_of_run_results_folder, path_of_modified_inputs_folder, path_of_code_folder \ = set_paths.create_output_paths(settings.start_time_readable, run_id) start_time_postproc = time.time() # pass dicts thru the vehicle_name and/or option_name function to add some identifiers and generate some figures if settings.calc_cap: # add identifier attributes cap_totals_dict = Vehicle().vehicle_name(settings, settings.options_cap_dict, cap_totals_dict) cap_averages_dict = Vehicle().vehicle_name(settings, settings.options_cap_dict, cap_averages_dict) cap_pv_annualized_dict = Vehicle().option_name(settings, settings.options_cap_dict, cap_pv_annualized_dict) # rearrange columns for better presentation cap_totals_df = pd.DataFrame(cap_totals_dict).transpose() cols = [col for col in cap_totals_df.columns if col not in settings.row_header_for_fleet_files] cap_totals_df = pd.DataFrame(cap_totals_df, columns=settings.row_header_for_fleet_files + cols) cap_averages_df = pd.DataFrame(cap_averages_dict).transpose() cols = [col for col in cap_averages_df.columns if col not in settings.row_header_for_fleet_files] cap_averages_df = pd.DataFrame(cap_averages_df, columns=settings.row_header_for_fleet_files + cols) cap_pv_annualized_df = pd.DataFrame(cap_pv_annualized_dict).transpose() cols = [col for col in cap_pv_annualized_df.columns if col not in settings.row_header_for_annual_summary_files] cap_pv_annualized_df = pd.DataFrame(cap_pv_annualized_df, columns=settings.row_header_for_annual_summary_files + cols) if settings.calc_ghg: # add identifier attributes ghg_totals_dict = Vehicle().vehicle_name(settings, settings.options_ghg_dict, ghg_totals_dict) ghg_averages_dict = Vehicle().vehicle_name(settings, settings.options_ghg_dict, ghg_averages_dict) ghg_pv_annualized_dict = Vehicle().option_name(settings, settings.options_ghg_dict, ghg_pv_annualized_dict) # rearrange columns for better presentation ghg_totals_df = pd.DataFrame(ghg_totals_dict).transpose() cols = [col for col in ghg_totals_df.columns if col not in settings.row_header_for_fleet_files] ghg_totals_df = pd.DataFrame(ghg_totals_df, columns=settings.row_header_for_fleet_files + cols) ghg_averages_df = pd.DataFrame(ghg_averages_dict).transpose() cols = [col for col in ghg_averages_df.columns if col not in settings.row_header_for_fleet_files] ghg_averages_df = pd.DataFrame(ghg_averages_df, columns=settings.row_header_for_fleet_files + cols) ghg_pv_annualized_df = pd.DataFrame(ghg_pv_annualized_dict).transpose() cols = [col for col in ghg_pv_annualized_df.columns if col not in settings.row_header_for_annual_summary_files] ghg_pv_annualized_df = pd.DataFrame(ghg_pv_annualized_df, columns=settings.row_header_for_annual_summary_files + cols) elapsed_time_postproc = time.time() - start_time_postproc start_time_outputs = time.time() # copy input files into results folder; also save fuel_prices and reshaped files to this folder print('\nCopying input files and code to the outputs folder...\n') if run_id == 'test': pass else: inputs_filename_list = inputs_filenames(settings.input_files_pathlist) for file in inputs_filename_list: path_source = set_paths.path_inputs / file path_destination = path_of_run_inputs_folder / file shutil.copy2(path_source, path_destination) for file in set_paths.files_in_code_folder(): try: shutil.copy2(file, path_of_code_folder / file.name) except: print('\nUnable to copy Python code to run results folder when using the executable.\n') settings.fuel_prices.to_csv(path_of_modified_inputs_folder / f'fuel_prices_{settings.aeo_case}.csv', index=False) settings.regclass_costs.to_csv(path_of_modified_inputs_folder / 'regclass_costs.csv', index=False) settings.sourcetype_costs.to_csv(path_of_modified_inputs_folder / 'sourcetype_costs.csv', index=False) settings.repair_and_maintenance.to_csv(path_of_modified_inputs_folder / 'repair_and_maintenance.csv') settings.def_prices.to_csv(path_of_modified_inputs_folder / 'def_prices.csv', index=False) gdp_deflators = pd.DataFrame(settings.gdp_deflators) # from dict to df gdp_deflators.to_csv(path_of_modified_inputs_folder / 'gdp_deflators.csv', index=True) # save dictionaries to csv and also add some identifying info using the vehicle_name function print("\nSaving the output files...\n") if settings.calc_cap: cap_totals_df.to_csv(path_of_run_results_folder / f'CAP_bca_tool_fleet_totals_{settings.start_time_readable}.csv', index=False) cap_averages_df.to_csv(path_of_run_results_folder / f'CAP_bca_tool_fleet_averages_{settings.start_time_readable}.csv', index=False) cap_pv_annualized_df.to_csv(path_of_run_results_folder / f'CAP_bca_tool_annual_summary_{settings.start_time_readable}.csv', index=False) save_dict_to_csv(Vehicle().vehicle_name(settings, settings.options_cap_dict, estimated_ages_dict), path_of_run_results_folder / f'CAP_bca_tool_estimated_ages_{settings.start_time_readable}', list(), 'vehicle', 'optionID', 'modelYearID', 'identifier') save_dict_to_csv(Vehicle().vehicle_name(settings, settings.options_cap_dict, repair_cpm_dict), path_of_run_results_folder / f'CAP_bca_tool_repair_cpm_details_{settings.start_time_readable}', list(), 'vehicle', 'optionID', 'modelYearID', 'ageID', 'DiscountRate') save_dict_to_csv(wtd_def_cpm_dict, path_of_run_results_folder / f'CAP_bca_tool_vmt_weighted_def_cpm_{settings.start_time_readable}', list(), 'vehicle', 'optionID', 'modelYearID') save_dict_to_csv(wtd_repair_cpm_dict, path_of_run_results_folder / f'CAP_bca_tool_vmt_weighted_emission_repair_cpm_{settings.start_time_readable}', list(), 'vehicle', 'optionID', 'modelYearID') save_dict_to_csv(wtd_cap_fuel_cpm_dict, path_of_run_results_folder / f'CAP_bca_tool_vmt_weighted_fuel_cpm_{settings.start_time_readable}', list(), 'vehicle', 'optionID', 'modelYearID') # create figures arg_list = ['TechCost', 'EmissionRepairCost', 'DEFCost', 'FuelCost_Pretax', 'TechAndOperatingCost'] CreateFigures(cap_pv_annualized_df, 'US Dollars', path_of_run_results_folder, 'CAP').create_figures(arg_list) if settings.calc_ghg: ghg_totals_df.to_csv(path_of_run_results_folder / f'GHG_bca_tool_fleet_totals_{settings.start_time_readable}.csv', index=False) ghg_averages_df.to_csv(path_of_run_results_folder / f'GHG_bca_tool_fleet_averages_{settings.start_time_readable}.csv', index=False) ghg_pv_annualized_df.to_csv(path_of_run_results_folder / f'GHG_bca_tool_annual_summary_{settings.start_time_readable}.csv', index=False) # create figures arg_list = ['TechCost', 'FuelCost_Pretax', 'TechAndOperatingCost'] CreateFigures(ghg_pv_annualized_df, 'US Dollars', path_of_run_results_folder, 'GHG').create_figures(arg_list) elapsed_time_outputs = time.time() - start_time_outputs end_time = time.time() end_time_readable = datetime.now().strftime('%Y%m%d-%H%M%S') elapsed_time = end_time - settings.start_time summary_log = pd.DataFrame(data={'Item': ['Version', 'Run folder', 'Calc CAP costs', 'Calc CAP pollution', 'Calc GHG costs', 'Calc GHG pollution', 'Start of run', 'Elapsed time read inputs', 'Elapsed time calculations', 'Elapsed time post-processing', 'Elapsed time save outputs', 'End of run', 'Elapsed runtime'], 'Results': [bca_tool_code.__version__, path_of_run_folder, settings.calc_cap_value, settings.calc_cap_pollution_effects_value, settings.calc_ghg_value, settings.calc_ghg_pollution_effects_value, settings.start_time_readable, settings.elapsed_time_read, elapsed_time_calcs, elapsed_time_postproc, elapsed_time_outputs, end_time_readable, elapsed_time], 'Units': ['', '', '', '', '', '', 'YYYYmmdd-HHMMSS', 'seconds', 'seconds', 'seconds', 'seconds', 'YYYYmmdd-HHMMSS', 'seconds']}) summary_log = pd.concat([summary_log, get_file_datetime(settings.input_files_pathlist)], axis=0, sort=False, ignore_index=True) summary_log.to_csv(path_of_run_results_folder.joinpath('summary_log.csv'), index=False) print(f'\nOutput files have been saved to {path_of_run_folder}\n')
def discount_values(settings, dict_of_values, program, arg): """ The discount function determines metrics appropriate for discounting (those contained in dict_of_values) and does the discounting calculation to a given year and point within that year. Parameters: settings: The SetInputs class.\n dict_of_values: Dictionary; provides values to be discounted with keys consisting of vehicle, model_year, age_id and discount rate.\n program: String; indicates what program is being passed. arg: String; indicates whether totals or averages are being discounted. Returns: The passed dictionary with new key, value pairs where keys stipulate the discount rate and monetized values are discounted at the same rate as the discount rate of the input stream of values. Note: The costs_start entry of the BCA_General_Inputs file should be set to 'start-year' or 'end-year', where start-year represents costs starting at time t=0 (i.e., first year costs are undiscounted), and end-year represents costs starting at time t=1 (i.e., first year costs are discounted). """ print(f'\nDiscounting values for {program} {arg}...') if arg == 'totals': calcs = FleetTotals(dict_of_values) else: calcs = FleetAverages(dict_of_values) # get cost attributes d = [nested_dict for key, nested_dict in dict_of_values.items()][0] all_costs = [k for k, v in d.items() if 'Cost' in k] emission_cost_args_25 = [item for item in all_costs if '_0.025' in item] emission_cost_args_3 = [item for item in all_costs if '_0.03' in item] emission_cost_args_5 = [item for item in all_costs if '_0.05' in item] emission_cost_args_7 = [item for item in all_costs if '_0.07' in item] non_emission_cost_args = [item for item in all_costs if '_0.0' not in item] if settings.costs_start == 'start-year': discount_offset = 0 elif settings.costs_start == 'end-year': discount_offset = 1 discount_to_year = settings.discount_to_yearID for key in dict_of_values.keys(): vehicle, alt, model_year, age_id, rate = key if rate == 0: pass # no need to discount undiscounted values with 0 percent discount rate else: year = model_year + age_id temp_dict = dict() for arg in non_emission_cost_args: arg_value = calcs.get_attribute_value(key, arg) arg_value_discounted = arg_value / ( (1 + rate)**(year - discount_to_year + discount_offset)) temp_dict[arg] = arg_value_discounted emission_rate = 0.025 for arg in emission_cost_args_25: arg_value = calcs.get_attribute_value(key, arg) arg_value_discounted = arg_value / ((1 + emission_rate)**( year - discount_to_year + discount_offset)) temp_dict[arg] = arg_value_discounted emission_rate = 0.03 for arg in emission_cost_args_3: arg_value = calcs.get_attribute_value(key, arg) arg_value_discounted = arg_value / ((1 + emission_rate)**( year - discount_to_year + discount_offset)) temp_dict[arg] = arg_value_discounted emission_rate = 0.05 for arg in emission_cost_args_5: arg_value = calcs.get_attribute_value(key, arg) arg_value_discounted = arg_value / ((1 + emission_rate)**( year - discount_to_year + discount_offset)) temp_dict[arg] = arg_value_discounted emission_rate = 0.07 for arg in emission_cost_args_7: arg_value = calcs.get_attribute_value(key, arg) arg_value_discounted = arg_value / ((1 + emission_rate)**( year - discount_to_year + discount_offset)) temp_dict[arg] = arg_value_discounted calcs.update_dict(key, temp_dict) return dict_of_values