Skip to content

Commit

Permalink
Merge pull request #133 from WISDEM/dev
Browse files Browse the repository at this point in the history
v1.0.8 Release
  • Loading branch information
JakeNunemaker authored Mar 1, 2023
2 parents f8d6ac4 + e88297e commit 1653322
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 31 deletions.
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
ci:
skip: [isort, black, pylint]

repos:
- repo: https://github.com/timothycrosley/isort
rev: 4.3.21
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2020 Alliance for Sustainable Energy, LLC
Copyright (c) 2020 Alliance for Sustainable Energy, LLC

Apache License
Version 2.0, January 2004
Expand Down
2 changes: 1 addition & 1 deletion ORBIT/core/cargo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class Cargo(Object):

def __repr__(self):
return self.type

Expand Down
56 changes: 52 additions & 4 deletions ORBIT/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ProjectManager:
date_format_short = "%m/%d/%Y"
date_format_long = "%m/%d/%Y %H:%M"

_design_phases = [
_design_phases = (
MonopileDesign,
ArraySystemDesign,
CustomArraySystemDesign,
Expand All @@ -71,9 +71,9 @@ class ProjectManager:
MooringSystemDesign,
SemiSubmersibleDesign,
SparDesign,
]
)

_install_phases = [
_install_phases = (
MonopileInstallation,
TurbineInstallation,
OffshoreSubstationInstallation,
Expand All @@ -85,7 +85,7 @@ class ProjectManager:
GravityBasedInstallation,
FloatingSubstationInstallation,
JacketInstallation,
]
)

