Skip to content

Commit

Permalink
Merge pull request #145 from WISDEM/release-2.3.0
Browse files Browse the repository at this point in the history
Release 2.3.0
  • Loading branch information
akey7 authored May 26, 2020
2 parents f42d2d2 + a624b80 commit 2f582b2
Show file tree
Hide file tree
Showing 16 changed files with 752 additions and 316 deletions.
7 changes: 6 additions & 1 deletion landbosse/excelio/XlsxReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,11 @@ def create_master_input_dictionary(self, project_data_dataframes, project_parame
incomplete_input_dict['gust_velocity_m_per_s'] = project_parameters['50-year Gust Velocity (m/s)']
incomplete_input_dict['project_size_megawatts'] = project_parameters['Number of turbines'] * project_parameters['Turbine rating MW']

if project_parameters['Calculate road cost for distributed wind? (y/n)'] == 'y':
incomplete_input_dict['road_distributed_wind'] = True
else:
incomplete_input_dict['road_distributed_wind'] = False
incomplete_input_dict['site_prep_area_m2'] = project_parameters['Site prep area for Distributed wind (m2)']
incomplete_input_dict['road_length_adder_m'] = project_parameters['Road length adder (m)']
incomplete_input_dict['fraction_new_roads'] = project_parameters['Percent of roads that will be constructed']
incomplete_input_dict['road_quality'] = project_parameters['Road Quality (0-1)']
Expand Down Expand Up @@ -483,7 +488,7 @@ def create_master_input_dictionary(self, project_data_dataframes, project_parame
incomplete_input_dict['overtime_multiplier'] = project_parameters['Overtime multiplier']
incomplete_input_dict['allow_same_flag'] = True if project_parameters['Allow same flag'] == 'y' else False

override_total_mgmt_cost_col_name = 'Override total management cost (0 does not override)'
override_total_mgmt_cost_col_name = 'Override total management cost for distributed (0 does not override)'
if override_total_mgmt_cost_col_name in project_parameters and project_parameters[override_total_mgmt_cost_col_name] > 0:
incomplete_input_dict['override_total_management_cost'] = \
project_parameters[override_total_mgmt_cost_col_name]
Expand Down
339 changes: 237 additions & 102 deletions landbosse/model/CollectionCost.py

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions landbosse/model/CostModule.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ class CostModule:
mobilization cost calculations.
"""

def mobilization_cost_multiplier(self, turbine_rating):
"""
Calculates a mobilization cost term as a function of
turbine rating.
Parameters
----------
turbine_rating : float
Turbine rating in megawatts
Returns
-------
float
The mobilization cost multiplier as a function of turbine rating.
"""

mobilization_cost_multiplier = (36.892 * math.exp(-5e-04 * (turbine_rating * 1000))) / 100
return mobilization_cost_multiplier

def outputs_for_costs_by_module_type_operation(self,
*,
input_df,
Expand Down
91 changes: 66 additions & 25 deletions landbosse/model/FoundationCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,19 @@ def determine_foundation_size(self, foundation_size_input_data, foundation_size_
Foundation volume [in m^3] -> foundation_volume_concrete_m3_per_turbine
"""
#TODO: still updating/fine-tuning foundation size equations for small DW (Parangat - Feb 27, 2020)
r = float(foundation_size_output_data['Radius_m'])
foundation_size_output_data['excavated_volume_m3'] = np.pi * (r + 0.5) ** 2 * foundation_size_input_data['depth']
foundation_size_output_data['foundation_volume_concrete_m3_per_turbine'] = np.pi * r ** 2 * foundation_size_input_data['depth'] * 0.45 # only compute the portion of the foundation that is composed of concrete (45% concrete; other portion is backfill); TODO: Add to sphinx -> (volume excavated = pi*(r_pick + .5m)^2 this assumes vertical sides which does not reflect reality as OSHA requires benched sides over 3’)
if foundation_size_input_data['turbine_rating_MW'] < 0.1:
foundation_size_output_data['excavated_volume_m3'] = r * r * foundation_size_input_data['depth']
foundation_size_output_data['foundation_volume_concrete_m3_per_turbine'] = foundation_size_output_data['excavated_volume_m3'] * 0.45
else:
foundation_size_output_data['excavated_volume_m3'] = np.pi * (r + 0.5) ** 2 * foundation_size_input_data['depth']

# only compute the portion of the foundation that is composed of concrete (45% concrete; other portion is
# backfill); TODO: Add to sphinx -> (volume excavated = pi*(r_pick + .5m)^2 this assumes vertical sides which
# does not reflect reality as OSHA requires benched sides over 3’)
foundation_size_output_data['foundation_volume_concrete_m3_per_turbine'] = np.pi * r ** 2 * \
foundation_size_input_data['depth'] * 0.45

return foundation_size_output_data

Expand Down Expand Up @@ -483,8 +493,13 @@ def estimate_construction_time(self, construction_time_input_data, construction_
construction_time_output_data['material_needs_entire_farm'] = material_needs_per_turbine.copy()
material_needs_entire_farm = construction_time_output_data['material_needs_entire_farm']
material_needs_entire_farm['Quantity of material'] = quantity_materials_entire_farm
operation_data = throughput_operations.where(throughput_operations['Module'] == 'Foundations').dropna(thresh=4)

if construction_time_input_data['turbine_rating_MW'] <= 0.1:
operation_data = throughput_operations.where(
throughput_operations['Module'] == 'Small DW Foundations').dropna(
thresh=4)
else:
operation_data = throughput_operations.where(throughput_operations['Module'] == 'Foundations').dropna(
thresh=4)

#operation data for entire wind farm:
operation_data = pd.merge(material_needs_entire_farm, operation_data, on=['Material type ID'], how='outer')
Expand All @@ -506,14 +521,17 @@ def estimate_construction_time(self, construction_time_input_data, construction_
construction_time_output_data['operation_data_entire_farm'] = operation_data

# pull out management data #TODO: Add this cost to Labor cost next
crew_cost = self.input_dict['crew_cost']
crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')]
management_crew = pd.merge(crew_cost, crew, on=['Labor type ID'])
management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days)
management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days)
management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total'])
self.output_dict['management_crew'] = management_crew
self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew['total_crew_cost_before_wind_delay'].sum()
if construction_time_input_data['turbine_rating_MW'] > 0.1:
crew_cost = self.input_dict['crew_cost']
crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')]
management_crew = pd.merge(crew_cost, crew, on=['Labor type ID'])
management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days)
management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days)
management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total'])
self.output_dict['management_crew'] = management_crew
self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew['total_crew_cost_before_wind_delay'].sum()
else:
self.output_dict['managament_crew_cost_before_wind_delay'] = 0

