From 28a6467e6b83ab9b2feb53833e0a98083bcfc89a Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Mon, 27 Jul 2020 10:50:18 -0600 Subject: [PATCH 01/12] Issue #138 There was a duplicate concrete record being output for every project in the detailed data. This fixes that bug. --- landbosse/model/FoundationCost.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/landbosse/model/FoundationCost.py b/landbosse/model/FoundationCost.py index bf92aec6..3e36d34c 100644 --- a/landbosse/model/FoundationCost.py +++ b/landbosse/model/FoundationCost.py @@ -778,12 +778,6 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): 'variable_df_key_col_name': 'Radius', 'value': float(self.output_dict['Radius_m']) }) - result.append({ - 'unit': 'm^3', - 'type': 'variable', - 'variable_df_key_col_name': 'foundation_volume_concrete_m3_per_turbine', - 'value': float(self.output_dict['foundation_volume_concrete_m3_per_turbine']) - }) result.append({ 'unit': 'short_ton', 'type': 'variable', From 70a4dde093e363a8557545f6362d5be4b9e2b141 Mon Sep 17 00:00:00 2001 From: PB_Energy Date: Wed, 29 Jul 2020 23:27:47 -0600 Subject: [PATCH 02/12] landbosse/model/ErectionCost.py --- landbosse/model/ErectionCost.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/landbosse/model/ErectionCost.py b/landbosse/model/ErectionCost.py index becf1220..dc4654d6 100644 --- a/landbosse/model/ErectionCost.py +++ b/landbosse/model/ErectionCost.py @@ -1,7 +1,7 @@ import pandas as pd import numpy as np -from shapely.geometry import Point -from shapely.geometry.polygon import Polygon +from sympy import Point +from sympy import Polygon from math import ceil from .CostModule import CostModule @@ -643,7 +643,8 @@ def calculate_crane_lift_polygons(self, crane_grouped): setup_time = max(crane['Setup time hr']) breakdown_time = max(crane['Breakdown time hr']) crew_type = crane.loc[0, 'Crew type ID'] # For every crane/boom combo the crew is the same, so we can just take first crew. - polygon = Polygon([(0, 0), (0, max(y)), (min(x), max(y)), (max(x), min(y)), (max(x), 0)]) + p1, p2, p3, p4, p5 = map(Point, [(0, 0), (0, max(y)), (min(x), max(y)), (max(x), min(y)), (max(x), 0)]) + polygon = Polygon(p1, p2, p3, p4, p5) df = pd.DataFrame([[equipment_name, equipment_id, crane_name, @@ -726,7 +727,7 @@ def calculate_component_lift_max_wind_speed(self, *, component_group, crane_poly point = Point(component_only['Mass tonne'] / 2, (component_only['Section height m'] + component_only['Offload hook height m'])) else: point = Point(component_only['Mass tonne'], (component_only['Lift height m'] + component_only['Offload hook height m'])) - crane['Lift boolean {component}'.format(component=component)] = polygon.contains(point) + crane['Lift boolean {component}'.format(component=component)] = polygon.encloses_point(point) # Transform the "Lift boolean" indexes in the series to a list of booleans # that signify if the crane can lift a component. From 720b633696dfc842e32a7d617d0962f1e33f3fe4 Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Thu, 30 Jul 2020 09:56:00 -0600 Subject: [PATCH 03/12] Issue #151 Retaining some useful logging statements. --- landbosse/model/ErectionCost.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/landbosse/model/ErectionCost.py b/landbosse/model/ErectionCost.py index dc4654d6..54cb28cf 100644 --- a/landbosse/model/ErectionCost.py +++ b/landbosse/model/ErectionCost.py @@ -644,7 +644,9 @@ def calculate_crane_lift_polygons(self, crane_grouped): breakdown_time = max(crane['Breakdown time hr']) crew_type = crane.loc[0, 'Crew type ID'] # For every crane/boom combo the crew is the same, so we can just take first crew. p1, p2, p3, p4, p5 = map(Point, [(0, 0), (0, max(y)), (min(x), max(y)), (max(x), min(y)), (max(x), 0)]) + # print(f"Erection points {p1} {p2} {p3} {p4} {p5}") polygon = Polygon(p1, p2, p3, p4, p5) + # print(f"Erection Polygon points {p1} {p2} {p3} {p4} {p5}") df = pd.DataFrame([[equipment_name, equipment_id, crane_name, From 2655af64ec8762124af5a55a1efeb0242de3b422 Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Thu, 6 Aug 2020 09:53:39 -0600 Subject: [PATCH 04/12] Update setup.py Force a downgrade of sympy and remove the dependence on shapely. --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 33c2185f..01051eab 100644 --- a/setup.py +++ b/setup.py @@ -25,9 +25,8 @@ install_requires=[ 'pandas==0.25.2', 'numpy', - 'sympy', + 'sympy==1.5.1', 'scipy', - 'shapely', 'xlsxwriter', 'xlrd', 'psycopg2-binary', From 6c709a1f69f6d36d7505989517d99ba5f7dd6ba9 Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Thu, 6 Aug 2020 10:09:46 -0600 Subject: [PATCH 05/12] Update XlsxReader.py The operator here should be `==` not `is`. This fixesthat problem. --- landbosse/excelio/XlsxReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landbosse/excelio/XlsxReader.py b/landbosse/excelio/XlsxReader.py index d5cfc9e8..d4a9da78 100644 --- a/landbosse/excelio/XlsxReader.py +++ b/landbosse/excelio/XlsxReader.py @@ -592,7 +592,7 @@ def apply_cost_and_scaling_modifications_to_project_parameters(self, project_par number_of_access_roads = 0.0 if project_size_MW <= 20 else ceil(0.0052 * project_size_MW + 0.7917) number_of_highway_permits = ceil(0.2 * project_parameters['Number of turbines']) - if flag_use_user_homerun is 1: + if flag_use_user_homerun == 1: project_parameters['Combined Homerun Trench Length to Substation (km)'] = 0.1776 * project_size_MW - 2.551 # 10 deliveries per week for 1.5 MW machines From 837043a03b78b9ab5ddc49eea141a3b48b139afd Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Thu, 6 Aug 2020 11:39:20 -0600 Subject: [PATCH 06/12] substituting scipy for sympy solve and ray casting code for Point and Polygon intersects --- landbosse/model/ErectionCost.py | 43 ++++++++++++++++++++----- landbosse/model/FoundationCost.py | 52 ++++++++++++------------------- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/landbosse/model/ErectionCost.py b/landbosse/model/ErectionCost.py index 54cb28cf..53257d6c 100644 --- a/landbosse/model/ErectionCost.py +++ b/landbosse/model/ErectionCost.py @@ -1,7 +1,5 @@ import pandas as pd import numpy as np -from sympy import Point -from sympy import Polygon from math import ceil from .CostModule import CostModule @@ -15,6 +13,40 @@ m_per_ft = 0.3048 +class Point(object): + def __init__(self, x, y): + if type(x) == type(pd.Series()): + self.x = float(x.values[0]) + self.y = float(y.values[0]) + elif type(x) == type(np.array([])): + self.x = float(x[0]) + self.y = float(y[0]) + elif type(x) == type(int(0)): + self.x = float(x) + self.y = float(y) + elif type(x) == type(float(0.0)): + self.x = x + self.y = y + else: + raise ValueError(type(x)) + +def ccw(A,B,C): + return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x) + +# Return true if line segments AB and CD intersect +def intersect(A,B,C,D): + return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D) + +def point_in_polygon(pt, poly): + result = False + maxx = float(np.r_[pt.x, np.array([m.x for m in poly])].max()) + for i in range(len(poly)-1): + if intersect(poly[i], poly[i+1], pt, Point(1.1*maxx, pt.y)): + result = not result + if intersect(poly[-1], poly[0], pt, Point(1.1*maxx, pt.y)): + result = not result + return result + class ErectionCost(CostModule): """ ErectionCost.py @@ -643,10 +675,7 @@ def calculate_crane_lift_polygons(self, crane_grouped): setup_time = max(crane['Setup time hr']) breakdown_time = max(crane['Breakdown time hr']) crew_type = crane.loc[0, 'Crew type ID'] # For every crane/boom combo the crew is the same, so we can just take first crew. - p1, p2, p3, p4, p5 = map(Point, [(0, 0), (0, max(y)), (min(x), max(y)), (max(x), min(y)), (max(x), 0)]) - # print(f"Erection points {p1} {p2} {p3} {p4} {p5}") - polygon = Polygon(p1, p2, p3, p4, p5) - # print(f"Erection Polygon points {p1} {p2} {p3} {p4} {p5}") + polygon = [Point(0, 0), Point(0, max(y)), Point(min(x), max(y)), Point(max(x), min(y)), Point(max(x), 0)] df = pd.DataFrame([[equipment_name, equipment_id, crane_name, @@ -729,7 +758,7 @@ def calculate_component_lift_max_wind_speed(self, *, component_group, crane_poly point = Point(component_only['Mass tonne'] / 2, (component_only['Section height m'] + component_only['Offload hook height m'])) else: point = Point(component_only['Mass tonne'], (component_only['Lift height m'] + component_only['Offload hook height m'])) - crane['Lift boolean {component}'.format(component=component)] = polygon.encloses_point(point) + crane['Lift boolean {component}'.format(component=component)] = point_in_polygon(point, polygon) # Transform the "Lift boolean" indexes in the series to a list of booleans # that signify if the crane can lift a component. diff --git a/landbosse/model/FoundationCost.py b/landbosse/model/FoundationCost.py index 3e36d34c..8d8bf034 100644 --- a/landbosse/model/FoundationCost.py +++ b/landbosse/model/FoundationCost.py @@ -4,8 +4,7 @@ import pandas as pd import numpy as np import math -from sympy.solvers import solve -from sympy import Symbol +from scipy.optimize import root_scalar from .WeatherDelay import WeatherDelay as WD from .CostModule import CostModule @@ -343,41 +342,30 @@ def calculate_foundation_load(self, foundation_load_input_data, foundation_load_ if (r_test_gapping / 3) < e: r_gapping = 0 else: - r_g = Symbol('r_g', real=True, positive=True) - foundation_vol = np.pi * r_g ** 2 * foundation_load_input_data['depth'] - v_1 = (foundation_vol * (vol_fraction_fill * unit_weight_fill + vol_fraction_concrete * unit_weight_concrete) + f_dead) - e = m_tot / v_1 - r_gapping = solve(e * 3 - r_g, r_g) - if len(r_gapping) > 0: - r_gapping = max(r_gapping) - else: - r_gapping = 0 + def r_g(x): + foundation_vol = np.pi * x** 2 * foundation_load_input_data['depth'] + v_1 = (foundation_vol * (vol_fraction_fill * unit_weight_fill + vol_fraction_concrete * unit_weight_concrete) + f_dead) + e = m_tot / v_1 + return (e * 3 - x) + result = root_scalar(r_g, method='brentq', bracket=[5.0, 50], xtol=1e-3, maxiter=50) + r_gapping = result.root + if not result.converged: + raise ValueError(f'Warning {self.project_name} calculate_foundation_load r_gapping solve failed, {result.flag}') r_test_bearing = max(r_test_gapping, r_gapping) # calculate foundation radius based on bearing pressure + def r_b(x): + foundation_vol = np.pi * r_test_bearing ** 2 * foundation_load_input_data['depth'] + v_1 = (foundation_vol * (vol_fraction_fill * unit_weight_fill + vol_fraction_concrete * unit_weight_concrete) + f_dead) + e = m_tot / v_1 + a_eff = v_1 / bearing_pressure + return (2 * (x ** 2 - e * (x ** 2 - e ** 2) ** 0.5) - a_eff) + result = root_scalar(r_b, method='brentq', bracket=[5.0, 50], xtol=1e-3, maxiter=50) + r_bearing = result.root - # Restrict r_b to only real numbers. Positive solutions for r_b are - # selected below - r_b = Symbol('r_b', real=True) - - foundation_vol = np.pi * r_test_bearing ** 2 * foundation_load_input_data['depth'] - v_1 = (foundation_vol * (vol_fraction_fill * unit_weight_fill + vol_fraction_concrete * unit_weight_concrete) + f_dead) - e = m_tot / v_1 - a_eff = v_1 / bearing_pressure - r_bearing = solve(2 * (r_b ** 2 - e * (r_b ** 2 - e ** 2) ** 0.5) - a_eff, r_b) - - # Select only positive solutions to r_b. This is selected by max(). If there are - # not positive solutions to r_b, that means something is wrong with the foundation - # parameters. In that case, generate a warning below. - - if len(r_bearing) > 0: - r_bearing = max(r_bearing) - else: - r_bearing = 0 - - if r_bearing < 0: - raise ValueError(f'Warning {self.project_name} calculate_foundation_load r_bearing is negative, r_bearing={r_bearing}') + if not result.converged: + raise ValueError(f'Warning {self.project_name} calculate_foundation_load r_bearing solve failed, {result.flag}') # pick the largest foundation radius based on all 4 foundation design criteria: moment, gapping, bearing, slipping r_choosen = max(r_bearing, r_overturn, r_slipping, r_gapping) From da7b4c58d548da932a265280327ad958b8a4ad7e Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Thu, 6 Aug 2020 11:39:28 -0600 Subject: [PATCH 07/12] removing dependencies --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 01051eab..5677de0b 100644 --- a/setup.py +++ b/setup.py @@ -23,9 +23,8 @@ test_suite='nose.collector', tests_require=['nose'], install_requires=[ - 'pandas==0.25.2', + 'pandas', 'numpy', - 'sympy==1.5.1', 'scipy', 'xlsxwriter', 'xlrd', From 471eedfbd8f5202416683dfbd2a2151ec33eb64a Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Thu, 6 Aug 2020 11:50:36 -0600 Subject: [PATCH 08/12] more robust and accurate root finding --- landbosse/model/FoundationCost.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/landbosse/model/FoundationCost.py b/landbosse/model/FoundationCost.py index 8d8bf034..0fc6481d 100644 --- a/landbosse/model/FoundationCost.py +++ b/landbosse/model/FoundationCost.py @@ -347,7 +347,7 @@ def r_g(x): v_1 = (foundation_vol * (vol_fraction_fill * unit_weight_fill + vol_fraction_concrete * unit_weight_concrete) + f_dead) e = m_tot / v_1 return (e * 3 - x) - result = root_scalar(r_g, method='brentq', bracket=[5.0, 50], xtol=1e-3, maxiter=50) + result = root_scalar(r_g, method='brentq', bracket=[0.9*r_overturn, 50], xtol=1e-4, maxiter=50) r_gapping = result.root if not result.converged: raise ValueError(f'Warning {self.project_name} calculate_foundation_load r_gapping solve failed, {result.flag}') @@ -361,7 +361,7 @@ def r_b(x): e = m_tot / v_1 a_eff = v_1 / bearing_pressure return (2 * (x ** 2 - e * (x ** 2 - e ** 2) ** 0.5) - a_eff) - result = root_scalar(r_b, method='brentq', bracket=[5.0, 50], xtol=1e-3, maxiter=50) + result = root_scalar(r_b, method='brentq', bracket=[0.9*r_overturn, 50], xtol=1e-4, maxiter=50) r_bearing = result.root if not result.converged: From 54f8dbee7e6b55dd58857af588198a8fe419bbab Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Thu, 6 Aug 2020 11:54:24 -0600 Subject: [PATCH 09/12] updating dependencies for travis --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c116b5ad..812c868e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,9 @@ python: # command to install dependencies install: - - pip install pandas==0.25.2 + - pip install pandas - pip install numpy - - pip install sympy - - pip install shapely + - pip install scipy - pip install xlsxwriter - pip install xlrd - pip install psycopg2-binary From 7c28171e004341066acd303c944d46f35bfe2165 Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Thu, 6 Aug 2020 12:05:49 -0600 Subject: [PATCH 10/12] Update macos_developer.md --- installation_instructions/macos_developer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installation_instructions/macos_developer.md b/installation_instructions/macos_developer.md index 722d6fd6..e4b8d218 100644 --- a/installation_instructions/macos_developer.md +++ b/installation_instructions/macos_developer.md @@ -131,8 +131,8 @@ The file named `projects_list.xlsx` must keep the same name. The names of the pr Suppose that you have created the directory structure on your desktop as mentioned above in step 2 "Create a folder structure". You can set the environment variables and execute LandBOSSE with a couple easy commands: ``` -cd ~/Desktop/landbosse -LANDBOSSE_INPUT_DIR=~/Desktop/landbosse/input LANDBOSSE_OUTPUT_DIR=~/Desktop/landbosse/output python LandBOSSE/main.py +cd ~/Desktop/landbosse/LandBOSSE +python main.py -i ~/Desktop/landbosse/input -o ~/Desktop/landbosse/output ``` The file will be produced with a filename like: From 13563b74f7f1151cd6d17b5c08a179f4a6b36c05 Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Thu, 6 Aug 2020 12:31:08 -0600 Subject: [PATCH 11/12] no_sympy_shapely Make xtol smaller so that it matches the validation data. --- landbosse/model/FoundationCost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landbosse/model/FoundationCost.py b/landbosse/model/FoundationCost.py index 0fc6481d..59ec8086 100644 --- a/landbosse/model/FoundationCost.py +++ b/landbosse/model/FoundationCost.py @@ -361,7 +361,7 @@ def r_b(x): e = m_tot / v_1 a_eff = v_1 / bearing_pressure return (2 * (x ** 2 - e * (x ** 2 - e ** 2) ** 0.5) - a_eff) - result = root_scalar(r_b, method='brentq', bracket=[0.9*r_overturn, 50], xtol=1e-4, maxiter=50) + result = root_scalar(r_b, method='brentq', bracket=[0.9*r_overturn, 50], xtol=1e-10, maxiter=50) r_bearing = result.root if not result.converged: From a601003cbd4fa3d8ecfbd773485e7efde95f2015 Mon Sep 17 00:00:00 2001 From: Alicia Key Date: Mon, 10 Aug 2020 14:44:45 -0600 Subject: [PATCH 12/12] Update extended_landbosse_output_to_csv_and_pgsql.py Default export into database to false. --- .../extended_landbosse_output_to_csv_and_pgsql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post_processing_scripts/extended_landbosse_output_to_csv_and_pgsql.py b/post_processing_scripts/extended_landbosse_output_to_csv_and_pgsql.py index 49e0f031..d30515cb 100644 --- a/post_processing_scripts/extended_landbosse_output_to_csv_and_pgsql.py +++ b/post_processing_scripts/extended_landbosse_output_to_csv_and_pgsql.py @@ -33,7 +33,7 @@ join_landbosse_output_costs.to_csv("extended_landbosse_costs.csv", index=False) join_landbosse_output_details.to_csv("extended_landbosse_details.csv", index=False) -load_into_database_enabled = True +load_into_database_enabled = False if load_into_database_enabled: print("Load into database...")