def __init__(self, config, library_path=None, weather=None):
"""
Expand Down Expand Up @@ -188,6 +188,54 @@ def phases(self):

return self._phases

@classmethod
def register_design_phase(cls, phase):
"""
Add a custom design phase to the `ProjectManager` class.
Parameters
----------
phase : ORBIT.phases.DesignPhase
"""

if not issubclass(phase, DesignPhase):
raise ValueError(
"Registered design phase must be a subclass of "
"'ORBIT.phases.DesignPhase'."
)

if phase.__name__ in [c.__name__ for c in cls._design_phases]:
raise ValueError(f"A phase with name '{phase.__name__}' already exists.")

if len(re.split("[_ ]", phase.__name__)) > 1:
raise ValueError(f"Registered phase name must not include a '_'.")

cls._design_phases = (*cls._design_phases, phase)

@classmethod
def register_install_phase(cls, phase):
"""
Add a custom install phase to the `ProjectManager` class.
Parameters
----------
phase : ORBIT.phases.InstallPhase
"""

if not issubclass(phase, InstallPhase):
raise ValueError(
"Registered install phase must be a subclass of "
"'ORBIT.phases.InstallPhase'."
)

if phase.__name__ in [c.__name__ for c in cls._install_phases]:
raise ValueError(f"A phase with name '{phase.__name__}' already exists.")

if len(re.split("[_ ]", phase.__name__)) > 1:
raise ValueError(f"Registered phase name must not include a '_'.")

cls._install_phases = (*cls._install_phases, phase)

@property
def _capex_categories(self):
"""Returns CapEx categories for phases in `self._install_phases`."""
Expand Down
51 changes: 37 additions & 14 deletions ORBIT/phases/design/array_system_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import matplotlib.pyplot as plt

from ORBIT.core.library import export_library_specs, extract_library_specs
from ORBIT.core.exceptions import LibraryItemNotFoundError
from ORBIT.phases.design._cables import Plant, CableSystem


Expand Down Expand Up @@ -363,7 +364,7 @@ def run(self):
self._create_wind_farm_layout()
self._create_cable_section_lengths()

def save_layout(self, save_name, return_df=False):
def save_layout(self, save_name, return_df=False, folder="cables"):
"""Outputs a csv of the substation and turbine positional and cable
related components.
Expand All @@ -375,12 +376,23 @@ def save_layout(self, save_name, return_df=False):
return_df : bool, optional
If true, returns layout_df, a pandas.DataFrame of the cabling
layout, by default False.
folder : str, optional
If "cables", then the layout will saved to the "cables" folder, and
if "plant", then the layout will be saved to the "project/plant" folder.
Returns
-------
pd.DataFrame
The DataFrame with the layout data.
Raises
------
ValueError
Raised if ``folder`` is not one of "cables" or "plant".
"""
if folder not in ("cables", "plant"):
raise ValueError("`folder` must be one of: 'cables' or plant'.")

num_turbines = self.system.num_turbines
columns = [
"id",
Expand Down Expand Up @@ -448,7 +460,7 @@ def save_layout(self, save_name, return_df=False):
print(
f"Saving custom array CSV to: <library_path>/cables/{save_name}.csv"
)
export_library_specs("cables", save_name, data, file_ext="csv")
export_library_specs(folder, save_name, data, file_ext="csv")
if return_df:
return layout_df

Expand Down Expand Up @@ -725,14 +737,21 @@ def __init__(self, config, distance=False, **kwargs):
super().__init__(config, **kwargs)
self.distance = config["array_system_design"].get("distance", distance)

def create_project_csv(self, save_name):
def create_project_csv(self, save_name, folder="cables"):
"""Creates a base CSV in <`library_path`>/cables/
Parameters
----------
save_name : [type]
[description]
Raises
------
ValueError
Raised if ``folder`` is not one of "cables" or "plant".
"""
if folder not in ("cables", "plant"):
raise ValueError("`folder` must be one of: 'cables' or plant'.")

self._initialize_cables()
self.create_strings()
Expand Down Expand Up @@ -792,7 +811,7 @@ def create_project_csv(self, save_name):
print(
f"Saving custom array CSV to: <library_path>/cables/{save_name}.csv"
)
export_library_specs("cables", save_name, rows, file_ext="csv")
export_library_specs(folder, save_name, rows, file_ext="csv")

def _format_windfarm_data(self):

Expand Down Expand Up @@ -843,16 +862,20 @@ def _format_windfarm_data(self):
def _initialize_custom_data(self):
windfarm = self.config["array_system_design"]["location_data"]

self.location_data = extract_library_specs(
"cables", windfarm, file_type="csv"
)

try:
self.location_data = extract_library_specs(
"cables", windfarm, file_type="csv"
)
except LibraryItemNotFoundError:
self.location_data = extract_library_specs(
"plant", windfarm, file_type="csv"
)
# Make sure no data is missing
missing = set(self.COLUMNS).difference(self.location_data.columns)
if missing:
raise ValueError(
"The following columns must be included in the location ",
f"data: {missing}",
"The following columns must be included in the location "
f"data: {missing}"
)

self._format_windfarm_data()
Expand Down Expand Up @@ -885,9 +908,9 @@ def _initialize_custom_data(self):
# Ensure the number of turbines matches what's expected
if self.location_data.shape[0] != self.system.num_turbines:
raise ValueError(
"The provided number of turbines ",
f"({self.location_data.shape[0]}) does not match the plant ",
f"data ({self.system.num_turbines}).",
"The provided number of turbines "
f"({self.location_data.shape[0]}) does not match the plant "
f"data ({self.system.num_turbines})."
)

n_coords = self.location_data.groupby(
Expand Down Expand Up @@ -1017,7 +1040,7 @@ def _create_windfarm_layout(self):
self.sections_bury_speeds[
string, order
] = data.bury_speed.values[order]
i += string + 1
i = string + 1

# Ensure any point in array without a turbine is set to None
no_turbines = self.location_data_x == 0
Expand Down
2 changes: 1 addition & 1 deletion ORBIT/phases/install/oss_install/floating.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def initialize_installation_vessel(self):

@property
def detailed_output(self):

return {}


Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ORBIT
Offshore Renewables Balance of system and Installation Tool


:Version: 1.0.7
:Version: 1.0.8
:Authors: `Jake Nunemaker <https://www.linkedin.com/in/jake-nunemaker/>`_, `Matt Shields <https://www.linkedin.com/in/matt-shields-834a6b66/>`_, `Rob Hammond <https://www.linkedin.com/in/rob-hammond-33583756/>`_
:Documentation: `ORBIT Docs <https://wisdem.github.io/ORBIT/>`_

Expand Down
2 changes: 1 addition & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
9 changes: 9 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
ORBIT Changelog
===============

1.0.8
-----

- Added explicit methods for adding custom design or install phases to
ProjectManager.
- Added WOMBAT compatibility for custom array system files.
- Fixed bug in custom array cable system design that breaks for plants with
more than two substations.

1.0.7
-----

Expand Down
2 changes: 1 addition & 1 deletion library/turbines/15MW_generic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ tower:
type: Tower
length: 150
mass: 480 # t
turbine_rating: 15 # MW
turbine_rating: 15 # MW
10 changes: 5 additions & 5 deletions templates/design_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ def __init__(self, config, **kwargs):
config : dict
"""

config = self.initialize_library(config, **kwargs) # These first two lines are required in all modules. They initialize the library
config = self.initialize_library(config, **kwargs) # These first two lines are required in all modules. They initialize the library
self.config = self.validate_config(config) # if it hasn't already been and validate the config against '.expected_config' from above


self._design = self.config.get("spar_design", {}) # Not required, but I often save module specific outputs to "_design" for later use
# If the "spar_design" sub dictionary isn't found, an empty one is returned to
# work with later methods.
# work with later methods.
self._outputs = {}

def run(self):
Expand Down Expand Up @@ -174,7 +174,7 @@ def stiffened_column_cost(self):
Calculates the cost of the stiffened column for a single spar. From original OffshoreBOS model.
"""

cr = self._design.get("stiffened_column_CR", 3120) # This is how I typically handle outputs. This will look for the key in
cr = self._design.get("stiffened_column_CR", 3120) # This is how I typically handle outputs. This will look for the key in
# self._design, and return default value if it isn't found.
return self.stiffened_column_mass * cr

Expand Down Expand Up @@ -267,7 +267,7 @@ def substructure_cost(self):
# The following properties are required methods for a DesignPhase

# .detailed_output returns any relevant detailed outputs from the module
# in a dictionary.
# in a dictionary.
@property
def detailed_output(self):
"""Returns detailed phase information."""
Expand Down
Loading

0 comments on commit 1653322

Please sign in to comment.