return construction_time_output_data['operation_data_entire_farm']

Expand Down Expand Up @@ -595,6 +613,7 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic


operation_data = calculate_costs_output_dict['operation_data_entire_farm']

wind_delay = calculate_costs_output_dict['wind_delay_time']

wind_delay_fraction = (wind_delay / calculate_costs_input_dict['operational_hrs_per_day']) / operation_data['Time construct days'].max(skipna=True)
Expand All @@ -605,54 +624,76 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic
calculate_costs_output_dict['wind_multiplier'] = wind_multiplier

rsmeans = calculate_costs_input_dict['rsmeans']
if calculate_costs_input_dict['turbine_rating_MW'] > 0.1:
rsmeans = rsmeans.where(rsmeans['Module'] == 'Foundations').dropna(thresh=4)
else:
rsmeans = rsmeans.where(rsmeans['Module'] == 'Small DW Foundations').dropna(thresh=4)

labor_equip_data = pd.merge(material_vol_entire_farm, rsmeans, on=['Material type ID'])

# Create foundation cost dataframe
foundation_cost = pd.DataFrame(columns=['Type of cost', 'Cost USD', 'Phase of construction'])

# Calculate per diem
per_diem = operation_data['Number of workers'] * operation_data['Number of crews'] * (operation_data['Time construct days'] +
np.ceil(operation_data['Time construct days'] / 7)) * calculate_costs_input_dict['rsmeans_per_diem']
where_are_na_ns = np.isnan(per_diem)
per_diem[where_are_na_ns] = 0
labor_equip_data['Cost USD'] = (labor_equip_data['Quantity of material'] * labor_equip_data['Rate USD per unit'] * calculate_costs_input_dict['overtime_multiplier'] + per_diem + calculate_costs_output_dict['managament_crew_cost_before_wind_delay']) * wind_multiplier
self.output_dict['labor_equip_data'] = labor_equip_data

