Skip to content

Commit

Permalink
Merge branch 'dev' into enhancement/pyproject-setup
Browse files Browse the repository at this point in the history
  • Loading branch information
RHammond2 committed Jun 20, 2024
2 parents a458fd2 + 4d242e4 commit 5d728a1
Show file tree
Hide file tree
Showing 54 changed files with 2,947 additions and 866 deletions.
7 changes: 7 additions & 0 deletions ORBIT/api/wisdem.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ def setup(self):
3,
desc="Number of station keeping vessels that attach to floating platforms under tow-out.",
)
self.add_discrete_input(
"ahts_vessels",
1,
desc="Number of ahts vessels that attach to floating platforms under tow-out.",
)
self.add_discrete_input(
"oss_install_vessel",
"example_heavy_lift_vessel",
Expand Down Expand Up @@ -378,11 +383,13 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o
if floating_flag:
vessels = {
"support_vessel": "example_support_vessel",
"ahts_vessel" : "example_ahts_vessel",
"towing_vessel": "example_towing_vessel",
"mooring_install_vessel": "example_support_vessel",
"towing_vessel_groups": {
"towing_vessels": int(discrete_inputs["num_towing"]),
"station_keeping_vessels": int(discrete_inputs["num_station_keeping"]),
"ahts_vessels" : int(discrete_inputs["ahts_vessels"])
},
}
else:
Expand Down
2 changes: 1 addition & 1 deletion ORBIT/core/defaults/process_times.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"mooring_system_load_time": 5 # hr
"mooring_site_survey_time": 4 # hr
"suction_pile_install_time": 11 # hr
"drag_embed_install_time": 5 # hr
"drag_embed_install_time": 12 # hr # Increased from 5h so it now includes proof loading. This time increases with depth (in mooring.py)

# Misc.
"site_position_time": 2 # hr
Expand Down
3 changes: 2 additions & 1 deletion ORBIT/core/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
import yaml
import pandas as pd
from yaml import Dumper

from ORBIT.core.exceptions import LibraryItemNotFoundError

ROOT = os.path.abspath(os.path.join(os.path.abspath(__file__), "../../.."))
default_library = os.path.join(ROOT, "library")


# Need a custom loader to read in scientific notation correctly
class CustomSafeLoader(yaml.SafeLoader):
def construct_python_tuple(self, node):
Expand Down Expand Up @@ -277,6 +277,7 @@ def export_library_specs(key, filename, data, file_ext="yaml"):
"wtiv": "vessels",
"towing_vessel": "vessels",
"support_vessel": "vessels",
"ahts_vessel": "vessels",
# cables
"cables": "cables",
"array_system": "cables",
Expand Down
4 changes: 4 additions & 0 deletions ORBIT/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
ArraySystemDesign,
ExportSystemDesign,
MooringSystemDesign,
SemiTautMooringSystemDesign,
ScourProtectionDesign,
SemiSubmersibleDesign,
CustomArraySystemDesign,
OffshoreSubstationDesign,
OffshoreFloatingSubstationDesign,
)
from ORBIT.phases.install import (
JacketInstallation,
Expand Down Expand Up @@ -69,7 +71,9 @@ class ProjectManager:
ExportSystemDesign,
ScourProtectionDesign,
OffshoreSubstationDesign,
OffshoreFloatingSubstationDesign,
MooringSystemDesign,
SemiTautMooringSystemDesign,
SemiSubmersibleDesign,
SparDesign,
ElectricalDesign,
Expand Down
167 changes: 167 additions & 0 deletions ORBIT/phases/design/SemiTaut_mooring_system_design.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
"""`MooringSystemDesign` and related functionality."""

__author__ = "Jake Nunemaker, modified by Becca F."
__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
__maintainer__ = "Jake Nunemaker"
__email__ = "[email protected] & [email protected]"

from scipy.interpolate import interp1d
import numpy as np

from ORBIT.phases.design import DesignPhase


class SemiTautMooringSystemDesign(DesignPhase):
"""SemiTaut Mooring System and Anchor Design."""

expected_config = {
"site": {"depth": "float"},
"turbine": {"turbine_rating": "int | float"},
"plant": {"num_turbines": "int"},
"mooring_system_design": {
"num_lines": "int | float (optional, default: 4)",
"anchor_type": "str (optional, default: 'Drag Embedment')",
"mooring_line_cost_rate": "int | float (optional)",
"drag_embedment_fixed_length": "int | float (optional, default: .5km)",
},
}

output_config = {
"mooring_system": {
"num_lines": "int",
#"line_diam": "m, float", # this is not needed for mooring.py
"line_mass": "t", # you need this for mooring.py (mooring installation module)
"line_cost": "USD", # you can calculate this based on each rope&chain length & diameter.
"line_length": "m", # this can be calculated from rope length and chain length (which you get from an empirical eqn as function of depth)
"anchor_mass": "t", # you need this for mooring.py (mooring installation module)
"anchor_type": "str", # keep, changed default to drag embedment.
"anchor_cost": "USD", # this can be calculated also as a function of (depth?) from the empirical data you have.
}
}

def __init__(self, config, **kwargs):
"""
Creates an instance of SemiTautMooringSystemDesign.
Parameters
----------
config : dict
"""

config = self.initialize_library(config, **kwargs)
self.config = self.validate_config(config)
self.num_turbines = self.config["plant"]["num_turbines"]

self._design = self.config.get("mooring_system_design", {})
self.num_lines = self._design.get("num_lines", 4)
self.anchor_type = self._design.get("anchor_type", "Drag Embedment")

self._outputs = {}

def run(self):
"""
Main run function.
"""

self.calculate_line_length_mass()
self.determine_mooring_line_cost()
self.calculate_anchor_mass_cost()

self._outputs["mooring_system"] = {**self.design_result}

def calculate_line_length_mass(self):
"""
Returns the mooring line length and mass.
"""

depth = self.config["site"]["depth"]

# Input hybrid mooring system design from Cooperman et al. (2022), https://www.nrel.gov/docs/fy22osti/82341.pdf 'Assessment of Offshore Wind Energy Leasing Areas for Humboldt and Moorow Bay Wind Energy Areas, California
depths = np.array([500, 750, 1000, 1250, 1500])
rope_lengths = np.array([478.41, 830.34, 1229.98, 1183.93, 1079.62])
rope_diameters = np.array([0.2, 0.2, 0.2, 0.2, 0.2]) # you need the diameter for the cost data
chain_lengths = np.array([917.11, 800.36, 609.07, 896.42, 1280.57])
chain_diameters = np.array([0.13, 0.17, 0.22, 0.22, 0.22])

# Interpolate
finterp_rope = interp1d(depths, rope_lengths)
finterp_chain = interp1d(depths, chain_lengths)
finterp_rope_diam = interp1d(depths, rope_diameters)
finterp_chain_diam = interp1d(depths, chain_diameters)

# Rope and chain length at project depth
self.chain_length = finterp_chain(depth)
self.rope_length = finterp_rope(depth)
# Rope and chain diameter at project depth
self.rope_diameter = finterp_rope_diam(depth)
self.chain_diameter = finterp_chain_diam(depth)

self.line_length = self.rope_length + self.chain_length

chain_kg_per_m = 19900 * (self.chain_diameter**2) # 19,900 kg/m^2 (diameter)/m (length)
rope_kg_per_m = 797.8 * (self.rope_diameter**2) # 797.8 kg/ m^2 (diameter) / m (length)
self.line_mass = (self.chain_length * chain_kg_per_m) + (self.rope_length * rope_kg_per_m) # kg
#print('total hybrid line mass is ' + str(self.line_mass) + 'kg')
# convert kg to metric tonnes
self.line_mass = self.line_mass/1e3

def calculate_anchor_mass_cost(self):
"""
Returns the mass and cost of anchors.
TODO: Anchor masses are rough estimates based on initial literature
review. Should be revised when this module is overhauled in the future.
"""

if self.anchor_type == "Drag Embedment":
self.anchor_mass = 20 # t. This should be updated, but I don't have updated data right now for this.
# Input hybrid mooring system design from Cooperman et al. (2022), https://www.nrel.gov/docs/fy22osti/82341.pdf 'Assessment of Offshore Wind Energy Leasing Areas for Humboldt and Moorow Bay Wind Energy Areas, California
depths = np.array([500, 750, 1000, 1250, 1500])
anchor_costs = np.array([112766, 125511, 148703, 204988, 246655]) # [USD]

# interpolate anchor cost to project depth
depth = self.config["site"]["depth"]
finterp_anchor_cost = interp1d(depths, anchor_costs)
self.anchor_cost = finterp_anchor_cost(depth) # replace this with interp. function based on depth of hybrid mooring line

def determine_mooring_line_cost(self):
"""Returns cost of one line mooring line."""
# Input hybrid mooring system design from Cooperman et al. (2022), https://www.nrel.gov/docs/fy22osti/82341.pdf 'Assessment of Offshore Wind Energy Leasing Areas for Humboldt and Moorow Bay Wind Energy Areas, California
depths = np.array([500, 750, 1000, 1250, 1500]) # [m]
total_line_costs = np.array([826598, 1221471, 1682208, 2380035, 3229700]) # [USD]
finterp_total_line_cost = interp1d(depths, total_line_costs)
depth = self.config["site"]["depth"]
self.line_cost = finterp_total_line_cost(depth)
return self.line_cost

@property
def total_cost(self):
"""Returns the total cost of the mooring system."""

return (
self.num_lines
* self.num_turbines
* (self.anchor_cost + self.line_cost)
)

@property
def detailed_output(self):
"""Returns detailed phase information."""

return {
"num_lines": self.num_lines,
#"line_diam": self.line_diam,
"line_mass": self.line_mass,
"line_length": self.line_length,
"line_cost": self.line_cost,
"anchor_type": self.anchor_type,
"anchor_mass": self.anchor_mass,
"anchor_cost": self.anchor_cost,
"system_cost": self.total_cost,
}

@property
def design_result(self):
"""Returns the results of the design phase."""

return {"mooring_system": self.detailed_output}
2 changes: 2 additions & 0 deletions ORBIT/phases/design/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
from .monopile_design import MonopileDesign
from .electrical_export import ElectricalDesign
from .array_system_design import ArraySystemDesign, CustomArraySystemDesign
from .oss_design_floating import OffshoreFloatingSubstationDesign
from .export_system_design import ExportSystemDesign
from .mooring_system_design import MooringSystemDesign
from .scour_protection_design import ScourProtectionDesign
from .semi_submersible_design import SemiSubmersibleDesign
from .SemiTaut_mooring_system_design import SemiTautMooringSystemDesign
61 changes: 42 additions & 19 deletions ORBIT/phases/design/electrical_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class ElectricalDesign(CableSystem):
"converter_cost": "USD (optional)",
"onshore_converter_cost": "USD (optional)",
"topside_assembly_factor": "float (optional)",
"oss_substructure_type": "str (optional, default: Monopile)",
"oss_substructure_cost_rate": "USD/t (optional)",
"oss_pile_cost_rate": "USD/t (optional)",
},
Expand Down Expand Up @@ -126,20 +127,24 @@ def __init__(self, config, **kwargs):
"interconnection_distance", 3
)

