Skip to content

Commit

Permalink
Merge pull request #190 from WISDEM/develop
Browse files Browse the repository at this point in the history
Batch of fixes and updates from old branches
  • Loading branch information
gbarter authored Jun 19, 2023
2 parents 1920f8e + 6838b5f commit 6575457
Show file tree
Hide file tree
Showing 20 changed files with 404 additions and 163 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/CI_LandBOSSE.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: False
matrix:
os: ["ubuntu-latest", "windows-latest"]
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
fail-fast: False
matrix:
os: ["ubuntu-latest", "windows-latest"]
python-version: [3.7, 3.8, 3.9]
python-version: ["3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
Expand Down
28 changes: 0 additions & 28 deletions .travis.yml

This file was deleted.

1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[![Build Status](https://travis-ci.com/WISDEM/LandBOSSE.svg?branch=issue_136_add_tests)](https://travis-ci.com/WISDEM/LandBOSSE)
# LandBOSSE

## Welcome to LandBOSSE!
Expand Down
9 changes: 7 additions & 2 deletions landbosse/excelio/WeatherWindowCSVReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,13 @@ def read_weather_window(weather_data, local_timezone='America/Denver'):
weather_data = weather_data.reset_index(drop=True)
weather_data = weather_data[renamed_columns.values()]

# Parse the datetime data and localize it to UTC
weather_data['Date UTC'] = pd.to_datetime(weather_data['Date UTC']).dt.tz_localize('UTC')
# Parse the datetime data and localize it to UTC. If the timestamp does not contain
# a timestamp, assume it is in UTC and process with tz_localize. If it contains a
# timestamp, assume it is in UTC and use tz_convert.
try:
weather_data['Date UTC'] = pd.to_datetime(weather_data['Date UTC']).dt.tz_localize('UTC')
except TypeError as err:
weather_data['Date UTC'] = pd.to_datetime(weather_data['Date UTC']).dt.tz_convert('UTC')

# Convert UTC to local time
weather_data['Date'] = weather_data['Date UTC'].dt.tz_convert(local_timezone)
Expand Down
2 changes: 1 addition & 1 deletion landbosse/excelio/XlsxReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def modify_project_data_and_project_list(self, project_data_dataframes, project_
cell_spec_re = re.compile('^.*/.*/.*$')

# Go through each project parameter
for index, value in project_parameters.iteritems():
for index, value in project_parameters.items():

# If the column specifies a cell to change in the dataframe
# or project list, inspect it to ensure it points somewhere
Expand Down
63 changes: 34 additions & 29 deletions landbosse/landbosse_omdao/landbosse.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import openmdao.api as om
from math import ceil
import numpy as np
import warnings
from math import ceil

with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="numpy.ufunc size changed")
import pandas as pd
import numpy as np
import openmdao.api as om

from landbosse.model.Manager import Manager
from landbosse.model.DefaultMasterInputDict import DefaultMasterInputDict
from landbosse.landbosse_omdao.OpenMDAODataframeCache import OpenMDAODataframeCache
from landbosse.landbosse_omdao.WeatherWindowCSVReader import read_weather_window

with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="numpy.ufunc size changed")
import pandas as pd


use_default_component_data = -1.0


class LandBOSSE(om.Group):
def setup(self):

# Add a tower section height variable. The default value of 30 m is for transportable tower sections.
self.set_input_defaults("tower_section_length_m", 30.0, units="m")
self.set_input_defaults("blade_drag_coefficient", use_default_component_data) # Unitless
Expand All @@ -26,6 +27,7 @@ def setup(self):
self.set_input_defaults("blade_offload_hook_height", use_default_component_data, units="m")
self.set_input_defaults("blade_offload_cycle_time", use_default_component_data, units="h")
self.set_input_defaults("blade_drag_multiplier", use_default_component_data) # Unitless
self.set_input_defaults("blade_surface_area", use_default_component_data, units="m**2")

self.set_input_defaults("turbine_spacing_rotor_diameters", 4)
self.set_input_defaults("row_spacing_rotor_diameters", 10)
Expand Down Expand Up @@ -65,6 +67,7 @@ def setup_inputs(self):
self.add_input("blade_offload_hook_height", use_default_component_data, units="m")
self.add_input("blade_offload_cycle_time", use_default_component_data, units="h")
self.add_input("blade_drag_multiplier", use_default_component_data) # Unitless
self.add_input("blade_surface_area", use_default_component_data, units="m**2")

# Even though LandBOSSE doesn't use foundation height, TowerSE does,
# and foundation height can be used with hub height to calculate
Expand Down Expand Up @@ -447,7 +450,7 @@ def prepare_master_input_dictionary(self, inputs, discrete_inputs):

# Needed to avoid distributed wind keys
incomplete_input_dict["road_distributed_wind"] = False

defaults = DefaultMasterInputDict()
master_input_dict = defaults.populate_input_dict(incomplete_input_dict)

Expand Down Expand Up @@ -648,21 +651,21 @@ def modify_component_lists(self, inputs, discrete_inputs):
kg_per_tonne = 1000

# Get the hub height
hub_height_meters = inputs["hub_height_meters"][0]
hub_height_meters = float(inputs["hub_height_meters"])

# Make the nacelle. This does not include the hub or blades.
nacelle_mass_kg = inputs["nacelle_mass"][0]
nacelle_mass_kg = float(inputs["nacelle_mass"])
nacelle = input_components[input_components["Component"].str.startswith("Nacelle")].iloc[0].copy()
if inputs["nacelle_mass"] != use_default_component_data:
nacelle["Mass tonne"] = nacelle_mass_kg / kg_per_tonne
nacelle["Component"] = "Nacelle"
nacelle["Lift height m"] = hub_height_meters
nacelle["Lift height m"] = nacelle["Lever arm m"] = hub_height_meters
output_components_list.append(nacelle)

# Make the hub
hub_mass_kg = inputs["hub_mass"][0]
hub_mass_kg = float(inputs["hub_mass"])
hub = input_components[input_components["Component"].str.startswith("Hub")].iloc[0].copy()
hub["Lift height m"] = hub_height_meters
hub["Lift height m"] = hub["Lever arm m"] = hub_height_meters
if hub_mass_kg != use_default_component_data:
hub["Mass tonne"] = hub_mass_kg / kg_per_tonne
output_components_list.append(hub)
Expand All @@ -671,33 +674,35 @@ def modify_component_lists(self, inputs, discrete_inputs):
blade = input_components[input_components["Component"].str.startswith("Blade")].iloc[0].copy()

# There is always a hub height, so use that as the lift height
blade["Lift height m"] = hub_height_meters
blade["Lift height m"] = blade["Lever arm m"] = hub_height_meters

if inputs["blade_drag_coefficient"][0] != use_default_component_data:
blade["Coeff drag"] = inputs["blade_drag_coefficient"][0]
if float(inputs["blade_drag_coefficient"]) != use_default_component_data:
blade["Coeff drag"] = float(inputs["blade_drag_coefficient"])

if inputs["blade_lever_arm"][0] != use_default_component_data:
blade["Lever arm m"] = inputs["blade_lever_arm"][0]
if float(inputs["blade_lever_arm"]) != use_default_component_data:
blade["Lever arm m"] = float(inputs["blade_lever_arm"])

if inputs["blade_install_cycle_time"][0] != use_default_component_data:
blade["Cycle time installation hrs"] = inputs["blade_install_cycle_time"][0]
if float(inputs["blade_install_cycle_time"]) != use_default_component_data:
blade["Cycle time installation hrs"] = float(inputs["blade_install_cycle_time"])

if inputs["blade_offload_hook_height"][0] != use_default_component_data:
if float(inputs["blade_offload_hook_height"]) != use_default_component_data:
blade["Offload hook height m"] = hub_height_meters

if inputs["blade_offload_cycle_time"][0] != use_default_component_data:
if float(inputs["blade_offload_cycle_time"]) != use_default_component_data:
blade["Offload cycle time hrs"] = inputs["blade_offload_cycle_time"]

if inputs["blade_drag_multiplier"][0] != use_default_component_data:
if float(inputs["blade_drag_multiplier"]) != use_default_component_data:
blade["Multiplier drag rotor"] = inputs["blade_drag_multiplier"]

if inputs["blade_mass"][0] != use_default_component_data:
blade["Mass tonne"] = inputs["blade_mass"][0] / kg_per_tonne
if float(inputs["blade_mass"]) != use_default_component_data:
blade["Mass tonne"] = float(inputs["blade_mass"]) / kg_per_tonne

if float(inputs["blade_surface_area"]) != use_default_component_data:
blade["Surface area sq m"] = float(inputs["blade_surface_area"])

# Assume that number_of_blades always has a reasonable value. It's
# default count when the discrete input is declared of 3 is always
# reasonable unless overridden by another input.

number_of_blades = discrete_inputs["number_of_blades"]
for i in range(number_of_blades):
component = f"Blade {i + 1}"
Expand All @@ -706,8 +711,8 @@ def modify_component_lists(self, inputs, discrete_inputs):
output_components_list.append(blade_i)

# Make tower sections
tower_mass_tonnes = inputs["tower_mass"][0] / kg_per_tonne
tower_height_m = hub_height_meters - inputs["foundation_height"][0]
tower_mass_tonnes = float(inputs["tower_mass"]) / kg_per_tonne
tower_height_m = hub_height_meters - float(inputs["foundation_height"])
default_tower_section = input_components[input_components["Component"].str.startswith("Tower")].iloc[0]
tower_sections = self.make_tower_sections(tower_mass_tonnes, tower_height_m, default_tower_section)
output_components_list.extend(tower_sections)
Expand Down Expand Up @@ -767,7 +772,7 @@ def make_tower_sections(tower_mass_tonnes, tower_height_m, default_tower_section

tower_section_mass = tower_mass_tonnes / number_of_sections

tower_section_surface_area_m2 = np.pi * tower_section_height_m * (tower_radius ** 2)
tower_section_surface_area_m2 = np.pi * tower_section_height_m * (tower_radius**2)

sections = []
for i in range(number_of_sections):
Expand Down
2 changes: 1 addition & 1 deletion landbosse/model/CollectionCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def calculate_weather_delay(self, weather_delay_input_data, weather_delay_output

# if greater than 4 hour delay, then shut down for full day (10 hours)
wind_delay[(wind_delay > 4)] = 10
weather_delay_output_data['wind_delay_time'] = float(wind_delay.sum())
weather_delay_output_data['wind_delay_time'] = float(wind_delay.sum().iloc[0])

return weather_delay_output_data

Expand Down
8 changes: 5 additions & 3 deletions landbosse/model/ErectionCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ def calculate_erection_operation_time(self):
self.output_dict['component_name_topvbase'] = project_data['components'][['Component', 'Operation']]

# create groups for operations
top_v_base = project_data['components'].groupby(['Operation'])
top_v_base = project_data['components'].groupby('Operation')

# group crane data by boom system and crane name to get distinct cranes
crane_grouped = project_data['crane_specs'].groupby(
Expand Down Expand Up @@ -923,7 +923,7 @@ def aggregate_erection_costs(self):
# Intent is to keep the most expensive labor row.

# group crew costs by crew type and operation
crew_cost_grouped = crew_cost.groupby(['Crew type ID', 'Operation']).sum().reset_index()
crew_cost_grouped = crew_cost.groupby(['Crew type ID', 'Operation']).sum(numeric_only=True).reset_index()

# merge crane data with grouped crew costs

Expand Down Expand Up @@ -1120,7 +1120,7 @@ def calculate_management_crews_cost(self, erection_cost):

# Aggregate and sum
management_crew_cost_grouped = \
management_crews.groupby(['Crew type ID', 'Operation', 'Crew name']).sum().reset_index()
management_crews.groupby(['Crew type ID', 'Operation', 'Crew name']).sum(numeric_only=True).reset_index()

# Total management cost
total_management_cost = management_crews['crew_level_total_costs'].sum()
Expand All @@ -1139,11 +1139,13 @@ def calculate_costs(self):
for erection.
"""
[crane_specs, operation_time] = self.calculate_erection_operation_time()
crane_specs = crane_specs.infer_objects() # cast cols of Booleans to bool dtype

self.output_dict['crane_specs'] = crane_specs
self.output_dict['operation_time'] = operation_time

[offload_specs, offload_time] = self.calculate_offload_operation_time()
offload_specs = offload_specs.infer_objects() # cast cols of Booleans to bool dtype

self.output_dict['offload_specs'] = offload_specs
self.output_dict['offload_time'] = offload_time
Expand Down
2 changes: 1 addition & 1 deletion landbosse/model/FoundationCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ def calculate_weather_delay(self, weather_delay_input_data, weather_delay_output

# if greater than 4 hour delay, then shut down for full day (10 hours)
wind_delay[(wind_delay > 4)] = 10
weather_delay_output_data['wind_delay_time'] = float(wind_delay.sum())
weather_delay_output_data['wind_delay_time'] = float(wind_delay.sum().iloc[0])

return weather_delay_output_data

Expand Down
11 changes: 8 additions & 3 deletions landbosse/model/Manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from .ManagementCost import ManagementCost
from .FoundationCost import FoundationCost
from .SubstationCost import SubstationCost
from .TransportCost import TransportCost
from .GridConnectionCost import GridConnectionCost
from .SitePreparationCost import SitePreparationCost
from .CollectionCost import Cable, Array, ArraySystem
from .CollectionCost import ArraySystem
from .ErectionCost import ErectionCost
from .DevelopmentCost import DevelopmentCost

Expand Down Expand Up @@ -58,6 +59,9 @@ def execute_landbosse(self, project_name):
substation_cost = SubstationCost(input_dict=self.input_dict, output_dict=self.output_dict, project_name=project_name)
substation_cost.run_module()

transport_cost = TransportCost(input_dict=self.input_dict, output_dict=self.output_dict, project_name=project_name)
transport_cost.run_module()

transdist_cost = GridConnectionCost(input_dict=self.input_dict, output_dict=self.output_dict, project_name=project_name)
transdist_cost.run_module()

Expand Down Expand Up @@ -92,14 +96,15 @@ def execute_landbosse(self, project_name):
road_cost.loc[index, 'Cost USD'] = other['Cost USD'] - amount_shorter_than_input_construction_time * 55500
self.output_dict['total_road_cost'] = road_cost

total_costs = pd.concat( (self.output_dict['total_collection_cost'],
total_costs = pd.concat((self.output_dict['total_collection_cost'],
self.output_dict['total_road_cost'],
self.output_dict['total_transdist_cost'],
self.output_dict['total_substation_cost'],
self.output_dict['total_transport_cost'],
self.output_dict['total_foundation_cost'],
self.output_dict['total_erection_cost'],
self.output_dict['total_development_cost'],
) )
), sort=True)
self.input_dict['project_value_usd'] = float(total_costs['Cost USD'].sum())
self.input_dict['foundation_cost_usd'] = self.output_dict['total_foundation_cost']['Cost USD'].sum()

Expand Down
9 changes: 4 additions & 5 deletions landbosse/model/SitePreparationCost.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import pandas as pd
import numpy as np
import math
from .WeatherDelay import WeatherDelay as WD
import traceback
from .CostModule import CostModule

import pandas as pd

class SitePreparationCost(CostModule):
"""
Expand Down Expand Up @@ -420,7 +419,7 @@ def calculate_weather_delay(self, weather_delay_input_data, weather_delay_output

# if greater than 4 hour delay, then shut down for full day (10 hours)
wind_delay[(wind_delay > 4)] = 10
weather_delay_output_data['wind_delay_time'] = float(wind_delay.sum())
weather_delay_output_data['wind_delay_time'] = float(wind_delay.sum().iloc[0])

return weather_delay_output_data

Expand Down Expand Up @@ -541,7 +540,7 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict)
if calculate_cost_input_dict['road_distributed_wind'] and \
calculate_cost_input_dict['turbine_rating_MW'] >= 0.1:

labor_for_new_roads_cost_usd = (labor_data['Cost USD'].sum()) + \
labor_for_new_roads_cost_usd = labor_data['Cost USD'].sum() + \
calculate_cost_output_dict['managament_crew_cost_before_wind_delay']

labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd)
Expand All @@ -551,7 +550,7 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict)
elif calculate_cost_input_dict['road_distributed_wind'] and \
calculate_cost_input_dict['turbine_rating_MW'] < 0.1: # small DW

labor_for_new_roads_cost_usd = (labor_data['Cost USD'].sum())
labor_for_new_roads_cost_usd = labor_data['Cost USD'].sum()
labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd)

labor_costs = pd.DataFrame([['Labor', float(labor_for_new_and_old_roads_cost_usd), 'Small DW Roads']],
Expand Down
Loading

0 comments on commit 6575457

Please sign in to comment.