#Create foundation cost dataframe
foundation_cost = pd.DataFrame(columns=['Type of cost', 'Cost USD', 'Phase of construction'])

#Create equipment costs row to be appended to foundation_cost
# EQUIPMENT COST
# Create equipment costs row to be appended to foundation_cost
equipment_dataframe = labor_equip_data[labor_equip_data['Type of cost'].str.match('Equipment rental')]
equipment_cost_usd_without_delay = (equipment_dataframe['Quantity of material'] * equipment_dataframe['Rate USD per unit'] * calculate_costs_input_dict['overtime_multiplier'] + per_diem)

equipment_cost_usd_without_delay = (
equipment_dataframe['Quantity of material'] * equipment_dataframe['Rate USD per unit'] *
calculate_costs_input_dict['overtime_multiplier'] + per_diem)
equipment_cost_usd_with_weather_delays = equipment_cost_usd_without_delay.sum() * wind_multiplier
equipment_costs = pd.DataFrame([['Equipment rental', equipment_cost_usd_with_weather_delays, 'Foundation']],
columns=['Type of cost', 'Cost USD', 'Phase of construction'])

# LABOR COST
# Create labor costs row to be appended to foundation_cost
labor_dataframe = labor_equip_data[labor_equip_data['Type of cost'].str.match('Labor')]
labor_cost_usd_without_management= (labor_dataframe['Quantity of material'] * labor_dataframe['Rate USD per unit'] * calculate_costs_input_dict['overtime_multiplier'] + per_diem )
labor_cost_usd_with_management = labor_cost_usd_without_management.sum() + calculate_costs_output_dict['managament_crew_cost_before_wind_delay']
labor_cost_usd_with_management_plus_weather_delays = labor_cost_usd_with_management * wind_multiplier
labor_costs = pd.DataFrame([['Labor', labor_cost_usd_with_management_plus_weather_delays, 'Foundation']],
columns=['Type of cost', 'Cost USD', 'Phase of construction'])


columns=['Type of cost', 'Cost USD', 'Phase of construction'])

# MATERIAL COST
material_cost_dataframe = pd.DataFrame(columns=['Operation ID', 'Type of cost', 'Cost USD'])
material_cost_dataframe['Operation ID'] = material_data_entire_farm['Material type ID']
material_cost_dataframe['Type of cost'] = 'Materials'
material_cost_dataframe['Cost USD'] = material_data_entire_farm['Cost USD']
material_costs_sum = material_cost_dataframe['Cost USD'].sum()
material_costs = pd.DataFrame([['Materials', material_costs_sum, 'Foundation']],
columns=['Type of cost', 'Cost USD', 'Phase of construction'])

columns=['Type of cost', 'Cost USD', 'Phase of construction'])

# Append all cost items to foundation_cost
foundation_cost = foundation_cost.append(equipment_costs)
foundation_cost = foundation_cost.append(labor_costs)
foundation_cost = foundation_cost.append(material_costs)

