diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d12f1d..4820278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # LandBOSSE Changelog +## Unreleased + ++ Corrects an error in multiple names defined for `LandBOSSERunner`s data connection to project + Excel file, which is now called "data_tables", consistent with the implementation made in + the `WAVES` integration. ++ Removes the reliance on `component` data being provided outside the Excel project data to reduce + duplicated data definitions. ++ Corrects the `LandBOSSERunner.keys_rename` to ensure all required variables are included. ++ Makes the `weather` input to `LandBOSSERunner` optional, so that the Excel "weather_window" sheet + will still be used if provided. If `weather` is provided as an input, it will override the the + Excel-defined "weather_window" sheet. ++ `LandBOSSERunner.convert_excel_to_dict()` added for converting a project from an Excel-based + project list to a compatible dictionary. + ## 2.6.2 (February 26, 2026) + Docstrings are reformatted to display nicely on a Jupyter Book documentation site. diff --git a/README.md b/README.md index 010fa33..9ba1c55 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ LandBOSSE is primarily developed with the support of the U.S. Department of Ener - [Techno-Economic Modeling Workshop](https://nrel.github.io/WETOStack/workshops/user_workshops_2024.html#tea-and-cost-modeling) - [Systems Engineering Workshop](https://nrel.github.io/WETOStack/workshops/user_workshops_2024.html#systems-engineering) -## User Guides +## User Guide + +### Installation For any installation, users should use a virtual environment. We recommend Miniconda or Anaconda, but any supporting PyPI or source installations are possible. Here, we'll work with conda for @@ -37,13 +39,13 @@ can be any that you prefer as long as it's supported by LandBOSSE. conda create -n landbosse python=3.13 -y ``` -### PyPI +#### PyPI ```bash pip install NREL-landbosse ``` -### Source +#### Source 1. Navigate to your preferred installation location 2. Clone the repo (or fork and clone your fork, if preferred). @@ -61,9 +63,7 @@ pip install NREL-landbosse Optional: `pip install -e .` for editable installations if you plan to modify the code itself. -## User Guides - -### First time running teh model +### First time running the model At its most basic, the following setup is required, though the provided input data in `project_inpute_template` can be used to test out the model and view results before diving into configuring custom scenarios. @@ -75,8 +75,7 @@ can be used to test out the model and view results before diving into configurin 3. Each project in `project_list.xlsx` should have a corresponding Excel file in `project_data` similar to the examples in `LandBOSSE/project_input_template/project_data`. - -### Running the model +### Running the model Once the initial steps (above) are followed, we can run the model: @@ -85,18 +84,17 @@ Once the initial steps (above) are followed, we can run the model: 3. Run the model: `python main.py -i input-folder-path -o output-folder-path` (be sure to replace "input-folder-path" and "output-folder-path" with your respective input and output folders). -All together + All together, this is: -```bash -conda activate landbosse -cd /path/to/LandBOSSE -python main.py -i /path/to/inputs -o /path/to/outputs -conda deactivate -``` + ```bash + conda activate landbosse + cd /path/to/LandBOSSE + python main.py -i /path/to/inputs -o /path/to/outputs + conda deactivate + ``` 4. View your results in the output folder. - ### Integrating LandBOSSE into your code While LandBOSSE was originally designed as a CLI tool powered by Excel workbooks, an API also exists diff --git a/docs/_config.yml b/docs/_config.yml index fe2d496..0630f8c 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -30,6 +30,16 @@ repository: html: use_issues_button: true use_repository_button: true + home_page_in_navbar: true + +parse: + myst_url_schemes: [mailto, http, https] + myst_enable_extensions: + - dollarmath + - amsmath + - deflist + - linkify + - colon_fence sphinx: extra_extensions: diff --git a/docs/_toc.yml b/docs/_toc.yml index 2004a87..c2942f7 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -5,5 +5,5 @@ format: jb-book root: intro chapters: - file: api -- file: examples +- file: example - file: contributing \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index 3302eb1..1d8f2ed 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,3 +1,42 @@ -# Contributor's Guide +# Developer's Guide -Coming soon. \ No newline at end of file +## Installation + +1. Navigate to your preferred installation location +2. Fork LandBOSSE +3. Clone your fork + + ```bash + git clone https://github.com//LandBOSSE.git + ``` + +4. Enter the directory and locally install an editable version with the testing and documentation + building dependencies. + + ```bash + cd LandBOSSE + pip install -e .[test, docs] + ``` + +## Testing the Code + +Run LandBOSSE's testing suite using the following command. Please note the tests are not +particularly robust at this time, so passing tests after modifying the codeshould be viewed with +caution. + +```bash +pytest landbosse +``` + +## Building the documentation + +LandBOSSE uses Jupyter Book (v1) for its documentation site, which can be recreated locally with +the following command from the top-level directory. + +```bash +jupyter book build docs/ +``` + +Once complete, there will be a callout to "paste this line directly into your browser bar" with the +next line in the form of `file:///path/to/LandBOSSE/docs/_build/html/index.html`, which should be +pasted into the browser bar like a URL. You should now be able to peruse the documentation. diff --git a/docs/example.md b/docs/example.md new file mode 100644 index 0000000..e6c525a --- /dev/null +++ b/docs/example.md @@ -0,0 +1,679 @@ +# User Guide + +This example will work with the `project_input_template/project_list_simplified.xlsx`, which +includes the GE 1.5 MW validation example (`project_input_template/project_data/ge15_public.xlsx`). +Using these data, we will walk through the setup of the project-specific data, project listings, +and running the model. + +## Project Input Data + +LandBOSSE relies on the use of separate input and output folders. For any set of projects, these +can be defined as environment variables using the names `LANDBOSSE_INPUT_DIR` and +`LANDBOSSE_OUTPUT_DIR`, or provided when running the code (more details in [Running LandBOSSE](#running-landbosse)). + +An example of a project input folder is provided with the code: the folder `project_input_template/` +where listings of projects can be stored and the subfolder `project_data` where all project-specific +Excel files are stored. + +### Project listing and the `LandBOSSERunner` API + +As stated in the introduction, we'll be working with `project_list_simplified.xlsx` which contains +a single project (unlike `project_list.xlsx` which runs several projects simultaneously), the +public-facing GE 1.5 MW example (`ge15_public.xlsx`). + +The below table explains each of the expected parameters found when setting up either the +`project_list.xlsx` or the `LandBOSSERunner` input dictionary. For Excel setups, each row in +"Project List Column" represents a column name in the project list Excel file. Using the Excel setup, +we can run multiple projects, though this example relies models a single project. Similarly, using +the `LandBOSSERunner` configuration dictionary, each row in "LandBOSSE Runner Key" represents a +dictionary key. Notably, using the `LandBOSSERunner` API enables single project runs only. In both +cases the "Description" column describes what data are expected as an Excel sheet's cell value or +dictionary key's value. + +| Project List Column | LandBOSSE Runner Key | Description | +| -------------------------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| Project ID | id | Name of the project | +| Project data file | data_tables | Excel filename associated with the project | +| Total project construction time (months) | construction_months | Total construction duration (months) | +| Turbine rating MW | turbine_rating_MW | Capacity of turbine (MW) | +| Hub height m | hub_height_m | Turbine hub height (m) | +| Rotor diameter m | rotor_diameter_m | Turbine rotor diameter (m) | +| Turbine spacing (times rotor diameter) | turbine_spacing_rotor_diameter | Layout spacing between turbines in each row, in rotor diameters | +| Row spacing (times rotor diameter) | row_spacing_rotor_diameter | Layout spacing between rows, in rotor diameters | +| Number of turbines | num_turbines | Number of turbines | +| Turbine Capex (USD/kW) | turbine_capex | CapEx of turbine ($/kW) | +| Breakpoint between base and topping (percent) | base_topping_breakpoint | Height at which cranes change from 'Base' to 'Topping', represented as a fraction of hub height | +| Fuel cost USD per gal | fuel_cost_usd_per_gal | Cost of fuel per gallon ($/gal) | +| Rate of deliveries (turbines per week) | turbine_delivery_rate_per_week | Number of turbine deliveries (all components) per week | +| Wind shear exponent | wind_shear | Wind shear (assumed constant across all timestamps and turbines) | +| Foundation depth m | foundation_depth_m | Depth of concrete foundation (m) | +| Rated Thrust (N) | rated_thrust_N | Rated horizontal thrust of turbine (N) | +| Bearing Pressure (n/m2) | bearing_pressure_Pa | Bearing pressure capacity of soil underneath foundations (Pa) | +| 50-year Gust Velocity (m/s) | gust_velocity_50_year_m/s | 50 year wind gust velocity (m/s) | +| Line Frequency (Hz) | line_frequency | Electrical frequency of grid (Hz) | +| Combined homerun trench length to substation (km) | combined_homerun_trench_length_km | Combined homerun trench length to substation (km) (optional - can be null but cannot be omitted) | +| Flag for user-defined home run trench length (0 = no; 1 = yes) | user_trench_length_flag | Flag (0 = no; 1 = yes) to indicate if the user-defined trench length should be used | +| Non-Erection Wind Delay Critical Height (m) | non_erection_wind_delay_critical_height_m | When calculating wind delays for non-erection tasks, shear values to this height (m) | +| Non-Erection Wind Delay Critical Speed (m/s) | non_erection_wind_delay_critical_wind_speed_m/s | When calculating wind delays for non-erection tasks, wind speeds above this value will force work to stop (m/s) | +| Distance to interconnect (miles) | distance_to_interconnect_mi | Distance from substation to switchyard (miles) | +| Interconnect Voltage (kV) | interconnect_voltage_kV | Voltage of grid (kV) | +| New Switchyard (y/n) | new_switchyard | Should a new switchyard be built (true/false) | +| Road length adder (m) | road_length_adder_m | Distance from site entry point to the project (m) | +| Road Quality (0-1) | road_quality | Non-dimensional representation of the quality of roads. 0 is poor quality, 1 is good quality. A higher road quality reduces the total road cost | +| Percent of roads that will be constructed | percent_roads_to_be_constructed | What percentage of roads need to be constructed vs already exist | +| Road width (ft) | road_width_ft | Width of roads (ft) | +| Road thickness (in) | road_thickness_in | Thickness of roads (in) | +| Calculate road cost for distributed wind? (y/n) | calculate_road_cost_for_distributed_wind | Should road costs be included in the calculation for distributed wind projects (true/false) | +| Site prep area for Distributed wind (m2) | site_prep_area_for_distributed_wind_m2 | Site prep area for distributed wind projects (m^2) | +| Crane width (m) | crane_width_m | Width of crane (m) | +| Number of highway permits | num_highway_permits | Number of highway permits required | +| Number of access roads | num_access_roads | Number of site access roads required | +| Overtime multiplier | overtime_multiplier | Labor cost multiplier for overtime work | +| Allow same flag | allow_same_flag | flag to indicate whether choosing same base and topping crane is allowed (true/false) | +| Override total management cost for distributed (0 does not override) | override_total_management_cost_for_distributed | For distributed wind project, override the calculated management cost and use this value instead (0 means do not override) | +| Markup contingency | markup_contingency | Markup contingency | +| Markup warranty management | markup_warranty_management | Markup warranty management | +| Markup sales and use tax | markup_sales_and_use_tax | Markup sales and use tax | +| Markup overhead | markup_overhead | Markup overhead | +| Markup profit margin | markup_profit_margin | Markup profit margin | +| Utility Interconnection Fees (Small DW only) | utility_interconnection_fees_distributed_wind | Fee for connecting to the grid (distributed wind projects only) | +| Labor cost multiplier | labor_cost_multiplier | Multiplier to modify labor costs | +| Crane breakdown fraction | crane_breakdown_fraction | What fraction of cranes will breakdown. 0 means none, 1 means all. Breakdowns increase the total erection duration | +| No mapping available | enable_cost_and_scaling_modifications | Should cost and scaling modifications be applied (true/false) | + +### Project Excel Data + +Regardless of how the above data are provided, an Excel file will be required for individual project +configurations. These data will contain various cost, logistics, site, and engineering data. Each +of the following subsections will show up to the first 10 rows of the data in `ge15_public.xlsx` +to demonstrate the data and formats required for the input data. + +For each sheet below, except `weather_window`, additional columns can be provided after the listed, +required columns. These can be things such as notes and sources that are helpful for tracking +source data and any methodologies for transforming them to fit the LandBOSSE formats. In many +of the sheets in `ge15_public.xlsx`, there will be one or multiple notes and sources columns for +exactly this purpose. However, those data are not exemplified below. + +#### `components` + +The `components` sheet contains detailed information about any large scale, assembled on-site +component, such as tower sections, the nacelle, or blades. Each individual component should be +recorded in a single row (i.e. each blade gets 1 row even if they are exactly the same). + +Component +: Name of the component; can be specific or generic. For items where there are more than one of them + or they are are part of a whole like tower sections or blades, number them using 1-indexing, + e.g., "Tower 1", "Tower 2", "Tower 3". + +Mass tonne +: Mass in metric tonnes. + +Lift height m +: Height, in $m$, that a crane will have to lift the component in order to complete assembly. + +Surface area sq m +: Total surface area, in $m^2$, of a cross-section of the component. This will be used for + calculating thefoundational load. + +Coeff drag +: Coefficient of drag. This will be used to understand the effective mass while lifting the component. + +Coeff drag (installed) +: Coefficient of drag once the component is installed. This will be used for calculating the + foundational load. + +Section height m +: Total height ($m$) of a cross-section of the component. This will be used for calculating the + foundational load. + +Lever arm m +: Perpendicular distance, in $m$, from center of the foundation. + +Cycle time installation hrs +: Amount of time, in hours, required to prepare the component for assembly. + +Offload hook height m +: Height of the hook ($m$) above the top of the component to account for the total height of the crane's lift. + +Offload cycle time hrs +: Amount of time (in hours) required to detach the component from the crane. + +Multplier drag rotor +: Multiplier for accounting for the additional drag of the rotor for computing the foundation load. + This should only apply to the nacelle and blades + +Multiplier tower drag +: Multiplier for accounting for the additional drag of the tower for computing the foundation load. + +The following data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Component | Mass tonne | Lift height m | Surface area sq m | Coeff drag | Coeff drag (installed) | Section height m | Lever arm m | Cycle time installation hrs | Offload hook height m | Offload cycle time hrs | Multplier drag rotor | Multiplier tower drag | +|-------------------|--------------|-----------------|---------------------|--------------|--------------------------|--------------------|---------------|-------------------------------|-------------------------|--------------------------|------------------------|-------------------------| +| Nacelle GE 1.5SLE | 50 | 90 | 33 | 0.8 | 0.8 | 0 | 80 | 1.5 | 6 | 0.5 | 1 | 0 | +| Hub | 15.4 | 90 | 11.3 | 1.1 | 1.1 | 0 | 80 | 1 | 6 | 0.5 | 0 | 0 | +| Blade 1 | 5.2 | 90 | 33.44 | 0.1 | 1.4 | 0 | 80 | 1 | 6 | 0.5 | 0.666667 | 0 | +| Blade 2 | 5.2 | 90 | 33.44 | 0.1 | 1.4 | 0 | 80 | 1 | 6 | 0.5 | 0.666667 | 0 | +| Blade 3 | 5.2 | 90 | 33.44 | 0.1 | 1.4 | 0 | 80 | 1 | 6 | 0.5 | 0.666667 | 0 | +| Tower section 1 | 59.8 | 30 | 95.89 | 0.6 | 1.1 | 25 | 12 | 1 | 6 | 0.5 | 0 | 1 | +| Tower section 2 | 39.3 | 60 | 96.25 | 0.6 | 1.1 | 25 | 37 | 1 | 6 | 0.5 | 0 | 1 | +| Tower section 3 | 30.9 | 90 | 90 | 0.6 | 1.1 | 30 | 65 | 1 | 6 | 0.5 | 0 | 1 | + +#### `cable_specs` + +The `cable_specs` sheet should contain information about the array cables potentially used on site. +The specific types and quantities will be sized according to LandBOSSE's collection system. + +Array Cable +: Name or type of the array cable. + +Conductor Size (mm2) +: Cable cross section, in $mm^2$. + +Current Capacity (A) +: Cable current capacity at 1m burial depth, in $A$. + +Rated Voltage (V) +: Cable rated line-to-line voltage, in $V$. + +AC Resistance (Ohms/km) +: Cable resistance for the AC current per kilometer, in $\frac{\omega}{km}$ + +Inductance (mH/km) +: Cable inductance per kilometer, in $\frac{mH}{km}$ + +Capacitance (nF/km) +: Cable capacitance per kilometer, in $\frac{nF}{km}$ + +Cost (USD/LF) +: Cost of the cable per linear foot ($\frac{USD}{LF}$). + +The following data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Array Cable | Conductor Size (mm2) | Current Capacity (A) | Rated Voltage (V) | AC Resistance (Ohms/km) | Inductance (mH/km) | Capacitance (nF/km) | Cost (USD/LF) | +|---------------|------------------------|------------------------|---------------------|---------------------------|----------------------|-----------------------|-----------------| +| AWG 1/0 | 120 | 300 | 36 | 0.253 | 0.398 | 0.179 | 6 | +| AWG 4/0 | 240 | 440 | 36 | 0.125 | 0.359 | 0.223 | 9 | +| MCM 500 | 500 | 640 | 36 | 0.0605 | 0.317 | 0.293 | 13 | +| MCM1000 | 800 | 830 | 36 | 0.0367 | 0.291 | 0.375 | 16 | +| MCM1250 | 1000 | 935 | 36 | 0.0291 | 0.284 | 0.411 | 17 | + +#### `equip` + +The `equip` sheet is for information about the equipment grouping required for each general operational +category. The optimal equipment will be chosen based on a comparison of the equipment grouping's +capabilities and the components being lifted. + +Equipment ID +: Unique identififer for the equipment group. + +Operation +: One of "Top", "Base", or "Offload" to indicate what grouping of operations the equipment can + perform. + +Equipment name +: Name of the equipment in the equipment group. + +Crane capacity tonne +: Maximum capacityof the crane, in metric tonnes. + +Number of equipment +: Number of the equipment used during the operation. + +The following subset of the data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Equipment ID | Operation | Equipment name | Crane capacity tonne | Number of equipment | +|----------------|-------------|------------------|------------------------|-----------------------| +| E1 | Base | Crawler crane | 500 | 1 | +| E1 | Base | Truck crane | 50 | 1 | +| E1 | Top | Crawler crane | 500 | 1 | +| E1 | Top | Truck crane | 50 | 2 | +| E2 | Top | Crawler crane | 600 | 1 | +| E2 | Top | Truck crane | 50 | 2 | +| E2 | Top | RT | 100 | 2 | +| E3 | Top | Crawler crane | 750 | 1 | +| E3 | Top | RT | 100 | 2 | +| E4 | Top | Crawler crane | 1000 | 1 | +| ... | ... | ... | ... | ... | + +#### `crane_specs` + +The `crane_specs` sheet should contain all the detailed information about a crane's operating +limits and capacities. + +Equipment name +: Name matching with the `equip` sheet. + +Crane name +: Crane model name + +Boom system +: Type of boom system used on the crane. Primarily used for logging crane details. + +Crane capacity tonne +: Maximum crane capacity. Used for matching cranes to components for ideal assembly costs and speeds. + +Speed of travel km per hr +: Ground travel speed, in $km/h$ + +Hoist speed m per min +: Crane lifting rate, in $m/min$. + +Crew type ID +: Unique identifer for the type of crew required for the operation. + +Equipment ID +: Unique identifier matching with the `equip` sheet. + +Setup time hr +: Amount of time, in hours, required for the crane to be prepared at a new lift location. + +Breakdown time hr +: Amount of time, in hours, required for the crane to be broken down prior to being moved to a new + lift location. + +Max wind speed m per s +: Maximum allowable windspeed allowed during operations, in $m/s$. + +Hub height m +: Maximum lift height for the crane, in $m$. + +Max capacity tonne +: Maximum lift capacity for the crane, in metric tonnes. + +Radius m +: Undefined, and potentially unused. + +Hook Height m +: Height of the hook above the top of the component to be lifted. Used for the total lift mass and + wind speed calculations. + +Mobilization cost USD +: Cost to mobilize the crane for the duration of the installation period, in USD. + +The following subset of the data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Equipment name | Crane name | Boom system | Crane capacity tonne | Speed of travel km per hr | Hoist speed m per min | Crew type ID | Equipment ID | Setup time hr | Breakdown time hr | Max wind speed m per s | Hub height m | Max capacity tonne | Radius m | Hook Height m | Mobilization cost USD | +|------------------|--------------|------------------|------------------------|-----------------------------|-------------------------|----------------|----------------|-----------------|---------------------|--------------------------|----------------|----------------------|------------|-----------------|-------------------------| +| Offload crane | LB 75 | Hydraulic | 75 | 40 | 60 | C0 | OL1 | 0 | 0 | 10 | 12.5 | 38 | 5.4 | 12.5 | 8040 | +| Offload crane | Big LB 2580 | Big Hydraulic | 500 | 40 | 60 | C0 | OL2 | 0 | 0 | 10 | 48 | 500 | 11 | 48 | 406100 | +| Offload crane | LB 258 | Hydraulic | 200 | 40 | 60 | C0 | OL2 | 0 | 0 | 10 | 48 | 63 | 11 | 48 | 40610 | +| Crawler crane | M999 | 22EL | 275 | 2 | 20 | C1 | B1 | 0 | 40 | 9 | 57 | 67 | 14 | 57 | 68709 | +| Crawler crane | LR1500 | SL3F | 500 | 2 | 20 | C1 | E1 | 0 | 40 | 9 | 80 | 102 | 16 | 94 | 184398 | +| Crawler crane | LR1500 | SL3F | 500 | 2 | 20 | C1 | E1 | 0 | 40 | 9 | 85 | 95 | 16 | 100 | 184398 | +| Crawler crane | LR1500 | SL3F | 500 | 2 | 20 | C1 | E1 | 0 | 40 | 9 | 90 | 87 | 16 | 106 | 184398 | +| Crawler crane | LR1500 | SL3F | 500 | 2 | 20 | C1 | E1 | 0 | 40 | 9 | 100 | 77 | 18 | 112 | 184398 | +| Crawler crane | LR1500 | SL4DFB + Derrick | 500 | 2 | 20 | C1 | E1 | 2 | 40 | 9 | 80 | 112 | 16 | 94 | 184398 | +| Crawler crane | LR1500 | SL4DFB + Derrick | 500 | 2 | 20 | C1 | E1 | 2 | 40 | 9 | 90 | 105 | 16 | 100 | 184398 | +| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | + +#### `development` + +The `development` sheet contains any project development costs that are unaccounted for in the other +project specifications. + +Type of cost +: Development cost category. + +Cost USD +: Total cost, in USD. + +Phase of construction +: Phase of construction where the cost is incurred, likely alway "Development". + +The following data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Type of cost | Cost USD | Phase of construction | +|------------------|------------|-------------------------| +| Equipment rental | 0 | Development | +| Labor | 1000000 | Development | +| Materials | 0 | Development | +| Mobilization | 0 | Development | +| Other | 0 | Development | + +#### `crew_price` + +The `crew_price` sheet contains information about the hourly and daily costs of having a single crew +member on site for each labor category. + +Labor type ID +: Unique labor type identifier, matching with `crew`. + +Hourly rate USD per hour +: Hourly cost of the crew per hour, in $USD/hr$. + +Per diem USD per day +: Daily cost for food, etc. for the type of crew member, in $USD/day$. + +The following subset of the data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Labor type ID | Hourly rate USD per hour | Per diem USD per day | +|----------------------|----------------------------|------------------------| +| Crane operator | 81.9105 | 149 | +| Oiler | 61.625 | 149 | +| Rigger | 85.84 | 149 | +| Truck driver | 66.41 | 149 | +| Iron worker | 94.5545 | 149 | +| Project manager | 119 | 149 | +| Site manager | 112.2 | 149 | +| Construction manager | 112.2 | 149 | +| Project engineer | 93.5 | 149 | +| Safety or qc manager | 106.118 | 149 | +| ... | ... | ... | + +#### `crew` + +The `crew` sheet is for information about the amount and type of labor required in a crew category. + +Crew type ID +: Unique crew type identifier, matching with `crane_specs` + +Operation +: General operational category for the type of crew member. + +Crew name +: Name of the crew type. + +Labor type ID +: Unique labor type identifier for the type of crew member. + +Number of workers +: Number of workers in the labor type that make up one unit of the crew. + +The following subset of the data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Crew type ID | Operation | Crew name | Labor type ID | Number of workers | +|----------------|-----------------------|--------------------------------|----------------------|---------------------| +| M0 | Management | Management - project size | Project manager | 1 | +| M0 | Management | Management - project size | Site manager | 1 | +| M0 | Management | Management - project size | Construction manager | 1 | +| M0 | Management | Management - project size | Project engineer | 1 | +| M0 | Management | Management - project size | Safety or qc manager | 1 | +| M0 | Management | Management - project size | Logistics manager | 1 | +| M0 | Management | Management - project size | Office admin | 1 | +| M1 | Management | Management - rate construction | Tool room | 1 | +| M1 | Management | Management - rate construction | Electrician | 2 | +| MC0 | Mechanical completion | Mechanical completion | QC/QA tech | 2 | +| ... | ... | ... | ... | ... | + +#### `equip_price` + +The `equip_price` sheet is for information about the operational costs for a single crane. + +Equipment name +: Name of the equipment. + +Crane capacity tonne +: Maximum capacity of the crane, in metric tonnes. + +Equipment price USD per hour +: Hourly cost of the equipment, in $USD/hr$. + +Cost USD per breakdown +: Total cost to breakdown the equipment before moving to another lift site, in USD. + +Fuel consumption gal per day +: Expected daily fuel consumption, in $gal/day$. + +The following subset of the data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Equipment name | Crane capacity tonne | Equipment price USD per hour | Cost USD per breakdown | Fuel consumption gal per day | +|------------------|------------------------|--------------------------------|--------------------------|--------------------------------| +| Mobile crane | 75 | 35 | nan | 44 | +| Mobile crane | 200 | 149 | nan | 64 | +| Crawler crane | 275 | 217 | 37674 | 76 | +| Crawler crane | 250 | 194 | 30534 | 72 | +| Crawler crane | 400 | 330 | 73375 | 97 | +| Crawler crane | 500 | 420 | 101936 | 113 | +| Crawler crane | 600 | 511 | 130497 | 130 | +| Crawler crane | 750 | 647 | 173339 | 154 | +| Crawler crane | 1000 | 873 | 244741 | 195 | +| Crawler crane | 1350 | 1190 | 344705 | 252 | +| ... | ... | ... | ... | ... | + +#### `material_price` + +The `material_price` sheet is about the construction materials costs for aspects such as road and +foundation building, not turbine components. + +Material type ID +: Unique material identifier + +Material price USD per unit +: Cost of the materials per unit, in $USD/unit$. + +Unit +: Measurement units used for measuring the material's quantity. + +The following data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Material type ID | Material price USD per unit | Unit | +|------------------------------------|-------------------------------|-------------------------------| +| unique identifier for the material | price of material per unit | unit of measure used for cost | +| Concrete 3000 psi | 117 | cubic yard | +| Concrete 5000 psi | 140 | cubic yard | +| Concrete 8000 psi | 130 | cubic yard | +| Steel - rebar | 1120 | ton (short) | +| Road base - 3/4 inch crushed stone | 15 | Loose cubic yard | +| Excavated dirt | 0 | cubic yard | +| Backfill | 0 | cubic yard | + +#### `rsmeans` + +The `rsmeans` sheet for any relevant RSMeans operational cost data. + +Operation ID +: Unique identifier for the operation. + +Type of cost +: Cost category. + +Material type ID +: Type of material used, should match `material_price`. + +Rate USD per unit +: Cost per unit, in $USD/unit$ + +Units +: Measurment units. + +Daily output +: Daily output of the operation. + +Per Diem Hours (per unit) +: Additional hourly per diem costs per measured unit. + +Module +: Construction phase category. Should be one of "Foundations", "Roads", or "Collection". + +Number of workers +: Number of workers required for the operation type. + +The following subset of data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Operation ID | Type of cost | Material type ID | Rate USD per unit | Units | Daily output | Per Diem Hours (per unit) | Module | Number of workers | +|----------------------------------------------|------------------|------------------------------------|---------------------|------------------------------|----------------|-----------------------------|-------------|---------------------| +| Concrete placement | Equipment rental | Concrete 5000 psi | 4.61 | $/cubic yard | | | Foundations | | +| Rebar installation | Equipment rental | Steel - rebar | 0 | $/ton (short) | | | Foundations | | +| Survey | Equipment rental | | | | | | Roads | | +| Clear and grub | Equipment rental | | | | | | Roads | | +| Topsoil stripping and stockpiling | Equipment rental | | 1.63 | cubic yard | | 0 | Roads | 1 | +| Stormwater pollution prevention | Equipment rental | | | | | | Roads | | +| Culverts | Equipment rental | | | | | | Roads | | +| Compaction of soil (subgrade and crane path) | Equipment rental | | 1.15 | embankment cubic yards crane | | 0 | Roads | 3 | +| Mass material movement (cut and fill) | Equipment rental | | | | | | Roads | | +| Placing road base (hauling) | Equipment rental | Road base - 3/4 inch crushed stone | 5.16 | loose cubic yard | | 0 | Roads | 1 | +| ... | ... | ... | ... | ... | ... | ... | ... | ... | + +#### `site_facility_building_area` + +The `site_facility_building_area` is for sizing the wind farm's on-site facility appropriately for +the wind power plant's size. + +Size Min (MW) +: Minimum wind power plant size to estimate the site facility building size, in $MW$. + +Size Max (MW) +: Maximum wind power plant size to estimate the site facility building size, in $MW$. + +Building area (sq. ft.) +: Building size for site facilities, in ${ft}^2$. + +The following data are used for the GE 1.5 MW public example (`ge15_public.xlsx`). + +| Size Min (MW) | Size Max (MW) | Building area (sq. ft.) | +|-----------------|-----------------|---------------------------| +| 0 | 200 | 3000 | +| 200 | 500 | 5000 | +| 500 | 800 | 7000 | +| 800 | 1000 | 9000 | +| 1000 | 5000 | 12000 | + +#### `weather_window` + +The hourly weather profiles uses 4 columns with 3 header rows formatted like the following table. +In the following data, taken from the GE 1.5 MW public example (`ge15_public.xlsx`), the first +row should contain the measurement units, and the second row should indicate the height at which +the measurement was taken, in $m$. + +| | Temperature | Pressure | Direction | Speed | +| ------------------- | ----------- | ---------- | --------- | ------ | +| | C | atm | Degrees | m/s | +| | 100 | 100 | 100 | 100 | +| 2012-01-01 12:00:00 | 2.444 | 0.95205318 | 185 | 8.681 | +| 2012-01-01 13:00:00 | 1.655 | 0.95227456 | 270 | 7.234 | +| 2012-01-01 14:00:00 | 2.512 | 0.9524854 | 322 | 12.898 | +| 2012-01-01 15:00:00 | 2.589 | 0.953325 | 321 | 14.795 | +| 2012-01-01 16:00:00 | 1.534 | 0.95440628 | 323 | 15.076 | +| 2012-01-01 17:00:00 | 1.014 | 0.95551016 | 322 | 15.53 | +| ... | ... | ... | ... | ... | + +## Running LandBOSSE + +The two primary means for running LandBOSSE are through the terminal using only Excel-based data, +or through Python (script, IDE, Jupyter Notebook, etc.). The following sections will walk through +running LandBOSSE through each of these methods. + +### Terminal + +Per the installation instructions, this example assumes your conda (or other) Python environment +has been created and LandBOSSE has been installed. + +1. Determine the desired input and output folder locations for your data. +2. Configure your project listing Excel file and the project data Excel file for each listed project + described in [Project Input Data](#project-input-data). + + :::{important} The name of the project listing must be called `project_list.xlsx` as it will + be the only file that is used for running projects. + ::: + +3. Open a terminal (or Anaconda Prompt or other) session. +4. Navigate to where LandBOSSE has been downloaded. In the terminal: + + ```bash + cd /path/to/LandBOSSE + ``` + +5. Activate your Python environment. For conda environments, but be sure to use the name of your + environment: + + ```bash + conda activate landbose + ``` + +6. Run all validLandBOSSE, where `-i` can be substituted for `--input`, and `-o` for `--output`. + + ```bash + python main.py -i /path/to/input-folder -o /path/to/output-folder + ``` + + Additional run flags also exist: + + - `-s` or `--scaling` for scaling study mode. This method modifies each row of the project list + after it has been modified by the parameters for to scale certain input values based on what + has been parametrically modified. + - `-v` or `--validate` which creates the file path for the output file from prior LandBOSSE run + that will be used to check latest run. The validation output file must be in the inputs folder + and must be called `landbosse-output-validation.xlsx`. + +### `LandBOSSERunner` + +#### Converting the Excel project list to a dictionary + +The below code snippet demonstrates how to convert the project list file for a given project, such +as the "foundation_validation_ge15" project found in `project_list_simplified.xlsx` that is +expemplified throughout this walkthrough. Please read the in-code comments for further context +about what steps are being taken and why. + +```python +import pandas as pd + +from landbosse.landbosse_runner import LandBOSSERunner + +inputs = LandBOSSERunner( + filename="/path/to/LandBOSSE/project_input_template/project_list_simplified.xlsx", + project_id="foundation_validation_ge15", + enable_cost_and_scaling_modifications=False, +) + +# Add a missing value from the Excel data that is used in the dictionary-based model +inputs["turbine_capex"] = 3500 + +# Load the data tables +# NOTE: the Excel input data as provided in this repository contains neither the path to +# the file, nor the extension, so they must be added here +inputs["data_tables"] = pd.read_excel( + f"/path/to/LandBOSSE/project_input_template/project_data/{inputs['data_tables']}.xlsx", + sheet_name=None, +) +``` + +#### Running the model + +1. Determine the desired input and output folder locations for your data. +2. Configure your project listing Excel file and the project data Excel file for each listed project + described in [Project Input Data](#project-input-data). +3. In a Python script, Jupyter Notebook, etc., some form of the following code can be used to run + a single project listing. Please read the inline comments for further context about what steps + are beging taken and why. + + ```python + from pathlib import Path + + import yaml + import pandas as pd + + from landbosse.landbosse_runner import LandBOSSERunner + + # If your data are stored in a YAML file (preferable), load them first, or + # create a data dictionary in place of the following code. + with Path("/path/to/my_project_data.yaml").open() as f: + inputs = yaml.safe_load(f) + + # Load the Excel project data. If the value of "data_tables" is relative to where the + # code will be run, simply exclude the `data_path` portion of the following code. + data_path = Path("/path/to/data_tables/").resolve() + inputs["data_tables"] = pd.read_excel( + data_path /inputs["data_tables"], sheet_name=None + ) + + # Optional: load alternative weather data + weather = pd.read_csv("/my/weather/data.csv") # replace csv with your file format + weather = LandBOSSERunner.add_header_to_weather_dataframe(weather) + + # Create your runner object, and run the analysis. Simply exclude the weather + # keyword argument if yours is contained in the `data_tables` + lb = LandBOSSERunner(input_config=inputs, weather=weather) + lb.run() + ``` + +4. Once the above code (or similar) is successfully run, the `result` object is created and attched + to the `LandBOSSERunner`. For complete details please see the `LandBOSSEResult` API documentation. + The `lb.result` (using the above examples naming convention for the runner object) contains the + following attributes as separate Pandas Series or DataFrames, and can be saved to CSV or one + Excel book as desired. + + - `project_parameters`: The values provided from the input dictionary. + - `data_sheets`: The Excel project data loaded as a dictionary of dataframes. + - `model_variables`: A copy of the model details that would traditionally be saved to Excel + when running LandBOSSE through the terminal. + - `operation_cost`: The detailed operational cost logs for the simulated installation. diff --git a/docs/examples.md b/docs/examples.md deleted file mode 100644 index b557eef..0000000 --- a/docs/examples.md +++ /dev/null @@ -1,3 +0,0 @@ -# Examples - -Coming soon \ No newline at end of file diff --git a/landbosse/excelio/XlsxReader.py b/landbosse/excelio/XlsxReader.py index a8cf4b0..5502239 100644 --- a/landbosse/excelio/XlsxReader.py +++ b/landbosse/excelio/XlsxReader.py @@ -18,27 +18,16 @@ class XlsxReader: of data is read from the following sheets: - components - - cable_specs - - equip - - crane_specs - - development - - crew_price - - crew - - equip_price - - material_price - - rsmeans - - site_facility_building_area - - weather_window The second set of data are read from a single sheet as described below. diff --git a/landbosse/landbosse_runner.py b/landbosse/landbosse_runner.py index 47aee8a..f17a7a0 100644 --- a/landbosse/landbosse_runner.py +++ b/landbosse/landbosse_runner.py @@ -3,6 +3,8 @@ """ import typing +from copy import deepcopy +from pathlib import Path import attrs import pandas as pd @@ -95,34 +97,13 @@ class LandBOSSERunner: "labor_cost_multiplier": "Multiplier to modify labor costs", "crane_breakdown_fraction": "What fraction of cranes will breakdown. 0 means none, 1 means " "all. Breakdowns increase the total erection duration", - "component": { - "nacelle": { - "mass_t": "nacelle mass (t)", - "surface_area_m2": "nacelle surface area (m^2)", - }, - "hub": { - "mass_t": "hub mass (t)", - "surface_area_m2": "hub surface area (m^2)", - }, - "blade": { - "mass_t": "Blade mass (t) (one blade)", - "surface_area_m2": "Blade surface area (m^2) (one blade)", - }, - "tower_section": { - "mass_t": "List of tower section masses (t), from bottom to top. Must be the same " - "length as the other tower section attributes", - "surface_area_m2": "List of tower section surface areas (m^2), from bottom to top. " - "Must be the same legnth as the other tower section attributes", - "height_m": "List of tower section heights (m), from bottom to top. Must be the " - "same length as the other tower section attributes", - }, - }, + "user_trench_length_flag": "Flag (0 = no; 1 = yes) to indicate if the user-defined trench length should be used" } # Mapping from new parameter input names to those expected by LandBOSSE keys_rename: typing.ClassVar[dict] = { "id": "Project ID", - "datafile": "Project data file", + "data_tables": "Project data file", "construction_months": "Total project construction time (months)", "turbine_rating_MW": "Turbine rating MW", "hub_height_m": "Hub height m", @@ -172,11 +153,14 @@ class LandBOSSERunner: "DW only)", "labor_cost_multiplier": "Labor cost multiplier", "crane_breakdown_fraction": "Crane breakdown fraction", + "combined_homerun_trench_length_km": "Combined Homerun Trench Length to Substation (km)", + "user_trench_length_flag": "Flag for user-defined home run trench length (0 = no; 1 = yes)", } input_config: dict = attrs.field(converter=dict) weather: pd.DataFrame = attrs.field( - validator=attrs.validators.instance_of(pd.DataFrame), + default=None, + validator=attrs.validators.optional(attrs.validators.instance_of(pd.DataFrame)), ) result: LandBOSSEResult = attrs.field( validator=attrs.validators.instance_of(LandBOSSEResult), @@ -186,6 +170,38 @@ class LandBOSSERunner: def __attrs_post_init__(self): self.check_expected_configs_are_provided(self.input_config) + @staticmethod + def convert_excel_to_dict( + filename: str | Path, + project_id: str, + *, + enable_cost_and_scaling_modifications: bool = False, + ) -> dict: + """Convert a :py:attr:`project_id` from an Excel project list to a ``LandBOSSERunner`` + compliant dictionary. + + Parameters + ---------- + filename : str | Path + The project list file where the project's data can be found. + project_id : str + The ID for the project in the "Project ID" column of the project list. + enable_cost_and_scaling_modifications : bool, optional + Whether or not to enable the cost and scaling modifications, by default False. + + Returns + ------- + dict + The project listing data for a given :py:attr:`project_id` converted to a ``LandBOSSERunner``-compliant + format. + """ + df = pd.read_excel(filename) + data = df.loc[df["Project ID"].eq(project_id)].reset_index(drop=True).loc[0].to_dict() + keys_rename = {v: k for k, v in LandBOSSERunner.keys_rename.items()} + data = {keys_rename.get(k, k): v for k, v in data.items()} + data["enable_cost_and_scaling_modifications"] = enable_cost_and_scaling_modifications + return data + def check_expected_configs_are_provided(self, input_config: dict) -> None: """Checks to ensure all inputs required by the model have been provided. @@ -216,8 +232,12 @@ def get_project_parameters(self) -> pd.Series: ------- pd.Series Series containing input parameters for LandBOSSE with the required names + dict[str, pd.DataFrame] + Dictionary of the Excel project data with each sheet's name as a key and the + data loaded as a pandas DataFrame. """ - project_parameters_dict = self.input_config + project_parameters_dict = deepcopy(self.input_config) + data_sheets = project_parameters_dict.pop("data_tables") # Convert parameters dict to pandas Series (LandBOSSE expects a Series) project_parameters = pd.Series(project_parameters_dict, name="value") @@ -237,7 +257,7 @@ def get_project_parameters(self) -> pd.Series: project_parameters["Project ID with serial"] = project_parameters["Project ID"] - return project_parameters + return project_parameters, data_sheets def run(self) -> None: """Run the LandBOSSE model and save outputs in a LandBOSSEResult object instead of excel @@ -248,14 +268,11 @@ def run(self) -> None: LandBOSSEResult Outputs from LandBOSSE model """ - project_parameters = self.get_project_parameters() - data_sheets = project_parameters.pop("data_table") + project_parameters, data_sheets = self.get_project_parameters() - # Read WAVES weather data into expected LandBOSSE format - data_sheets["weather_window"] = self.add_header_to_weather_dataframe(self.weather) - - # Convert YAML component info into table format expected by LandBOSSE - data_sheets["components"] = self.create_component_dataframe(project_parameters, data_sheets) + # Prioritize weather profile provided at initialization over existing "weather_window" in Excel + if self.weather is not None: + data_sheets["weather_window"] = self.add_header_to_weather_dataframe(self.weather) xlsx_reader = XlsxReader() xlsx_reader.modify_project_data_and_project_list(data_sheets, project_parameters) @@ -338,99 +355,6 @@ def add_header_to_weather_dataframe(weather_window: pd.DataFrame) -> pd.DataFram ) return weather_window - @staticmethod - def create_component_dataframe( - project_parameters: pd.Series, - data_sheets: dict[str, pd.DataFrame], - ) -> pd.DataFrame: - """Convert the data from the input component data dictionary to the dataframe format - required by LandBOSSE. This includes rows for each separate blade and tower section. - - Parameters - ---------- - project_parameters : pd.Series - LandBOSSE YAML inputs - data_sheets : dict[str, pd.DataFrame] - LandBOSSE excel input tables - - Returns - ------- - pd.DataFrame - Component data in LandBOSSE format - """ - NUM_BLADES = 3 - - component_param = project_parameters["component"] - component_template = data_sheets["components"] - - component_nacelle = pd.Series(component_param["nacelle"], name="Nacelle") - component_nacelle["Component Name"] = "Nacelle" - - component_hub = pd.Series(component_param["hub"], name="Hub") - component_hub["Component Name"] = "Hub" - - component_blade = component_param["blade"] - component_blade["Component Name"] = "Blade" - - component_blade = pd.Series(component_blade) - component_blade = pd.concat( - objs={f"Blade {i+1:d}": component_blade for i in range(NUM_BLADES)}, - axis=1, - ) - - component_tower_section = pd.DataFrame(component_param["tower_section"]) - component_tower_section["Component Name"] = "Tower section" - component_tower_section = component_tower_section.set_axis( - [f"Tower section {i+1:d}" for i in range(len(component_tower_section.index))], - ) - component_tower_section["lift_height_m"] = ( - component_tower_section["height_m"].shift(-1).cumsum() - ) - component_tower_section["lever_arm_m"] = ( - component_tower_section["lift_height_m"] + component_tower_section["height_m"] / 2.0 - ) - - component_df = pd.concat( - objs=(component_nacelle, component_hub, component_blade, component_tower_section.T), - axis=1, - ).T - - hub_height = project_parameters["Hub height m"] - component_df = component_df.astype( - { - "height_m": float, - "lift_height_m": float, - "lever_arm_m": float, - } - ) - component_df = component_df.fillna( - { - "height_m": 0.0, - "lift_height_m": hub_height, - "lever_arm_m": hub_height, - } - ) - component_df = component_df.rename( - columns={ - "mass_t": "Mass tonne", - "surface_area_m2": "Surface area sq m", - "height_m": "Section height m", - "lift_height_m": "Lift height m", - "lever_arm_m": "Lever arm m", - } - ) - - component_combined = component_df.merge(component_template, on="Component Name") - component_combined = component_combined.drop(columns="Component Name") - component_combined.insert( - loc=0, - value=component_df.index, - column="Component", - ) - component_combined = component_combined.convert_dtypes() - - return component_combined - if __name__ == "__main__": pass diff --git a/landbosse/model/ErectionCost.py b/landbosse/model/ErectionCost.py index 7436847..03fe4f0 100644 --- a/landbosse/model/ErectionCost.py +++ b/landbosse/model/ErectionCost.py @@ -144,7 +144,7 @@ def __init__(self, input_dict, output_dict, project_name): material_price (pd.DatFrame) Prices for various materials used during erection. rsmeans - (p.DataFrame) RSMeans data + (pd.DataFrame) RSMeans data output_dict : dict The output dictionary with key value pairs as found on the output documentation. diff --git a/landbosse/model/FoundationCost.py b/landbosse/model/FoundationCost.py index bab37e9..bddeeed 100644 --- a/landbosse/model/FoundationCost.py +++ b/landbosse/model/FoundationCost.py @@ -158,7 +158,7 @@ def calculate_foundation_load(self, foundation_load_input_data, foundation_load_ * Section height m * Surface area sq m * Coeff drag (installed) - * Lever arm m m + * Lever arm m * Multplier drag rotor * Multiplier tower drag * Mass tonne diff --git a/pyproject.toml b/pyproject.toml index 780588a..ae057b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ dependencies = ['numpy', 'openpyxl', 'pandas', 'scipy', 'xlsxwriter'] [project.optional-dependencies] # Optional test = ["coveralls", "pytest"] docs = ["jupyter-book==1.*", "myst-parser", "sphinxcontrib-napoleon"] +develop = ["NREL-landbosse[test, docs]"] # List URLs that are relevant to your project #