Skip to content

Commit

Permalink
Merge pull request #127 from WISDEM/release-2.2.7
Browse files Browse the repository at this point in the history
Release 2.2.7
  • Loading branch information
akey7 authored Mar 10, 2020
2 parents ad95090 + 32133f3 commit be295f6
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 62 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,20 @@

## 2.2.5

+ Fixed a solver problem that would hang during foundation calculations.
+ Fixed a solver problem that would hang during foundation calculations. This was a bug encountered in the upgrade to SymPy 1.5.1

+ Added post processing scripts to load data into PostgreSQL

## 2.2.6

+ Fix setup.py to automatically find the `landbosse` package.

## 2.2.7

+ Chnaged construction duration functionality.

+ Weather window can now be extended to an arbitrarily long length by duplicating the underlying wind data.

+ All modules now report the actual construction time they require to perform the scope of work they model.

+ ManagementCost now keeps the management crew onsite for only the time necessary to complete all scope of work.
58 changes: 56 additions & 2 deletions landbosse/excelio/WeatherWindowCSVReader.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from math import ceil

import pandas as pd


SEASON_WINTER = 'winter'
SEASON_SPRING = 'spring'
SEASON_SUMMER = 'summer'
SEASON_FALL = 'fall'


month_numbers_to_seasons = {
1: SEASON_WINTER,
2: SEASON_WINTER,
Expand Down Expand Up @@ -101,12 +105,14 @@ def read_weather_window(weather_data, local_timezone='America/Denver'):
# Convert UTC to local time
weather_data['Date'] = weather_data['Date UTC'].dt.tz_convert(local_timezone)

# Extract year, month, day, hour from the local date
weather_data['Year'] = weather_data['Date'].dt.year
# Extract month, day, hour from the local date
weather_data['Month'] = weather_data['Date'].dt.month
weather_data['Day'] = weather_data['Date'].dt.day
weather_data['Hour'] = weather_data['Date'].dt.hour

# The original date columns are now redundant. Drop them.
weather_data.drop(columns=['Date', 'Date UTC'])

# create time window for normal (8am to 6pm) versus long (24 hour) time window for operation
weather_data['Time window'] = weather_data['Hour'].between(8, 18, inclusive=True)
boolean_dictionary = {True: 'normal', False: 'long'}
Expand All @@ -122,3 +128,51 @@ def read_weather_window(weather_data, local_timezone='America/Denver'):

# return the result
return weather_data

def extend_weather_window(weather_window_df, months_of_weather_data_needed):
"""
This function extends a weather window by duplicating the rows to create
a weather window that spans the needed number of months.
Suppose that a weather window is 8760 hours long, but that 72 months
(52560 hours) are needed. This method will create a new dataframe
that has the original 8760 hours duplicated 6 times.
However, if the number of needed months is less than or equal to the
duration of the weather window, a reference to the original weather
window is returned. Also, the number of rows returned will always be
a multiple of the number of rows in the original data frame.
If rows are added to the weather window, they are added to a new dataframe.
The weather window is not modified in place.
Parameters
----------
weather_window_df : pd.DataFrame
The original weather window.
months_of_weather_data_needed : int
The number of months of weather data needed. Each month is approximated
to have 730 hours.
Returns
-------
pd.DataFrame
If the weather window accommodates the necessary number of months, then
a reference to the original dataframe is returned. Otherwise, a reference
to a new dataframe that has a row count that is a multiple of the number
of rows in the original weather window.
"""
hours_per_month = 730
hours_of_weather_data_needed = hours_per_month * months_of_weather_data_needed
hours_of_weather_data_available = len(weather_window_df)

if hours_of_weather_data_needed <= hours_of_weather_data_available:
return weather_window_df

number_of_windows_needed = int(ceil(hours_of_weather_data_needed / hours_of_weather_data_available))
weather_window_as_list = weather_window_df.to_dict(orient='records')
weather_window_repeated_as_list = weather_window_as_list * number_of_windows_needed
result = pd.DataFrame(weather_window_repeated_as_list)

return result
4 changes: 2 additions & 2 deletions landbosse/excelio/XlsxFileOperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_input_output_paths_from_argv_or_env(self):
There is a fourth option this method looks for:
--scaling-study or -s
--scaling or -s
This enables auto scaling of input parameters as found in XlsxReader's
apply_cost_and_scaling_modifications_to_project_parameters() method.
Expand Down Expand Up @@ -81,7 +81,7 @@ def get_input_output_paths_from_argv_or_env(self):
validation_enabled = '--validate' in sys.argv or '-v' in sys.argv

# This is for scaling study operation
enable_scaling_study = '--scaling-study' in sys.argv or '-s' in sys.argv
enable_scaling_study = '--scaling' in sys.argv or '-s' in sys.argv

# If validation and scaling study are simultaneously enabled, raise
# an error
Expand Down
21 changes: 13 additions & 8 deletions landbosse/excelio/XlsxReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from math import ceil

from .XlsxOperationException import XlsxOperationException
from .WeatherWindowCSVReader import read_weather_window
from .WeatherWindowCSVReader import read_weather_window, extend_weather_window
from ..model import DefaultMasterInputDict
from .GridSearchTree import GridSearchTree

Expand Down Expand Up @@ -392,12 +392,6 @@ def create_master_input_dictionary(self, project_data_dataframes, project_parame
incomplete_input_dict['site_facility_building_area_df'] = project_data_dataframes['site_facility_building_area']
incomplete_input_dict['material_price'] = project_data_dataframes['material_price']

# The weather window is stored on a sheet of the project_data, but
# needs preprocessing after it is read. The preprocessing changes it
# from wind toolkit format to a dataframe.
weather_window_input_df = project_data_dataframes['weather_window']
incomplete_input_dict['weather_window'] = read_weather_window(weather_window_input_df)

# Read development tab, if it exists (it is optional since development costs can
# be placed in the project list):
if 'development' in project_data_dataframes:
Expand Down Expand Up @@ -500,6 +494,15 @@ def create_master_input_dictionary(self, project_data_dataframes, project_parame
incomplete_input_dict['markup_overhead'] = project_parameters['Markup overhead']
incomplete_input_dict['markup_profit_margin'] = project_parameters['Markup profit margin']

# The weather window is stored on a sheet of the project_data, but
# needs preprocessing after it is read. The preprocessing changes it
# from wind toolkit format to a dataframe.
number_of_months_for_construction = int(project_parameters['Total project construction time (months)'])
weather_window_input = project_data_dataframes['weather_window']
weather_window_intermediate = read_weather_window(weather_window_input)
extended_weather_window = extend_weather_window(weather_window_intermediate, number_of_months_for_construction)
incomplete_input_dict['weather_window'] = extended_weather_window

# Now fill any missing values with sensible defaults.
defaults = DefaultMasterInputDict()
master_input_dict = defaults.populate_input_dict(incomplete_input_dict=incomplete_input_dict)
Expand Down Expand Up @@ -633,7 +636,9 @@ def create_serial_number(self, project_id, index, max_index):
total_digit_count = 1
index_digit_count = len(str(index))

if 0 < max_index < 1e1 - 1:
if max_index < 10:
total_digit_count = 1
elif 0 < max_index < 1e1 - 1:
total_digit_count = 1
elif 1e1 <= max_index < 1e2 - 1:
total_digit_count = 2
Expand Down
13 changes: 9 additions & 4 deletions landbosse/model/CollectionCost.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,14 +653,14 @@ def estimate_construction_time(self, construction_time_input_data, construction_
boolean_dictionary = {True: collection_construction_time * 30, False: np.NAN}
operation_data['time_construct_bool'] = operation_data['time_construct_bool'].map(boolean_dictionary)
operation_data['Time construct days'] = operation_data[['time_construct_bool', 'Number of days taken by single crew']].min(axis=1)
num_days = operation_data['Time construct days']
num_days = operation_data['Time construct days'].max()

# pull out management data
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.iloc[0])
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.iloc[0])
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()
Expand Down Expand Up @@ -915,8 +915,13 @@ def run_module(self):
self.weather_input_dict[
'wind_height_of_interest_m'] = self.input_dict['critical_height_non_erection_wind_delays_m']

# compute and specify weather delay mission time for roads
# Compute the duration of the construction for electrical collection
duration_construction = operation_data['Time construct days'].max(skipna=True)
days_per_month = 30
duration_construction_months = duration_construction / days_per_month
self.output_dict['collection_construction_months'] = duration_construction_months

# compute and specify weather delay mission time for roads
operational_hrs_per_day = self.input_dict['hour_day'][self.input_dict['time_construct']]
mission_time_hrs = duration_construction * operational_hrs_per_day
self.weather_input_dict['mission_time_hours'] = int(mission_time_hrs)
Expand Down
Loading

0 comments on commit be295f6

Please sign in to comment.