# SUBSTATION
self._outputs = {}

def run(self):
"""Main run function."""

self.export_system_design = self.config["export_system_design"]
self.offshore_substation_design = self.config.get(
"substation_design", {}
)

self.substructure_type = self.offshore_substation_design.get(
"oss_substructure_type", "Monopile"
)

self.onshore_substation_design = self.config.get(
"onshore_substation_design", {}
)

self._outputs = {}

def run(self):
"""Main run function."""

# CABLES
self._initialize_cables()
self.cable = self.cables[[*self.cables][0]]
Expand Down Expand Up @@ -183,7 +188,7 @@ def run(self):
self.calc_onshore_cost()

self._outputs["offshore_substation_substructure"] = {
"type": "Monopile", # Substation install only supports monopiles
"type": self.substructure_type,
"deck_space": self.substructure_deck_space,
"mass": self.substructure_mass,
"length": self.substructure_length,
Expand Down Expand Up @@ -346,19 +351,18 @@ def calc_num_substations(self):
----------
substation_capacity : int | float
"""
self._design = self.config.get("substation_design", {})

# HVAC substation capacity
_substation_capacity = self._design.get(
_substation_capacity = self.offshore_substation_design.get(
"substation_capacity", 1200
) # MW

if "HVDC" in self.cable.cable_type:
self.num_substations = self._design.get(
self.num_substations = self.offshore_substation_design.get(
"num_substations", int(self.num_cables / 2)
)
else:
self.num_substations = self._design.get(
self.num_substations = self.offshore_substation_design.get(
"num_substations",
int(np.ceil(self._plant_capacity / _substation_capacity)),
)
Expand Down Expand Up @@ -494,8 +498,10 @@ def calc_assembly_cost(self):
topside_assembly_factor : int | float
"""

_design = self.config.get("substation_design", {})
topside_assembly_factor = _design.get("topside_assembly_factor", 0.075)
topside_assembly_factor = self.offshore_substation_design.get(
"topside_assembly_factor", 0.075
)

self.land_assembly_cost = (
self.switchgear_cost
+ self.shunt_reactor_cost
Expand All @@ -512,14 +518,27 @@ def calc_substructure_mass_and_cost(self):
oss_pile_cost_rate : int | float
"""

_design = self.config.get("substation_design", {})
substructure_mass = 0.4 * self.topside_mass
oss_pile_cost_rate = _design.get("oss_pile_cost_rate", 0)
oss_substructure_cost_rate = _design.get(
oss_pile_cost_rate = self.offshore_substation_design.get(
"oss_pile_cost_rate", 0
)
oss_substructure_cost_rate = self.offshore_substation_design.get(
"oss_substructure_cost_rate", 3000
)

substructure_pile_mass = 8 * substructure_mass**0.5574
# Substructure mass components calculated by curve fits in
# equations 81-84 from Maness et al. 2017
# https://www.nrel.gov/docs/fy17osti/66874.pdf
#
# TODO: Determine a better method to calculate substructure mass
# for different substructure types
substructure_mass = 0.4 * self.topside_mass

if self.substructure_type == "Floating":
substructure_pile_mass = 0 # No piles used for floating platform

else:
substructure_pile_mass = 8 * substructure_mass**0.5574

self.substructure_cost = (
substructure_mass * oss_substructure_cost_rate
+ substructure_pile_mass * oss_pile_cost_rate
Expand All @@ -532,7 +551,11 @@ def calc_substructure_length(self):
Calculates substructure length as the site depth + 10m
"""

self.substructure_length = self.config["site"]["depth"] + 10
if self.substructure_type == "Floating":
self.substructure_length = 0

else:
self.substructure_length = self.config["site"]["depth"] + 10

def calc_substructure_deck_space(self):
"""
Expand Down
Loading

0 comments on commit 5d728a1

Please sign in to comment.