# calculate mobilization cost as percentage of total foundation cost and add to foundation_cost
mob_cost = pd.DataFrame([['Mobilization', foundation_cost['Cost USD'].sum() * 0.05, 'Foundation']],
columns=['Type of cost', 'Cost USD', 'Phase of construction'])
# Calculate mobilization cost as percentage of total foundation cost and add to foundation_cost
# Assumed 5% of total foundation cost and add to foundation_cost for utility scale plant
# A function of turbine size for distributed wind (< 10 turbines)
if calculate_costs_input_dict['num_turbines'] > 10:
mobilization_cost = foundation_cost['Cost USD'].sum() * 0.05
else:
if calculate_costs_input_dict['turbine_rating_MW'] < 0.1:
# Zero since mobilization cost of equipment is included in the equipment rental cost
mobilization_cost = 0
else:
# There is mobilization cost for 0-10 turbines 100+ kW in rating.
num_turbines = calculate_costs_input_dict['num_turbines']
rating = calculate_costs_input_dict['turbine_rating_MW']
mobilization_multipler = self.mobilization_cost_multiplier(rating)
mobilization_cost = foundation_cost['Cost USD'].sum() / num_turbines * mobilization_multipler

mob_cost = pd.DataFrame([['Mobilization', mobilization_cost, 'Foundation']], columns=['Type of cost', 'Cost USD', 'Phase of construction'])

foundation_cost = foundation_cost.append(mob_cost)

# todo: we add a separate tab in the output file for costs (all costs will be the same format but it's a different format than other data)
Expand Down
36 changes: 28 additions & 8 deletions landbosse/model/GridConnectionCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,35 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic
either returns a 0 if the module ran successfully, or it returns the error
raised that caused the failure.
"""

if calculate_costs_input_dict['distance_to_interconnect_mi'] == 0:
calculate_costs_output_dict['trans_dist_usd'] = 0
else:
if calculate_costs_input_dict['new_switchyard'] is True:
calculate_costs_output_dict['interconnect_adder_USD'] = 18115 * self.input_dict['interconnect_voltage_kV'] + 165944
# Switch between utility scale model and distributed model
# Run utility version of GridConnectionCost for project size > 15 MW:
if (calculate_costs_input_dict['turbine_rating_MW'] * calculate_costs_input_dict['num_turbines']) > 15:
if calculate_costs_input_dict['distance_to_interconnect_mi'] == 0:
calculate_costs_output_dict['trans_dist_usd'] = 0
else:
calculate_costs_output_dict['interconnect_adder_USD'] = 0
calculate_costs_output_dict['trans_dist_usd'] = ((1176 * self.input_dict['interconnect_voltage_kV'] + 218257) * (calculate_costs_input_dict['distance_to_interconnect_mi'] ** (-0.1063)) * calculate_costs_input_dict['distance_to_interconnect_mi']) + calculate_costs_output_dict['interconnect_adder_USD']
if calculate_costs_input_dict['new_switchyard'] is True:
calculate_costs_output_dict['interconnect_adder_USD'] = 18115 * self.input_dict['interconnect_voltage_kV'] + 165944
else:
calculate_costs_output_dict['interconnect_adder_USD'] = 0
calculate_costs_output_dict['trans_dist_usd'] = ((1176 * self.input_dict[
'interconnect_voltage_kV'] + 218257) * (calculate_costs_input_dict['distance_to_interconnect_mi'] ** (
-0.1063)) * calculate_costs_input_dict['distance_to_interconnect_mi']) + calculate_costs_output_dict[
'interconnect_adder_USD']

# Run distributed wind version of GridConnectionCost for project size < 15 MW:
else:
# Code below is for newer version of LandBOSSE which incorporates distributed wind into the model:
calculate_costs_output_dict['tower_to_point_of_interconnection_usd_per_kw'] = 1736.7 * ((
calculate_costs_input_dict[
'num_turbines'] *
calculate_costs_input_dict[
'turbine_rating_MW'] * 1000) ** (
-0.272))
calculate_costs_output_dict['trans_dist_usd'] = calculate_costs_input_dict['num_turbines'] * \
calculate_costs_input_dict[
'turbine_rating_MW'] * 1000 * \
calculate_costs_output_dict[
'tower_to_point_of_interconnection_usd_per_kw']


calculate_costs_output_dict['trans_dist_usd_df'] = pd.DataFrame([['Other', calculate_costs_output_dict['trans_dist_usd'], 'Transmission and Distribution']],
Expand Down
Loading

0 comments on commit 2f582b2

Please sign in to comment.