diff --git a/ORBIT/api/wisdem.py b/ORBIT/api/wisdem.py index f0a80256..6147c937 100644 --- a/ORBIT/api/wisdem.py +++ b/ORBIT/api/wisdem.py @@ -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", @@ -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: diff --git a/ORBIT/core/defaults/process_times.yaml b/ORBIT/core/defaults/process_times.yaml index 4be5543d..01a1f9b5 100644 --- a/ORBIT/core/defaults/process_times.yaml +++ b/ORBIT/core/defaults/process_times.yaml @@ -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 diff --git a/ORBIT/core/library.py b/ORBIT/core/library.py index 6f771cc0..e33b4523 100644 --- a/ORBIT/core/library.py +++ b/ORBIT/core/library.py @@ -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): @@ -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", diff --git a/ORBIT/manager.py b/ORBIT/manager.py index d7b51bdf..303ecbaf 100644 --- a/ORBIT/manager.py +++ b/ORBIT/manager.py @@ -31,10 +31,12 @@ ArraySystemDesign, ExportSystemDesign, MooringSystemDesign, + SemiTautMooringSystemDesign, ScourProtectionDesign, SemiSubmersibleDesign, CustomArraySystemDesign, OffshoreSubstationDesign, + OffshoreFloatingSubstationDesign, ) from ORBIT.phases.install import ( JacketInstallation, @@ -69,7 +71,9 @@ class ProjectManager: ExportSystemDesign, ScourProtectionDesign, OffshoreSubstationDesign, + OffshoreFloatingSubstationDesign, MooringSystemDesign, + SemiTautMooringSystemDesign, SemiSubmersibleDesign, SparDesign, ElectricalDesign, diff --git a/ORBIT/phases/design/SemiTaut_mooring_system_design.py b/ORBIT/phases/design/SemiTaut_mooring_system_design.py new file mode 100644 index 00000000..e6db2abd --- /dev/null +++ b/ORBIT/phases/design/SemiTaut_mooring_system_design.py @@ -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__ = "jake.nunemaker@nrel.gov & rebecca.fuchs@nrel.gov" + +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} diff --git a/ORBIT/phases/design/__init__.py b/ORBIT/phases/design/__init__.py index 8ef543e0..7871e0ca 100644 --- a/ORBIT/phases/design/__init__.py +++ b/ORBIT/phases/design/__init__.py @@ -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 diff --git a/ORBIT/phases/design/electrical_export.py b/ORBIT/phases/design/electrical_export.py index daf83ed0..0e44f83c 100644 --- a/ORBIT/phases/design/electrical_export.py +++ b/ORBIT/phases/design/electrical_export.py @@ -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)", }, @@ -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]] @@ -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, @@ -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)), ) @@ -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 @@ -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 @@ -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): """ diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py index 383a4924..e14bb09c 100644 --- a/ORBIT/phases/design/mooring_system_design.py +++ b/ORBIT/phases/design/mooring_system_design.py @@ -1,13 +1,17 @@ """`MooringSystemDesign` and related functionality.""" -__author__ = "Jake Nunemaker" +__author__ = "Jake Nunemaker, Becca Fuchs" __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" -__maintainer__ = "Jake Nunemaker" -__email__ = "jake.nunemaker@nrel.gov" - +__maintainer__ = "Nicholas Riccobono" +__email__ = ( + "jake.nunemaker@nrel.gov, rebecca.fuchs@nrel.gov," + "nicholas.riccobono@nrel.gov" +) from math import sqrt +from scipy.interpolate import interp1d + from ORBIT.phases.design import DesignPhase @@ -21,8 +25,12 @@ class MooringSystemDesign(DesignPhase): "mooring_system_design": { "num_lines": "int | float (optional, default: 4)", "anchor_type": "str (optional, default: 'Suction Pile')", + "mooring_type": "str (optional, default: 'Catenary')", "mooring_line_cost_rate": "int | float (optional)", - "drag_embedment_fixed_length": "int | float (optional, default: .5km)", + "drag_embedment_fixed_length": "int (optional, default: 500m)", + "draft_depth": "int (optional, default: 20m)", + "chain_density": "int | float (optional, default: 19900 kg/m**3)", + "rope_density": "int | float (optional, default: 797.8 kg/m**3)", }, } @@ -33,9 +41,11 @@ class MooringSystemDesign(DesignPhase): "line_mass": "t", "line_cost": "USD", "line_length": "m", + "mooring_type": "str", "anchor_mass": "t", "anchor_type": "str", "anchor_cost": "USD", + "system_cost": "USD", } } @@ -51,10 +61,30 @@ def __init__(self, config, **kwargs): config = self.initialize_library(config, **kwargs) self.config = self.validate_config(config) self.num_turbines = self.config["plant"]["num_turbines"] + self.depth = self.config["site"]["depth"] 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", "Suction Pile") + self.mooring_type = self._design.get("mooring_type", "Catenary") + + # Semi-Taut mooring system design parameters based on depth + # Cooperman et al. (2022), https://www.nrel.gov/docs/fy22osti/82341.pdf + self._semitaut_params = { + "depths": [500.0, 750.0, 1000.0, 1250.0, 1500.0], + "rope_lengths": [478.41, 830.34, 1229.98, 1183.93, 1079.62], + "rope_diameter": 0.2, + "chain_lengths": [917.11, 800.36, 609.07, 896.42, 1280.57], + "chain_diameters": [0.13, 0.17, 0.22, 0.22, 0.22], + "anchor_costs": [112766.0, 125511.0, 148703.0, 204988.0, 246655.0], + "total_line_costs": [ + 826598.0, + 1221471.0, + 1682208.0, + 2380035.0, + 3229700.0, + ], + } self._outputs = {} @@ -73,25 +103,33 @@ def run(self): def determine_mooring_line(self): """ Returns the diameter of the mooring lines based on the turbine rating. + + TODO: Add TLP option and consider merging SemiTaut interp here """ tr = self.config["turbine"]["turbine_rating"] - fit = -0.0004 * (tr ** 2) + 0.0132 * tr + 0.0536 + fit = -0.0004 * (tr**2) + 0.0132 * tr + 0.0536 if fit <= 0.09: self.line_diam = 0.09 self.line_mass_per_m = 0.161 - self.line_cost_rate = 399.0 + self.line_cost_rate = self._design.get( + "mooring_line_cost_rate", 399.0 + ) elif fit <= 0.12: self.line_diam = 0.12 self.line_mass_per_m = 0.288 - self.line_cost_rate = 721.0 + self.line_cost_rate = self._design.get( + "mooring_line_cost_rate", 721.0 + ) else: self.line_diam = 0.15 self.line_mass_per_m = 0.450 - self.line_cost_rate = 1088.0 + self.line_cost_rate = self._design.get( + "mooring_line_cost_rate", 1088.0 + ) def calculate_breaking_load(self): """ @@ -99,26 +137,81 @@ def calculate_breaking_load(self): """ self.breaking_load = ( - 419449 * (self.line_diam ** 2) + 93415 * self.line_diam - 3577.9 + 419449 * (self.line_diam**2) + 93415 * self.line_diam - 3577.9 ) def calculate_line_length_mass(self): """ Returns the mooring line length and mass. + + SemiTaut model based on: + https://github.com/NREL/MoorPy/blob/dev/moorpy/MoorProps_default.yaml + TODO: Improve TLP line length and mass + """ + # Add extra fixed line length for drag embedments if self.anchor_type == "Drag Embedment": fixed = self._design.get("drag_embedment_fixed_length", 500) else: fixed = 0 - depth = self.config["site"]["depth"] - self.line_length = ( - 0.0002 * (depth ** 2) + 1.264 * depth + 47.776 + fixed - ) + draft = self._design.get("draft_depth", 20) + + if self.mooring_type == "SemiTaut": + + # Interpolation of rope and chain length at project depth + self.chain_length = interp1d( + self._semitaut_params["depths"], + self._semitaut_params["chain_lengths"], + fill_value="extrapolate", + )(self.depth).item() + self.rope_length = interp1d( + self._semitaut_params["depths"], + self._semitaut_params["rope_lengths"], + fill_value="extrapolate", + )(self.depth).item() + + # Rope and interpolated chain diameter at project depth + rope_diameter = self._semitaut_params["rope_diameter"] + chain_diameter = interp1d( + self._semitaut_params["depths"], + self._semitaut_params["chain_diameters"], + fill_value="extrapolate", + )(self.depth).item() + + fixed = self._design.get("drag_embedment_fixed_length", 0) + self.line_length = self.rope_length + self.chain_length + fixed + + # line characteristics based on MoorPy defaults, + chain_mass_per_m = ( + self._design.get("mooring_chain_density", 19900) + * chain_diameter**2 + ) # kg/m + rope_mass_per_m = ( + self._design.get("mooring_rope_density", 797.8) + * rope_diameter**2 + ) # kg/m + + self.line_mass = ( + self.chain_length * chain_mass_per_m + + self.rope_length * rope_mass_per_m + ) / 1e3 # tonnes + + elif self.mooring_type == "TLP": + + self.line_length = self.depth - draft + + self.line_mass = self.line_length * self.line_mass_per_m + + else: + + self.line_length = ( + 0.0002 * (self.depth**2) + 1.264 * self.depth + 47.776 + fixed + ) - self.line_mass = self.line_length * self.line_mass_per_m + self.line_mass = self.line_length * self.line_mass_per_m def calculate_anchor_mass_cost(self): """ @@ -126,21 +219,57 @@ def calculate_anchor_mass_cost(self): TODO: Anchor masses are rough estimates based on initial literature review. Should be revised when this module is overhauled in the future. + TODO: Mooring types for Catenary, TLP, SemiTaut will likely have + different anchors. """ - if self.anchor_type == "Drag Embedment": - self.anchor_mass = 20 - self.anchor_cost = self.breaking_load / 9.81 / 20.0 * 2000.0 + if self.mooring_type == "SemiTaut": + + if self.anchor_type == "Drag Embedment": + self.anchor_mass = 20 + + # Interpolation of anchor cost at project depth + self.anchor_cost = interp1d( + self._semitaut_params["depths"], + self._semitaut_params["anchor_costs"], + fill_value="extrapolate", + )(self.depth).item() + + else: + self.anchor_mass = 50 + self.anchor_cost = ( + sqrt(self.breaking_load / 9.81 / 1250) * 150000 + ) else: - self.anchor_mass = 50 - self.anchor_cost = sqrt(self.breaking_load / 9.81 / 1250) * 150000 + + if self.anchor_type == "Drag Embedment": + self.anchor_mass = 20 + self.anchor_cost = self.breaking_load / 9.81 / 20.0 * 2000.0 + + else: + self.anchor_mass = 50 + self.anchor_cost = ( + sqrt(self.breaking_load / 9.81 / 1250) * 150000 + ) @property def line_cost(self): """Returns cost of one line mooring line.""" - return self.line_length * self.line_cost_rate + if self.mooring_type == "SemiTaut": + # Interpolation of line cost at project depth + line_cost = interp1d( + self._semitaut_params["depths"], + self._semitaut_params["total_line_costs"], + fill_value="extrapolate", + )(self.depth).item() + + else: + + line_cost = self.line_length * self.line_cost_rate + + return line_cost @property def total_cost(self): @@ -149,7 +278,7 @@ def total_cost(self): return ( self.num_lines * self.num_turbines - * (self.anchor_cost + self.line_length * self.line_cost_rate) + * (self.anchor_cost + self.line_cost) ) @property @@ -162,6 +291,7 @@ def detailed_output(self): "line_mass": self.line_mass, "line_length": self.line_length, "line_cost": self.line_cost, + "mooring_type": self.mooring_type, "anchor_type": self.anchor_type, "anchor_mass": self.anchor_mass, "anchor_cost": self.anchor_cost, diff --git a/ORBIT/phases/design/oss_design_floating.py b/ORBIT/phases/design/oss_design_floating.py new file mode 100644 index 00000000..a1f384d3 --- /dev/null +++ b/ORBIT/phases/design/oss_design_floating.py @@ -0,0 +1,320 @@ +"""Provides the 'OffshoreSubstationDesign` class.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "Jake.Nunemaker@nrel.gov" + + +import numpy as np + +from ORBIT.phases.design import DesignPhase + + +class OffshoreFloatingSubstationDesign(DesignPhase): + """Offshore Substation Design Class.""" + + expected_config = { + "site": {"depth": "m"}, + "plant": {"num_turbines": "int"}, + "turbine": {"turbine_rating": "MW"}, + "substation_design": { + "mpt_cost_rate": "USD/MW (optional)", + "topside_fab_cost_rate": "USD/t (optional)", + "topside_design_cost": "USD (optional)", + "shunt_cost_rate": "USD/MW (optional)", + "switchgear_cost": "USD (optional)", + "backup_gen_cost": "USD (optional)", + "workspace_cost": "USD (optional)", + "other_ancillary_cost": "USD (optional)", + "topside_assembly_factor": "float (optional)", + "oss_substructure_cost_rate": "USD/t (optional)", + "oss_pile_cost_rate": "USD/t (optional)", + "num_substations": "int (optional)", + }, + } + + output_config = { + "num_substations": "int", + "offshore_substation_topside": "dict", + #"offshore_substation_substructure", "dict", + } + + def __init__(self, config, **kwargs): + """ + Creates an instance of OffshoreSubstationDesign. + + Parameters + ---------- + config : dict + """ + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self._outputs = {} + + def run(self): + """Main run function.""" + + self.calc_substructure_length() + self.calc_substructure_deck_space() + self.calc_topside_deck_space() + + self.calc_num_mpt_and_rating() + self.calc_mpt_cost() + self.calc_topside_mass_and_cost() + self.calc_shunt_reactor_cost() + self.calc_switchgear_cost() + self.calc_ancillary_system_cost() + self.calc_assembly_cost() + self.calc_substructure_mass_and_cost() + + self._outputs["offshore_substation_substructure"] = { + "type": "Floating", + "deck_space": self.substructure_deck_space, + "mass": self.substructure_mass, + "length": self.substructure_length, + "unit_cost": self.substructure_cost, + } + + self._outputs["offshore_substation_topside"] = { + "deck_space": self.topside_deck_space, + "mass": self.topside_mass, + "unit_cost": self.substation_cost, + } + + self._outputs["num_substations"] = self.num_substations + + @property + def substation_cost(self): + """Returns total procuremet cost of the topside.""" + + return ( + self.mpt_cost + + self.topside_cost + + self.shunt_reactor_cost + + self.switchgear_costs + + self.ancillary_system_costs + + self.land_assembly_cost + ) + + @property + def total_cost(self): + """Returns total procurement cost of the substation(s).""" + + if not self._outputs: + raise Exception("Has OffshoreSubstationDesign been ran yet?") + + return ( + self.substructure_cost + self.substation_cost + ) * self.num_substations + + def calc_substructure_length(self): + """ + Calculates substructure length as the site depth + 10m + """ + + self.substructure_length = self.config["site"]["depth"] + 10 + + def calc_substructure_deck_space(self): + """ + Calculates required deck space for the substation substructure. + + Coming soon! + """ + + self.substructure_deck_space = 1 + + def calc_topside_deck_space(self): + """ + Calculates required deck space for the substation topside. + + Coming soon! + """ + + self.topside_deck_space = 1 + + def calc_num_mpt_and_rating(self): + """ + Calculates the number of main power transformers (MPTs) and their rating. + + Parameters + ---------- + num_turbines : int + turbine_rating : float + """ + + _design = self.config.get("substation_design", {}) + + num_turbines = self.config["plant"]["num_turbines"] + turbine_rating = self.config["turbine"]["turbine_rating"] + capacity = num_turbines * turbine_rating + + self.num_substations = _design.get( + "num_substations", int(np.ceil(capacity / 500)) + ) + self.num_mpt = np.ceil( + num_turbines * turbine_rating / (250 * self.num_substations) + ) + self.mpt_rating = ( + round( + ( + (num_turbines * turbine_rating * 1.15) + / (self.num_mpt * self.num_substations) + ) + / 10.0 + ) + * 10.0 + ) + + def calc_mpt_cost(self): + """ + Calculates the total cost for all MPTs. + + Parameters + ---------- + mpt_cost_rate : float + """ + + _design = self.config.get("substation_design", {}) + mpt_cost_rate = _design.get("mpt_cost_rate", 12500) + + self.mpt_cost = self.mpt_rating * self.num_mpt * mpt_cost_rate + + def calc_topside_mass_and_cost(self): + """ + Calculates the mass and cost of the substation topsides. + + Parameters + ---------- + topside_fab_cost_rate : int | float + topside_design_cost: int | float + """ + + _design = self.config.get("substation_design", {}) + topside_fab_cost_rate = _design.get("topside_fab_cost_rate", 14500) + topside_design_cost = _design.get("topside_design_cost", 4.5e6) + + self.topside_mass = 3.85 * self.mpt_rating * self.num_mpt + 285 + self.topside_cost = ( + self.topside_mass * topside_fab_cost_rate + topside_design_cost + ) + + def calc_shunt_reactor_cost(self): + """ + Calculates the cost of the shunt reactor. + + Parameters + ---------- + shunt_cost_rate : int | float + """ + + _design = self.config.get("substation_design", {}) + shunt_cost_rate = _design.get("shunt_cost_rate", 35000) + + self.shunt_reactor_cost = ( + self.mpt_rating * self.num_mpt * shunt_cost_rate * 0.5 + ) + + def calc_switchgear_cost(self): + """ + Calculates the cost of the switchgear. + + Parameters + ---------- + switchgear_cost : int | float + """ + + _design = self.config.get("substation_design", {}) + switchgear_cost = _design.get("switchgear_cost", 14.5e5) + + self.switchgear_costs = self.num_mpt * switchgear_cost + + def calc_ancillary_system_cost(self): + """ + Calculates cost of ancillary systems. + + Parameters + ---------- + backup_gen_cost : int | float + workspace_cost : int | float + other_ancillary_cost : int | float + """ + + _design = self.config.get("substation_design", {}) + backup_gen_cost = _design.get("backup_gen_cost", 1e6) + workspace_cost = _design.get("workspace_cost", 2e6) + other_ancillary_cost = _design.get("other_ancillary_cost", 3e6) + + self.ancillary_system_costs = ( + backup_gen_cost + workspace_cost + other_ancillary_cost + ) + + def calc_assembly_cost(self): + """ + Calculates the cost of assembly on land. + + Parameters + ---------- + topside_assembly_factor : int | float + """ + + _design = self.config.get("substation_design", {}) + topside_assembly_factor = _design.get("topside_assembly_factor", 0.075) + self.land_assembly_cost = ( + self.switchgear_costs + + self.shunt_reactor_cost + + self.ancillary_system_costs + ) * topside_assembly_factor + + def calc_substructure_mass_and_cost(self): + """ + Calculates the mass and associated cost of the substation substructure. + + Parameters + ---------- + oss_substructure_cost_rate : int | float + oss_pile_cost_rate : int | float + """ + + _design = self.config.get("substation_design", {}) + oss_substructure_cost_rate = _design.get( + "oss_substructure_cost_rate", 3000 + ) + oss_pile_cost_rate = _design.get("oss_pile_cost_rate", 0) + + substructure_mass = 0.4 * self.topside_mass + #substructure_pile_mass = 8 * substructure_mass ** 0.5574 + substructure_pile_mass = 0 # the monopiles are no longer needed because there is s mooring system + self.substructure_cost = ( + substructure_mass * oss_substructure_cost_rate + + substructure_pile_mass * oss_pile_cost_rate + ) + #print('substructure cost:' + str(self.substructure_cost)) + self.substructure_mass = substructure_mass + substructure_pile_mass + + @property + def design_result(self): + """ + Returns the results of self.run(). + """ + + if not self._outputs: + raise Exception("Has OffshoreSubstationDesign been ran yet?") + + return self._outputs + + @property + def detailed_output(self): + """Returns detailed phase information.""" + + _outputs = { + "num_substations": self.num_substations, + "substation_mpt_rating": self.mpt_rating, + "substation_topside_mass": self.topside_mass, + "substation_topside_cost": self.topside_cost, + "substation_substructure_mass": self.substructure_mass, + "substation_substructure_cost": self.substructure_cost, + } + + return _outputs diff --git a/ORBIT/phases/install/cable_install/array.py b/ORBIT/phases/install/cable_install/array.py index d4d8a181..5fbb5923 100644 --- a/ORBIT/phases/install/cable_install/array.py +++ b/ORBIT/phases/install/cable_install/array.py @@ -10,7 +10,6 @@ import numpy as np from marmot import process - from ORBIT.core import Vessel from ORBIT.core.logic import position_onsite from ORBIT.phases.install import InstallPhase @@ -237,7 +236,6 @@ def install_array_cables( trench_vessel.at_site = True elif trench_vessel.at_site: - try: # Dig trench along each cable section distance trench_distance = trench_sections.pop(0) @@ -269,7 +267,6 @@ def install_array_cables( vessel.at_site = True elif vessel.at_site: - try: length, num_sections, *extra = sections.pop(0) if extra: @@ -291,12 +288,10 @@ def install_array_cables( break for _ in range(num_sections): - try: section = vessel.cable_storage.get_cable(length) except InsufficientCable: - yield vessel.transit(distance, **kwargs) yield load_cable_on_vessel(vessel, cable, **kwargs) yield vessel.transit(distance, **kwargs) @@ -330,11 +325,6 @@ def install_array_cables( vessel, installed, total_cable_length, breakpoints ) - # Transit back to port - vessel.at_site = False - yield vessel.transit(distance, **kwargs) - vessel.at_port = True - ## Burial Process if burial_vessel is None: vessel.submit_debug_log( diff --git a/ORBIT/phases/install/cable_install/common.py b/ORBIT/phases/install/cable_install/common.py index f2481138..483e9718 100644 --- a/ORBIT/phases/install/cable_install/common.py +++ b/ORBIT/phases/install/cable_install/common.py @@ -7,7 +7,6 @@ from marmot import process - from ORBIT.core.logic import position_onsite from ORBIT.core.defaults import process_times as pt @@ -176,8 +175,7 @@ def lay_bury_cable(vessel, distance, **kwargs): kwargs = {**kwargs, **getattr(vessel, "_transport_specs", {})} - key = "cable_lay_bury_speed" - lay_bury_speed = kwargs.get(key, pt[key]) + lay_bury_speed = vessel._vessel_specs.get("cable_lay_bury_speed", 0.0625) lay_bury_time = distance / lay_bury_speed yield vessel.task_wrapper( diff --git a/ORBIT/phases/install/install_phase.py b/ORBIT/phases/install/install_phase.py index 97b93c3b..0fbad57a 100644 --- a/ORBIT/phases/install/install_phase.py +++ b/ORBIT/phases/install/install_phase.py @@ -12,7 +12,6 @@ import numpy as np import simpy import pandas as pd - from ORBIT.core import Port, Vessel, Environment from ORBIT.phases import BasePhase from ORBIT.core.defaults import common_costs @@ -121,9 +120,14 @@ def port_costs(self): return 0 else: - key = "port_cost_per_month" port_config = self.config.get("port", {}) + assert port_config.get("sub_assembly_lines", 1) == port_config.get( + "turbine_assembly_cranes", 1 + ), "Number of substructure assembly lines is not equal to number of turbine assembly cranes" + + key = "port_cost_per_month" rate = port_config.get("monthly_rate", common_costs[key]) + rate += rate * (port_config.get("sub_assembly_lines", 1) - 1) * 0.5 months = self.total_phase_time / (8760 / 12) return months * rate diff --git a/ORBIT/phases/install/jacket_install/standard.py b/ORBIT/phases/install/jacket_install/standard.py index 2f8f0c55..95fb7402 100644 --- a/ORBIT/phases/install/jacket_install/standard.py +++ b/ORBIT/phases/install/jacket_install/standard.py @@ -480,7 +480,7 @@ def install_jackets_from_queue( start = wtiv.env.now yield queue.activate delay_time = wtiv.env.now - start - wtiv.submit_action_log("Delay", delay_time, location="Site") + wtiv.submit_action_log("Delay: Not enough vessels for jackets", delay_time, location="Site") # Transit to port wtiv.at_site = False diff --git a/ORBIT/phases/install/monopile_install/standard.py b/ORBIT/phases/install/monopile_install/standard.py index 5a204709..47ae2923 100644 --- a/ORBIT/phases/install/monopile_install/standard.py +++ b/ORBIT/phases/install/monopile_install/standard.py @@ -14,7 +14,7 @@ from ORBIT.core.logic import ( prep_for_site_operations, shuttle_items_to_queue_wait, - get_list_of_items_from_port_wait, + get_list_of_items_from_port, ) from ORBIT.phases.install import InstallPhase from ORBIT.core.exceptions import ItemNotFound @@ -326,7 +326,7 @@ def solo_install_monopiles(vessel, port, distance, monopiles, **kwargs): if vessel.at_port: try: # Get substructure + transition piece from port - yield get_list_of_items_from_port_wait( + yield get_list_of_items_from_port( vessel, port, component_list, **kwargs ) @@ -441,7 +441,7 @@ def install_monopiles_from_queue(wtiv, queue, monopiles, distance, **kwargs): start = wtiv.env.now yield queue.activate delay_time = wtiv.env.now - start - wtiv.submit_action_log("Delay", delay_time, location="Site") + wtiv.submit_action_log("Delay: Not enough vessels for monopiles", delay_time, location="Site") # Transit to port wtiv.at_site = False diff --git a/ORBIT/phases/install/mooring_install/mooring.py b/ORBIT/phases/install/mooring_install/mooring.py index 3b3573b9..a8997f39 100644 --- a/ORBIT/phases/install/mooring_install/mooring.py +++ b/ORBIT/phases/install/mooring_install/mooring.py @@ -319,26 +319,3 @@ def release(**kwargs): """Dummy method to work with `get_list_of_items_from_port`.""" return "", 0 - - def anchor_install_time(self, depth): - """ - Returns time to install anchor. Varies by depth. - - Parameters - ---------- - depth : int | float - Depth at site (m). - """ - - if self.anchor_type == "Suction Pile": - fixed = 11 - - elif self.anchor_type == "Drag Embedment": - fixed = 5 - - else: - raise ValueError( - f"Mooring System Anchor Type: {self.anchor_type} not recognized." - ) - - return fixed + 0.005 * depth diff --git a/ORBIT/phases/install/oss_install/floating.py b/ORBIT/phases/install/oss_install/floating.py index c2d1ca2b..1170ad7e 100644 --- a/ORBIT/phases/install/oss_install/floating.py +++ b/ORBIT/phases/install/oss_install/floating.py @@ -6,7 +6,9 @@ __email__ = "jake.nunemaker@nrel.gov" -from marmot import Agent, process, le +from warnings import warn + +from marmot import Agent, le, process from marmot._exceptions import AgentNotRegistered from ORBIT.core import WetStorage @@ -25,7 +27,7 @@ class FloatingSubstationInstallation(InstallPhase): and tow-out processes. """ - phase = "Offshore Substation Installation" + phase = "Offshore Floating Substation Installation" capex_category = "Offshore Substation" #: @@ -38,12 +40,19 @@ class FloatingSubstationInstallation(InstallPhase): "attach_time": "int | float (optional, default: 24)", }, "offshore_substation_substructure": { - "type": "Floating", + "type": "str", "takt_time": "int | float (optional, default: 0)", "unit_cost": "USD", - "mooring_cost": "USD", "towing_speed": "int | float (optional, default: 6 km/h)", }, + "mooring_system": { + "num_lines", + "int", + "line_cost", + "USD", + "anchor_cost", + "USD", + }, } def __init__(self, config, weather=None, **kwargs): @@ -64,9 +73,9 @@ def __init__(self, config, weather=None, **kwargs): self.config = self.validate_config(config) self.initialize_port() - self.setup_simulation(**kwargs) + self.setup_simulation() - def setup_simulation(self, **kwargs): + def setup_simulation(self): """ Initializes required objects for simulation. - Creates port @@ -89,11 +98,17 @@ def system_capex(self): substructure = self.config["offshore_substation_substructure"][ "unit_cost" ] - mooring = self.config["offshore_substation_substructure"][ - "mooring_cost" - ] + # mooring system + num_mooring_lines = self.config["mooring_system"]["num_lines"] + line_cost = self.config["mooring_system"]["line_cost"] + anchor_cost = self.config["mooring_system"]["anchor_cost"] + mooring_system_for_each_oss = num_mooring_lines * ( + line_cost + anchor_cost + ) - return self.num_substations * (topside + substructure + mooring) + return self.num_substations * ( + topside + substructure + mooring_system_for_each_oss + ) def initialize_substructure_production(self): """ @@ -101,6 +116,16 @@ def initialize_substructure_production(self): quayside. """ + substructure_type = self.config["offshore_substation_substructure"][ + "type" + ] + + if substructure_type != "Floating": + warn( + f"Offshore substation substructure is {substructure_type}" + " and should be 'Floating'.\n" + ) + self.wet_storage = WetStorage(self.env, float("inf")) takt_time = self.config["offshore_substation_substructure"].get( "takt_time", 0 @@ -144,7 +169,6 @@ def initialize_installation_vessel(self): @property def detailed_output(self): - return {} @@ -175,13 +199,12 @@ def install_floating_substations( travel_time = distance / towing_speed for _ in range(number): - start = vessel.env.now yield feed.get() delay = vessel.env.now - start if delay > 0: vessel.submit_action_log( - "Delay: Waiting on Completed Assembly", delay + "Delay: Waiting on Substation Assembly", delay ) yield vessel.task( @@ -196,7 +219,7 @@ def install_floating_substations( constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) - for _ in range (3): + for _ in range(3): yield perform_mooring_site_survey(vessel) yield install_mooring_anchor(vessel, depth, "Suction Pile") yield install_mooring_line(vessel, depth) @@ -215,6 +238,12 @@ def install_floating_substations( yield vessel.transit(distance) + # TODO: Revise this to work with multiple substations + vessel.submit_debug_log( + message="Substation installation complete!", + progress="Offshore Substation", + ) + class SubstationAssemblyLine(Agent): """Substation Assembly Line Class.""" @@ -294,7 +323,9 @@ def assemble_substructure(self): delay = self.env.now - start if delay > 0: - self.submit_action_log("Delay: No Wet Storage Available", delay) + self.submit_action_log( + "Delay: No Substructure Storage Available", delay + ) @process def start(self): diff --git a/ORBIT/phases/install/oss_install/standard.py b/ORBIT/phases/install/oss_install/standard.py index 43bd62af..f931bc24 100644 --- a/ORBIT/phases/install/oss_install/standard.py +++ b/ORBIT/phases/install/oss_install/standard.py @@ -9,7 +9,6 @@ import simpy from marmot import process -from ORBIT.core import Vessel from ORBIT.core.logic import shuttle_items_to_queue, prep_for_site_operations from ORBIT.phases.install import InstallPhase from ORBIT.phases.install.monopile_install.common import ( @@ -48,7 +47,7 @@ class OffshoreSubstationInstallation(InstallPhase): "unit_cost": "USD", }, "offshore_substation_substructure": { - "type": "Monopile", + "type": "str", "deck_space": "m2", "mass": "t", "length": "m", @@ -257,7 +256,11 @@ def install_oss_from_queue(vessel, queue, substations, distance, **kwargs): start = vessel.env.now yield queue.activate delay_time = vessel.env.now - start - vessel.submit_action_log("Delay", delay_time, location="Site") + vessel.submit_action_log( + "Delay: Not enough vessels for oss", + delay_time, + location="Site", + ) # Transit to port vessel.at_site = False diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index 252965b7..4083cc36 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -7,7 +7,7 @@ __email__ = "jake.nunemaker@nrel.gov" -from marmot import Agent, le, process +from marmot import Agent, le, false, process from marmot._exceptions import AgentNotRegistered @@ -94,7 +94,9 @@ def assemble_substructure(self): delay = self.env.now - start if delay > 0: - self.submit_action_log("Delay: No Wet Storage Available", delay) + self.submit_action_log( + "Delay: No Substructure Storage Available", delay + ) @process def start(self): @@ -213,6 +215,8 @@ def assemble_turbine(self): yield self.mechanical_completion() + yield self.electrical_completion() + start = self.env.now yield self.target.put(1) delay = self.env.now - start @@ -235,7 +239,7 @@ def move_substructure(self): TODO: Move to dynamic process involving tow groups. """ - yield self.task("Move Substructure", 8) + yield self.task("Move Substructure", 8, {"port_in_use": false()}) @process def prepare_for_assembly(self): @@ -255,7 +259,7 @@ def lift_and_attach_tower_section(self): yield self.task( "Lift and Attach Tower Section", - 12, + 4, constraints={"windspeed": le(15)}, ) @@ -267,7 +271,7 @@ def lift_and_attach_nacelle(self): """ yield self.task( - "Lift and Attach Nacelle", 7, constraints={"windspeed": le(15)} + "Lift and Attach Nacelle", 12, constraints={"windspeed": le(15)} ) @process @@ -292,11 +296,22 @@ def mechanical_completion(self): "Mechanical Completion", 24, constraints={"windspeed": le(18)} ) + @process + def electrical_completion(self): + """ + Task representing time associated with performing electrical completion + work at quayside, including precommissioning. Assumes the tower is delivered to port in multiple sections, requiring cable pull-in after tower assembly. + """ + + yield self.task( + "Electrical Completion", 72, constraints={"windspeed": le(18)} + ) + class TowingGroup(Agent): """Class to represent an arbitrary group of towing vessels.""" - def __init__(self, vessel_specs, num=1): + def __init__(self, vessel_specs, ahts_vessel_specs=None, num=1): """ Creates an instance of TowingGroup. @@ -305,13 +320,37 @@ def __init__(self, vessel_specs, num=1): vessel_specs : dict Specs for the individual vessels used in the towing group. Currently restricted to one vessel specification per group. + num : int + Towing group number + ahts_vessel_specs : dict + Specs for the anchor hndling tug vessel. """ super().__init__(f"Towing Group {num}") self._specs = vessel_specs - self.day_rate = self._specs["vessel_specs"]["day_rate"] + self.day_rate_towing = self._specs["vessel_specs"]["day_rate"] + self.day_rate_anchor = 0.0 + self.max_waveheight = self._specs["transport_specs"]["max_waveheight"] + self.max_windspeed = self._specs["transport_specs"]["max_windspeed"] self.transit_speed = self._specs["transport_specs"]["transit_speed"] + if ahts_vessel_specs is not None: + self.day_rate_anchor = ahts_vessel_specs["vessel_specs"][ + "day_rate" + ] + self.max_waveheight = min( + vessel_specs["transport_specs"]["max_waveheight"], + ahts_vessel_specs["transport_specs"]["max_waveheight"], + ) + self.max_windspeed = min( + vessel_specs["transport_specs"]["max_windspeed"], + ahts_vessel_specs["transport_specs"]["max_windspeed"], + ) + self.transit_speed = min( + vessel_specs["transport_specs"]["transit_speed"], + ahts_vessel_specs["transport_specs"]["transit_speed"], + ) + def initialize(self): """Initializes the towing group.""" @@ -319,7 +358,13 @@ def initialize(self): @process def group_task( - self, name, duration, num_vessels, constraints={}, **kwargs + self, + name, + duration, + num_vessels, + num_ahts_vessels=0, + constraints={}, + **kwargs, ): """ Submits a group task with any number of towing vessels. @@ -333,9 +378,12 @@ def group_task( Rounded up to the nearest int. num_vessels : int Number of individual towing vessels needed for the operation. + num_ahts_vessels : int + Number of anchor handling tug vessels used for the operation. """ kwargs = {**kwargs, "num_vessels": num_vessels} + kwargs = {**kwargs, "num_ahts_vessels": num_ahts_vessels} yield self.task(name, duration, constraints=constraints, **kwargs) def operation_cost(self, hours, **kwargs): @@ -352,8 +400,16 @@ def operation_cost(self, hours, **kwargs): """ mult = kwargs.get("cost_multiplier", 1.0) - vessels = kwargs.get("num_vessels", 1) - return (self.day_rate / 24) * vessels * hours * mult + num_towing_vessels = kwargs.get("num_vessels", 1) + num_ahts_vessels = kwargs.get("num_ahts_vessels", 0) + return ( + ( + (self.day_rate_towing / 24) * num_towing_vessels + + (self.day_rate_anchor / 24) * num_ahts_vessels + ) + * hours + * mult + ) def submit_action_log(self, action, duration, **kwargs): """ diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index 4cbd97f6..1e80b1fc 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -5,6 +5,7 @@ __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" +from warnings import warn import simpy from marmot import le, process @@ -26,11 +27,13 @@ class GravityBasedInstallation(InstallPhase): #: expected_config = { - "support_vessel": "str", + "support_vessel": "str, (optional)", + "ahts_vessel": "str", "towing_vessel": "str", "towing_vessel_groups": { "towing_vessels": "int", - "station_keeping_vessels": "int", + "station_keeping_vessels": "int (optional)", + "ahts_vessels": "int (optional, default: 1)", "num_groups": "int (optional)", }, "substructure": { @@ -184,7 +187,7 @@ def initialize_towing_groups(self, **kwargs): towing_speed = self.config["substructure"].get("towing_speed", 6) for i in range(num_groups): - g = TowingGroup(vessel, num=i + 1) + g = TowingGroup(vessel, None, num=i + 1) self.env.register(g) g.initialize() self.installation_groups.append(g) @@ -211,20 +214,48 @@ def initialize_queue(self): def initialize_support_vessel(self, **kwargs): """ + ** The support vessel is deprecated and an AHTS + vessel will perform the installation with the towing group. + # TODO: determine if the installation process for GBF is still + sound. + Initializes Multi-Purpose Support Vessel to perform installation processes at site. """ - specs = self.config["support_vessel"] - vessel = self.initialize_vessel("Multi-Purpose Support Vessel", specs) + specs = self.config.get("support_vessel", None) + + if specs is not None: + warn( + "support_vessel will be deprecated and replaced with" + " towing_vessels and ahts_vessel in the towing groups.\n", + DeprecationWarning, + stacklevel=2, + ) + + specs = self.config["ahts_vessel"] + vessel = self.initialize_vessel("Multi-Purpose AHTS Vessel", specs) self.env.register(vessel) vessel.initialize(mobilize=False) self.support_vessel = vessel - station_keeping_vessels = self.config["towing_vessel_groups"][ - "station_keeping_vessels" - ] + station_keeping_vessels = self.config["towing_vessel_groups"].get( + "station_keeping_vessels", None + ) + + if station_keeping_vessels is not None: + warn( + "['towing_vessl_groups]['station_keeping_vessels']" + " will be deprecated and replaced with" + " ['towing_vessl_groups]['ahts_vessels'].\n", + DeprecationWarning, + stacklevel=2, + ) + + station_keeping_vessels = self.config["towing_vessel_groups"].get( + "ahts_vessels", 1 + ) install_gravity_base_foundations( self.support_vessel, @@ -293,18 +324,21 @@ def transfer_gbf_substructures_from_storage( transit_time = distance / group.transit_speed while True: - start = group.env.now assembly = yield feed.get() delay = group.env.now - start if delay > 0: group.submit_action_log( - "Delay: No Completed Assemblies Available", delay + "Delay: No Completed Assemblies Available", + delay, + num_vessels=towing_vessels, ) yield group.group_task( - "Tow Substructure", towing_time, num_vessels=towing_vessels + "Tow Substructure", + towing_time, + num_vessels=towing_vessels, ) # At Site @@ -314,7 +348,12 @@ def transfer_gbf_substructures_from_storage( queue_time = group.env.now - queue_start if queue_time > 0: - group.submit_action_log("Queue", queue_time, location="Site") + group.submit_action_log( + "Queue", + queue_time, + location="Site", + num_vessels=towing_vessels, + ) queue.vessel = group active_start = group.env.now @@ -357,7 +396,6 @@ def install_gravity_base_foundations( n = 0 while n < substructures: if queue.vessel: - start = vessel.env.now if n == 0: vessel.mobilize() @@ -407,6 +445,10 @@ def install_gravity_base_foundations( delay_time = vessel.env.now - start if n != 0: - vessel.submit_action_log("Delay", delay_time, location="Site") + vessel.submit_action_log( + "Delay: Not enough vessels for gravity foundations", + delay_time, + location="Site", + ) yield vessel.transit(distance) diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index c38908b2..916a09fc 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -6,10 +6,12 @@ __email__ = "jake.nunemaker@nrel.gov" +from warnings import warn + import simpy from marmot import le, process -from ORBIT.core import Vessel, WetStorage +from ORBIT.core import WetStorage from ORBIT.phases.install import InstallPhase from .common import TowingGroup, TurbineAssemblyLine, SubstructureAssemblyLine @@ -26,11 +28,13 @@ class MooredSubInstallation(InstallPhase): #: expected_config = { - "support_vessel": "str", + "support_vessel": "str, (optional)", + "ahts_vessel": "str", "towing_vessel": "str", "towing_vessel_groups": { "towing_vessels": "int", - "station_keeping_vessels": "int", + "station_keeping_vessels": "int (optional)", + "ahts_vessels": "int (optional, default: 1)", "num_groups": "int (optional)", }, "substructure": { @@ -68,9 +72,9 @@ def __init__(self, config, weather=None, **kwargs): config = self.initialize_library(config, **kwargs) self.config = self.validate_config(config) - self.setup_simulation(**kwargs) + self.setup_simulation() - def setup_simulation(self, **kwargs): + def setup_simulation(self): """ Sets up simulation infrastructure. - Initializes substructure production @@ -177,24 +181,37 @@ def initialize_towing_groups(self, **kwargs): self.installation_groups = [] - vessel = self.config["towing_vessel"] + towing_vessel = self.config["towing_vessel"] num_groups = self.config["towing_vessel_groups"].get("num_groups", 1) - towing = self.config["towing_vessel_groups"]["towing_vessels"] + num_towing = self.config["towing_vessel_groups"]["towing_vessels"] towing_speed = self.config["substructure"].get("towing_speed", 6) + ahts_vessel = self.config.get("ahts_vessel", None) + num_ahts = self.config["towing_vessel_groups"].get("ahts_vessels", 1) + + if ahts_vessel is None: + warn( + "No ['ahts_vessel'] specified. num_ahts set to 0." + " ahts_vessel will be required in future releases.\n" + ) + num_ahts = 0 + + remaining_substructures = [1] * self.num_turbines + for i in range(num_groups): - g = TowingGroup(vessel, num=i + 1) + g = TowingGroup(towing_vessel, ahts_vessel, i + 1) self.env.register(g) g.initialize() self.installation_groups.append(g) - transfer_moored_substructures_from_storage( + transfer_install_moored_substructures_from_storage( g, self.assembly_storage, self.distance, - self.active_group, - towing, + num_towing, + num_ahts, towing_speed, + remaining_substructures, **kwargs, ) @@ -208,35 +225,57 @@ def initialize_queue(self): self.active_group.vessel = None self.active_group.activate = self.env.event() - def initialize_support_vessel(self, **kwargs): + def initialize_support_vessel(self): """ + ** DEPRECATED ** The support vessel is deprecated and an AHTS + vessel will perform the installation with the towing group. + Initializes Multi-Purpose Support Vessel to perform installation processes at site. """ - specs = self.config["support_vessel"] - vessel = self.initialize_vessel("Multi-Purpose Support Vessel", specs) + specs = self.config.get("support_vessel", None) - self.env.register(vessel) - vessel.initialize(mobilize=False) - self.support_vessel = vessel + if specs is not None: + warn( + "support_vessel will be deprecated and replaced with" + " towing_vessels and ahts_vessel in the towing groups.\n", + DeprecationWarning, + stacklevel=2, + ) + + # vessel = self.initialize_vessel("Multi-Purpose Support Vessel", + # specs) - station_keeping_vessels = self.config["towing_vessel_groups"][ - "station_keeping_vessels" - ] + # self.env.register(vessel) + # vessel.initialize(mobilize=False) + # self.support_vessel = vessel - install_moored_substructures( - self.support_vessel, - self.active_group, - self.distance, - self.num_turbines, - station_keeping_vessels, - **kwargs, + station_keeping_vessels = self.config["towing_vessel_groups"].get( + "station_keeping_vessels", None ) + if station_keeping_vessels is not None: + warn( + "['towing_vessl_groups]['station_keeping_vessels']" + " will be deprecated and replaced with" + " ['towing_vessl_groups]['ahts_vessels'].\n", + DeprecationWarning, + stacklevel=2, + ) + + # install_moored_substructures( + # self.support_vessel, + # self.active_group, + # self.distance, + # self.num_turbines, + # station_keeping_vessels, + # **kwargs, + # ) + @property def detailed_output(self): - """""" + """return detailed outputs.""" return { "operational_delays": { @@ -252,14 +291,14 @@ def detailed_output(self): k: self.operational_delay(str(k)) for k in self.installation_groups }, - self.support_vessel: self.operational_delay( - str(self.support_vessel) - ), + # self.support_vessel: self.operational_delay( + # str(self.support_vessel) + # ), } } def operational_delay(self, name): - """""" + """return operational delays""" actions = [a for a in self.env.actions if a["agent"] == name] delay = sum(a["duration"] for a in actions if "Delay" in a["action"]) @@ -268,11 +307,50 @@ def operational_delay(self, name): @process -def transfer_moored_substructures_from_storage( - group, feed, distance, queue, towing_vessels, towing_speed, **kwargs +def transfer_install_moored_substructures_from_storage( + group, + feed, + distance, + towing_vessels, + ahts_vessels, + towing_speed, + remaining_substructures, + **kwargs, ): """ - Process logic for the towing vessel group. + Trigger the substructure installtions. Shuts down after + self.remaining_substructures is empty. + """ + + while True: + try: + _ = remaining_substructures.pop(0) + yield towing_group_actions( + group, + feed, + distance, + towing_vessels, + ahts_vessels, + towing_speed, + **kwargs, + ) + + except IndexError: + break + + +@process +def towing_group_actions( + group, + feed, + distance, + towing_vessels, + ahts_vessels, + towing_speed, +): + """ + Process logic for the towing vessel group. Assumes there is an + anchor tug boat with each group Parameters ---------- @@ -284,69 +362,107 @@ def transfer_moored_substructures_from_storage( Distance from port to site. towing_vessels : int Number of vessels to use for towing to site. + ahts_vessels : int + Number of anchor handling tug vessels. towing_speed : int | float - Configured towing speed (km/h) + Configured towing speed (km/h). """ towing_time = distance / towing_speed transit_time = distance / group.transit_speed - while True: - - start = group.env.now - assembly = yield feed.get() - delay = group.env.now - start - - if delay > 0: - group.submit_action_log( - "Delay: No Completed Assemblies Available", delay - ) - - yield group.group_task( - "Ballast to Towing Draft", - 6, - num_vessels=towing_vessels, - constraints={"windspeed": le(15), "waveheight": le(2.5)}, - ) + start = group.env.now + _ = yield feed.get() + delay = group.env.now - start - yield group.group_task( - "Tow Substructure", - towing_time, + if delay > 0: + group.submit_action_log( + "Delay: No Completed Turbine Assemblies", + delay, num_vessels=towing_vessels, - constraints={"windspeed": le(15), "waveheight": le(2.5)}, + num_ahts_vessels=ahts_vessels, ) - # At Site - with queue.request() as req: - queue_start = group.env.now - yield req - - queue_time = group.env.now - queue_start - if queue_time > 0: - group.submit_action_log("Queue", queue_time, location="Site") - - queue.vessel = group - active_start = group.env.now - queue.activate.succeed() - - # Released by WTIV when objects are depleted - group.release = group.env.event() - yield group.release - active_time = group.env.now - active_start - - queue.vessel = None - queue.activate = group.env.event() - - yield group.group_task( - "Transit", transit_time, num_vessels=towing_vessels - ) + yield group.group_task( + "Ballast to Towing Draft", + 6, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + constraints={ + "windspeed": le(group.max_windspeed), + "waveheight": le(group.max_waveheight), + }, + ) + + yield group.group_task( + "Tow Substructure", + towing_time, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + constraints={ + "windspeed": le(group.max_windspeed), + "waveheight": le(group.max_waveheight), + }, + ) + + # At Site + yield group.group_task( + "Position Substructure", + 2, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, + ) + + yield group.group_task( + "Ballast to Operational Draft", + 6, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, + ) + + yield group.group_task( + "Connect Mooring Lines, Pre-tension and pre-stretch", + 20, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + suspendable=True, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, + ) + + yield group.group_task( + "Check Mooring Lines", + 6, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + suspendable=True, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, + ) + + group.submit_debug_log(progress="Substructure") + group.submit_debug_log(progress="Turbine") + + yield group.group_task( + "Transit", + transit_time, + num_vessels=towing_vessels, + num_ahts_vessels=ahts_vessels, + suspendable=True, + constraints={ + "windspeed": le(group.max_windspeed), + "waveheight": le(group.max_waveheight), + }, + ) @process def install_moored_substructures( - vessel, queue, distance, substructures, station_keeping_vessels, **kwargs + vessel, queue, distance, substructures, station_keeping_vessels ): """ + ** DEPRECATED ** This method is deprecated and is now performed + in towing_group_action() by the towing group with AHTS vessel. Logic that a Multi-Purpose Support Vessel uses at site to complete the installation of moored substructures. @@ -363,6 +479,11 @@ def install_moored_substructures( installation at site. """ + warn( + "** DEPRECATED ** This method is deprecated and is now performed" + " in towing_group_action() by the towing group with AHTS vessel.\n" + ) + n = 0 while n < substructures: if queue.vessel: diff --git a/ORBIT/phases/install/turbine_install/standard.py b/ORBIT/phases/install/turbine_install/standard.py index d23515a1..c05e3088 100644 --- a/ORBIT/phases/install/turbine_install/standard.py +++ b/ORBIT/phases/install/turbine_install/standard.py @@ -458,7 +458,7 @@ def install_turbine_components_from_queue( start = wtiv.env.now yield queue.activate delay_time = wtiv.env.now - start - wtiv.submit_action_log("Delay", delay_time, location="Site") + wtiv.submit_action_log("Delay: Not enough vessels for turbines", delay_time, location="Site") # Transit to port wtiv.at_site = False diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 0b8e1003..914f37a6 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,7 +5,21 @@ ORBIT Changelog Unreleased (TBD) ---------------- +- merged SemiTaut_Mooring_Update +- The ``MooringSystemDesign`` module now can use a Catenary or SemiTaut mooring system. User can specify "mooring_type". +- The ``FloatingOffshoreSubstation`` and ``ElectricalDesign`` modules now actually have a floating option to remove any substructure mass (and cost) from older versions. User can specify "oss_substructure_type" +- The ``MoredSubInstallation`` now utilizes an AHTS vessel which must be added to any config file as (ahts_vessel) +- "drag_embedment_install_time" increased from 5 to 12 hours +- quayside turbine tower section lift time from 12 to 4 hours per section. User specifies number of sections (default =1) +- quayside nacelle lift time changed from 7 to 12 hours +- XLPE_500mm_132kV cost_per_km changed from 200k to 500k +- example_cable_lay_vessel min_draft changed from 4.8m to 8.5m, overall_length 99m to 171m, max_mass 4000t to 13000t +- example_towing_vessel max_waveheight changed from 2.5m to 3.0m, max_windspeed 20m to 15m, transit_speed 6km/h to 14 km/h, day_rate 30k to 35k + +Unreleased (TBD) +---------------- +- merged electrical-refactor - Updated ``ElectricalDesign`` module. This class combines the elements of ``ExportSystemDesign`` and the ``OffshoreSubstationDesign`` modules. Its purpose is to represent the export system more accurately by linking the type of cable (AC versus DC) and substation’s components (i.e. transformers versus converters).Figure 1 shows how to add ElectricalDesign() to a yaml diff --git a/docs/source/phases/design/doc_OffshoreSubstationDesign.rst b/docs/source/phases/design/doc_OffshoreSubstationDesign.rst index ec393be6..ef309b47 100644 --- a/docs/source/phases/design/doc_OffshoreSubstationDesign.rst +++ b/docs/source/phases/design/doc_OffshoreSubstationDesign.rst @@ -16,4 +16,4 @@ References ---------- .. [#maness2017] Michael Maness, Benjamin Maples, Aaron Smith, - NREL Offshore Balance-of-System Model, 2017 + NREL Offshore Balance-of-System Model, 2017. https://www.nrel.gov/docs/fy17osti/66874.pdf diff --git a/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst b/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst index ad137a51..3838bca7 100644 --- a/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst +++ b/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst @@ -28,6 +28,7 @@ the key parameters available. ... "support_vessel": "example_support_vessel", # Will perform onsite installation procedures. + "ahts_vessel": "example_ahts_vessel", # Anchor handling tug supply vessel associated with each tow group. "towing_vessel": "example_towing_vessel", # Towing groups will contain multiple of this vessel. "towing_groups": { "towing_vessel": 1, # Vessels used to tow the substructure to site. diff --git a/examples/4. Example Fixed Project.ipynb b/examples/4. Example Fixed Project.ipynb index f8a4c389..d01275cb 100644 --- a/examples/4. Example Fixed Project.ipynb +++ b/examples/4. Example Fixed Project.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -899,7 +899,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -913,7 +913,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/examples/5. Example Floating Project-SemiTaut.ipynb b/examples/5. Example Floating Project-SemiTaut.ipynb new file mode 100644 index 00000000..d3090455 --- /dev/null +++ b/examples/5. Example Floating Project-SemiTaut.ipynb @@ -0,0 +1,642 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Jake Nunemaker\n", + "\n", + "National Renewable Energy Lab\n", + "\n", + "Last updated: 12/23/2020" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "UserWarning: /var/folders/90/1lkt657x3n1cw5x65j3lfgd5406fb8/T/ipykernel_31846/3472246521.py:7\n", + "Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format." + ] + } + ], + "source": [ + "import pandas as pd\n", + "from pprint import pprint\n", + "from ORBIT import ProjectManager, load_config\n", + "\n", + "import warnings\n", + "warnings.filterwarnings(\"default\")\n", + "weather = pd.read_csv(\"data/example_weather.csv\", parse_dates=[\"datetime\"])\\\n", + " .set_index(\"datetime\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load the project configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Num turbines: 50\n", + "Turbine: 12MW_generic\n", + "\n", + "Site: {'depth': 900, 'distance': 100, 'distance_to_landfall': 100}\n" + ] + } + ], + "source": [ + "fixed_config = load_config(\"configs/example_floating_project_SemiTaut.yaml\")\n", + "\n", + "print(f\"Num turbines: {fixed_config['plant']['num_turbines']}\")\n", + "print(f\"Turbine: {fixed_config['turbine']}\")\n", + "print(f\"\\nSite: {fixed_config['site']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Phases" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Design phases: ['ArraySystemDesign', 'ElectricalDesign', 'SemiTautMooringSystemDesign', 'SemiSubmersibleDesign']\n", + "\n", + "Install phases: ['ArrayCableInstallation', 'ExportCableInstallation', 'MooredSubInstallation', 'MooringSystemInstallation', 'FloatingSubstationInstallation']\n" + ] + } + ], + "source": [ + "print(f\"Design phases: {fixed_config['design_phases']}\")\n", + "print(f\"\\nInstall phases: {list(fixed_config['install_phases'].keys())}\")\n", + "# This now says \"SemiTautMooringSystemDesign\" in the design phases" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Run" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "UserWarning: /Users/nriccobo/GitHub/ORBIT/ORBIT/phases/install/quayside_assembly_tow/moored.py:193\n", + "No ['ahts_vessel'] specified. num_ahts set to 0. ahts_vessel will be required in future releases.\n", + "DeprecationWarning: /Users/nriccobo/GitHub/ORBIT/ORBIT/phases/install/quayside_assembly_tow/moored.py:93\n", + "support_vessel will be deprecated and replaced with towing_vessels and ahts_vessel in the towing groups.\n", + "DeprecationWarning: /Users/nriccobo/GitHub/ORBIT/ORBIT/phases/install/quayside_assembly_tow/moored.py:93\n", + "station_keeping_vessels will be deprecated and replaced with towing_vessels and ahts_vessels in the towing groups.\n" + ] + } + ], + "source": [ + "project = ProjectManager(fixed_config, weather=weather)\n", + "project.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Top Level Outputs" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Installation CapEx: 345 M\n", + "System CapEx: 1333 M\n", + "Turbine CapEx: 780 M\n", + "Soft CapEx: 387 M\n", + "Total CapEx: 2997 M\n", + "\n", + "Installation Time: 35527 h\n" + ] + } + ], + "source": [ + "print(f\"Installation CapEx: {project.installation_capex/1e6:.0f} M\")\n", + "print(f\"System CapEx: {project.system_capex/1e6:.0f} M\")\n", + "print(f\"Turbine CapEx: {project.turbine_capex/1e6:.0f} M\")\n", + "print(f\"Soft CapEx: {project.soft_capex/1e6:.0f} M\")\n", + "print(f\"Total CapEx: {project.total_capex/1e6:.0f} M\")\n", + "\n", + "print(f\"\\nInstallation Time: {project.installation_time:.0f} h\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CapEx Breakdown" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Array System': 56983076.60642063,\n", + " 'Export System': 259281192.288,\n", + " 'Substructure': 630709636.6,\n", + " 'Mooring System': 327467880.0,\n", + " 'Offshore Substation': 58536861.93724438,\n", + " 'Array System Installation': 63027746.845681354,\n", + " 'Export System Installation': 148076127.6910655,\n", + " 'Substructure Installation': 78801350.29354209,\n", + " 'Mooring System Installation': 48485331.05022831,\n", + " 'Offshore Substation Installation': 7070795.281582953,\n", + " 'Turbine': 780000000,\n", + " 'Soft': 387000000,\n", + " 'Project': 151250000.0}" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project.capex_breakdown" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Array System': 94.97179434403438,\n", + " 'Export System': 432.13532047999996,\n", + " 'Substructure': 1051.1827276666668,\n", + " 'Mooring System': 545.7798,\n", + " 'Offshore Substation': 97.56143656207396,\n", + " 'Array System Installation': 105.04624474280226,\n", + " 'Export System Installation': 246.79354615177581,\n", + " 'Substructure Installation': 131.33558382257016,\n", + " 'Mooring System Installation': 80.80888508371386,\n", + " 'Offshore Substation Installation': 11.784658802638255,\n", + " 'Turbine': 1300.0,\n", + " 'Soft': 645.0,\n", + " 'Project': 252.08333333333334}" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project.capex_breakdown_per_kw" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Installation Actions" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cost_multiplieragentactiondurationcostleveltimephaselocationphase_namemax_waveheightmax_windspeedtransit_speednum_vesselsnum_ahts_vessels
00.5Array Cable Installation VesselMobilize72.0000003.375000e+05ACTION0.000000ArrayCableInstallationNaNNaNNaNNaNNaNNaNNaN
10.5Export Cable Installation VesselMobilize72.0000003.375000e+05ACTION0.000000ExportCableInstallationNaNNaNNaNNaNNaNNaNNaN
2NaNOnshore ConstructionOnshore Construction0.0000001.665604e+06ACTION0.000000ExportCableInstallationLandfallNaNNaNNaNNaNNaNNaN
31.0Mooring System Installation VesselMobilize168.0000007.000000e+05ACTION0.000000MooringSystemInstallationNaNNaNNaNNaNNaNNaNNaN
4NaNSubstation Assembly Line 1Substation Substructure Assembly0.0000000.000000e+00ACTION0.000000FloatingSubstationInstallationNaNNaNNaNNaNNaNNaNNaN
................................................
2988NaNExport Cable Installation VesselPull In Cable5.5000005.156250e+04ACTION12017.280762ExportCableInstallationNaNExportCableInstallationNaNNaNNaNNaNNaN
2989NaNExport Cable Installation VesselTerminate Cable5.5000005.156250e+04ACTION12022.780762ExportCableInstallationNaNExportCableInstallationNaNNaNNaNNaNNaN
2990NaNExport Cable Installation VesselTransit8.0000007.500000e+04ACTION12030.780762ExportCableInstallationNaNNaNNaNNaNNaNNaNNaN
2991NaNExport Cable Installation VesselDelay26.0000002.437500e+05ACTION12056.780762ExportCableInstallationNaNNaNNaNNaNNaNNaNNaN
2992NaNExport Cable Installation VesselTransit0.6956526.521739e+03ACTION12057.476414ExportCableInstallationNaNNaNNaNNaNNaNNaNNaN
\n", + "

2993 rows \u00d7 15 columns

\n", + "
" + ], + "text/plain": [ + " cost_multiplier agent \\\n", + "0 0.5 Array Cable Installation Vessel \n", + "1 0.5 Export Cable Installation Vessel \n", + "2 NaN Onshore Construction \n", + "3 1.0 Mooring System Installation Vessel \n", + "4 NaN Substation Assembly Line 1 \n", + "... ... ... \n", + "2988 NaN Export Cable Installation Vessel \n", + "2989 NaN Export Cable Installation Vessel \n", + "2990 NaN Export Cable Installation Vessel \n", + "2991 NaN Export Cable Installation Vessel \n", + "2992 NaN Export Cable Installation Vessel \n", + "\n", + " action duration cost level \\\n", + "0 Mobilize 72.000000 3.375000e+05 ACTION \n", + "1 Mobilize 72.000000 3.375000e+05 ACTION \n", + "2 Onshore Construction 0.000000 1.665604e+06 ACTION \n", + "3 Mobilize 168.000000 7.000000e+05 ACTION \n", + "4 Substation Substructure Assembly 0.000000 0.000000e+00 ACTION \n", + "... ... ... ... ... \n", + "2988 Pull In Cable 5.500000 5.156250e+04 ACTION \n", + "2989 Terminate Cable 5.500000 5.156250e+04 ACTION \n", + "2990 Transit 8.000000 7.500000e+04 ACTION \n", + "2991 Delay 26.000000 2.437500e+05 ACTION \n", + "2992 Transit 0.695652 6.521739e+03 ACTION \n", + "\n", + " time phase location \\\n", + "0 0.000000 ArrayCableInstallation NaN \n", + "1 0.000000 ExportCableInstallation NaN \n", + "2 0.000000 ExportCableInstallation Landfall \n", + "3 0.000000 MooringSystemInstallation NaN \n", + "4 0.000000 FloatingSubstationInstallation NaN \n", + "... ... ... ... \n", + "2988 12017.280762 ExportCableInstallation NaN \n", + "2989 12022.780762 ExportCableInstallation NaN \n", + "2990 12030.780762 ExportCableInstallation NaN \n", + "2991 12056.780762 ExportCableInstallation NaN \n", + "2992 12057.476414 ExportCableInstallation NaN \n", + "\n", + " phase_name max_waveheight max_windspeed transit_speed \\\n", + "0 NaN NaN NaN NaN \n", + "1 NaN NaN NaN NaN \n", + "2 NaN NaN NaN NaN \n", + "3 NaN NaN NaN NaN \n", + "4 NaN NaN NaN NaN \n", + "... ... ... ... ... \n", + "2988 ExportCableInstallation NaN NaN NaN \n", + "2989 ExportCableInstallation NaN NaN NaN \n", + "2990 NaN NaN NaN NaN \n", + "2991 NaN NaN NaN NaN \n", + "2992 NaN NaN NaN NaN \n", + "\n", + " num_vessels num_ahts_vessels \n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "... ... ... \n", + "2988 NaN NaN \n", + "2989 NaN NaN \n", + "2990 NaN NaN \n", + "2991 NaN NaN \n", + "2992 NaN NaN \n", + "\n", + "[2993 rows x 15 columns]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.DataFrame(project.actions)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'deck_space': 1,\n", + " 'length': 0,\n", + " 'mass': 1192.0,\n", + " 'type': 'Floating',\n", + " 'unit_cost': 3576000.0}\n", + "{'deck_space': 1, 'mass': 2980.0, 'unit_cost': 48411504.33724438}\n", + "{'anchor_cost': array(139426.2),\n", + " 'anchor_mass': 20,\n", + " 'anchor_type': 'Drag Embedment',\n", + " 'line_cost': array(1497913.2),\n", + " 'line_length': 1755.71,\n", + " 'line_mass': 579.8762530880001,\n", + " 'num_lines': 4,\n", + " 'system_cost': 327467880.0}\n", + "'Mooring System: $/kW'\n", + "545.7798\n", + "80.80888508371386\n" + ] + } + ], + "source": [ + "pprint(project.design_results[\"offshore_substation_substructure\"])\n", + "pprint(project.design_results[\"offshore_substation_topside\"])\n", + "pprint(project.design_results[\"mooring_system\"])\n", + "\n", + "pprint(\"Mooring System: $/kW\")\n", + "pprint(project.capex_breakdown_per_kw['Mooring System'])\n", + "pprint(project.capex_breakdown_per_kw['Mooring System Installation'])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.18" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/5. Example Floating Project.ipynb b/examples/5. Example Floating Project.ipynb index 32f94b36..911922e2 100644 --- a/examples/5. Example Floating Project.ipynb +++ b/examples/5. Example Floating Project.ipynb @@ -1,547 +1,631 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Jake Nunemaker\n", - "\n", - "National Renewable Energy Lab\n", - "\n", - "Last updated: 12/23/2020" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import pandas as pd\n", - "from ORBIT import ProjectManager, load_config\n", - "\n", - "weather = pd.read_csv(\"data/example_weather.csv\", parse_dates=[\"datetime\"])\\\n", - " .set_index(\"datetime\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load the project configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ + "cells": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Num turbines: 50\n", - "Turbine: 12MW_generic\n", - "\n", - "Site: {'depth': 900, 'distance': 100, 'distance_to_landfall': 100}\n" - ] - } - ], - "source": [ - "fixed_config = load_config(\"configs/example_floating_project.yaml\") \n", - "\n", - "print(f\"Num turbines: {fixed_config['plant']['num_turbines']}\")\n", - "print(f\"Turbine: {fixed_config['turbine']}\")\n", - "print(f\"\\nSite: {fixed_config['site']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Phases" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example Floating Project\n", + "\n", + "This tutorial uses prepared ORBIT configs that are stored as .yaml files in the `~/configs/` folder. These example projects each exhibit different functionalities within ORBIT. Using these examples and combinations of them, most project configurations can be modeled. \n", + "\n", + "Last updated: May 2024" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Design phases: ['ArraySystemDesign', 'ExportSystemDesign', 'MooringSystemDesign', 'OffshoreSubstationDesign', 'SemiSubmersibleDesign']\n", - "\n", - "Install phases: ['ArrayCableInstallation', 'ExportCableInstallation', 'MooredSubInstallation', 'MooringSystemInstallation', 'OffshoreSubstationInstallation', 'TurbineInstallation']\n" - ] - } - ], - "source": [ - "print(f\"Design phases: {fixed_config['design_phases']}\")\n", - "print(f\"\\nInstall phases: {list(fixed_config['install_phases'].keys())}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Run" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "UserWarning: /var/folders/90/1lkt657x3n1cw5x65j3lfgd5406fb8/T/ipykernel_71795/2795185578.py:8\n", + "Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format." + ] + } + ], + "source": [ + "import pandas as pd\n", + "from pprint import pprint\n", + "from ORBIT import ProjectManager, load_config\n", + "\n", + "import warnings\n", + "warnings.filterwarnings(\"default\")\n", + "\n", + "weather = pd.read_csv(\"data/example_weather.csv\", parse_dates=[\"datetime\"])\\\n", + " .set_index(\"datetime\")" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "ORBIT library intialized at '/Users/jnunemak/Fun/repos/ORBIT/library'\n" - ] - } - ], - "source": [ - "project = ProjectManager(fixed_config, weather=weather)\n", - "project.run()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Top Level Outputs" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load the project configuration\n", + "`~/configs/example_floating_project.yaml`" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Installation CapEx: 420 M\n", - "System CapEx: 1222 M\n", - "Turbine CapEx: 780 M\n", - "Soft CapEx: 387 M\n", - "Total CapEx: 2961 M\n", - "\n", - "Installation Time: 27750 h\n" - ] - } - ], - "source": [ - "print(f\"Installation CapEx: {project.installation_capex/1e6:.0f} M\")\n", - "print(f\"System CapEx: {project.system_capex/1e6:.0f} M\")\n", - "print(f\"Turbine CapEx: {project.turbine_capex/1e6:.0f} M\")\n", - "print(f\"Soft CapEx: {project.soft_capex/1e6:.0f} M\")\n", - "print(f\"Total CapEx: {project.total_capex/1e6:.0f} M\")\n", - "\n", - "print(f\"\\nInstallation Time: {project.installation_time:.0f} h\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### CapEx Breakdown" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Num turbines: 50\n", + "Turbine: 12MW_generic\n", + "\n", + "Site: {'depth': 900, 'distance': 100, 'distance_to_landfall': 100}\n" + ] + } + ], + "source": [ + "floating_config = load_config(\"configs/example_floating_project.yaml\")\n", + "\n", + "print(f\"Num turbines: {floating_config['plant']['num_turbines']}\")\n", + "print(f\"Turbine: {floating_config['turbine']}\")\n", + "print(f\"\\nSite: {floating_config['site']}\")" + ] + }, { - "data": { - "text/plain": [ - "{'Array System': 56983076.60642063,\n", - " 'Export System': 103712476.9152,\n", - " 'Substructure': 630709636.6,\n", - " 'Mooring System': 331379224.80820334,\n", - " 'Offshore Substation': 99479100.0,\n", - " 'Array System Installation': 22844527.896071255,\n", - " 'Export System Installation': 135112258.0470523,\n", - " 'Substructure Installation': 79182122.33637744,\n", - " 'Mooring System Installation': 50094520.5479452,\n", - " 'Offshore Substation Installation': 5499328.911719939,\n", - " 'Turbine Installation': 127738070.77625567,\n", - " 'Turbine': 780000000,\n", - " 'Soft': 387000000,\n", - " 'Project': 151250000.0}" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Phases" ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "project.capex_breakdown" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Installation Actions" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ + }, { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
cost_multiplieragentactiondurationcostleveltimephaselocationsite_depthhub_heightphase_namemax_waveheightmax_windspeedtransit_speednum_vessels
00.5Array Cable Installation VesselMobilize72.0000001.800000e+05ACTION0.000000ArrayCableInstallationNaNNaNNaNNaNNaNNaNNaNNaN
10.5Export Cable Installation VesselMobilize72.0000001.800000e+05ACTION0.000000ExportCableInstallationNaNNaNNaNNaNNaNNaNNaNNaN
2NaNOnshore ConstructionOnshore Construction0.0000001.075454e+08ACTION0.000000ExportCableInstallationLandfallNaNNaNNaNNaNNaNNaNNaN
31.0Mooring System Installation VesselMobilize168.0000007.000000e+05ACTION0.000000MooringSystemInstallationNaNNaNNaNNaNNaNNaNNaNNaN
40.5Heavy Lift VesselMobilize72.0000007.500000e+05ACTION0.000000OffshoreSubstationInstallationNaNNaNNaNNaNNaNNaNNaNNaN
...................................................
4405NaNMulti-Purpose Support VesselConnect Mooring Lines22.0000009.166667e+04ACTION8554.500000MooredSubInstallationNaNNaNNaNNaNNaNNaNNaNNaN
4406NaNMulti-Purpose Support VesselCheck Mooring Lines12.0000005.000000e+04ACTION8566.500000MooredSubInstallationNaNNaNNaNNaNNaNNaNNaNNaN
4407NaNTowing Group 1Positioning Support42.0000001.050000e+05ACTION8566.500000MooredSubInstallationsiteNaNNaNNaNNaNNaNNaN2.0
4408NaNMulti-Purpose Support VesselTransit10.0000004.166667e+04ACTION8576.500000MooredSubInstallationNaNNaNNaNNaNNaNNaNNaNNaN
4409NaNTowing Group 1Transit16.6666676.250000e+04ACTION8583.166667MooredSubInstallationNaNNaNNaNNaNNaNNaNNaN3.0
\n", - "

4410 rows × 16 columns

\n", - "
" + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Design phases: ['ArraySystemDesign', 'ElectricalDesign', 'MooringSystemDesign', 'OffshoreFloatingSubstationDesign', 'SemiSubmersibleDesign']\n", + "\n", + "Install phases: ['ArrayCableInstallation', 'ExportCableInstallation', 'MooredSubInstallation', 'MooringSystemInstallation', 'FloatingSubstationInstallation', 'TurbineInstallation']\n" + ] + } ], - "text/plain": [ - " cost_multiplier agent \\\n", - "0 0.5 Array Cable Installation Vessel \n", - "1 0.5 Export Cable Installation Vessel \n", - "2 NaN Onshore Construction \n", - "3 1.0 Mooring System Installation Vessel \n", - "4 0.5 Heavy Lift Vessel \n", - "... ... ... \n", - "4405 NaN Multi-Purpose Support Vessel \n", - "4406 NaN Multi-Purpose Support Vessel \n", - "4407 NaN Towing Group 1 \n", - "4408 NaN Multi-Purpose Support Vessel \n", - "4409 NaN Towing Group 1 \n", - "\n", - " action duration cost level time \\\n", - "0 Mobilize 72.000000 1.800000e+05 ACTION 0.000000 \n", - "1 Mobilize 72.000000 1.800000e+05 ACTION 0.000000 \n", - "2 Onshore Construction 0.000000 1.075454e+08 ACTION 0.000000 \n", - "3 Mobilize 168.000000 7.000000e+05 ACTION 0.000000 \n", - "4 Mobilize 72.000000 7.500000e+05 ACTION 0.000000 \n", - "... ... ... ... ... ... \n", - "4405 Connect Mooring Lines 22.000000 9.166667e+04 ACTION 8554.500000 \n", - "4406 Check Mooring Lines 12.000000 5.000000e+04 ACTION 8566.500000 \n", - "4407 Positioning Support 42.000000 1.050000e+05 ACTION 8566.500000 \n", - "4408 Transit 10.000000 4.166667e+04 ACTION 8576.500000 \n", - "4409 Transit 16.666667 6.250000e+04 ACTION 8583.166667 \n", - "\n", - " phase location site_depth hub_height \\\n", - "0 ArrayCableInstallation NaN NaN NaN \n", - "1 ExportCableInstallation NaN NaN NaN \n", - "2 ExportCableInstallation Landfall NaN NaN \n", - "3 MooringSystemInstallation NaN NaN NaN \n", - "4 OffshoreSubstationInstallation NaN NaN NaN \n", - "... ... ... ... ... \n", - "4405 MooredSubInstallation NaN NaN NaN \n", - "4406 MooredSubInstallation NaN NaN NaN \n", - "4407 MooredSubInstallation site NaN NaN \n", - "4408 MooredSubInstallation NaN NaN NaN \n", - "4409 MooredSubInstallation NaN NaN NaN \n", - "\n", - " phase_name max_waveheight max_windspeed transit_speed num_vessels \n", - "0 NaN NaN NaN NaN NaN \n", - "1 NaN NaN NaN NaN NaN \n", - "2 NaN NaN NaN NaN NaN \n", - "3 NaN NaN NaN NaN NaN \n", - "4 NaN NaN NaN NaN NaN \n", - "... ... ... ... ... ... \n", - "4405 NaN NaN NaN NaN NaN \n", - "4406 NaN NaN NaN NaN NaN \n", - "4407 NaN NaN NaN NaN 2.0 \n", - "4408 NaN NaN NaN NaN NaN \n", - "4409 NaN NaN NaN NaN 3.0 \n", - "\n", - "[4410 rows x 16 columns]" + "source": [ + "print(f\"Design phases: {floating_config['design_phases']}\")\n", + "print(f\"\\nInstall phases: {list(floating_config['install_phases'].keys())}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Run" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ORBIT library intialized at '/Users/nriccobo/GitHub/ORBIT/library'\n", + "2\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DeprecationWarning: /Users/nriccobo/GitHub/ORBIT/ORBIT/phases/install/quayside_assembly_tow/moored.py:93\n", + "support_vessel will be deprecated and replaced with towing_vessels and ahts_vessel in the towing groups.\n", + "DeprecationWarning: /Users/nriccobo/GitHub/ORBIT/ORBIT/phases/install/quayside_assembly_tow/moored.py:93\n", + "['towing_vessl_groups]['station_keeping_vessels'] will be deprecated and replaced with ['towing_vessl_groups]['ahts_vessels'].\n" + ] + } + ], + "source": [ + "project = ProjectManager(floating_config, weather=weather)\n", + "project.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Top Level Outputs" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Installation CapEx: 519 M\n", + "System CapEx: 1333 M\n", + "Turbine CapEx: 780 M\n", + "Soft CapEx: 387 M\n", + "Total CapEx: 3171 M\n", + "\n", + "Installation Time: 40914 h\n" + ] + } + ], + "source": [ + "print(f\"Installation CapEx: {project.installation_capex/1e6:.0f} M\")\n", + "print(f\"System CapEx: {project.system_capex/1e6:.0f} M\")\n", + "print(f\"Turbine CapEx: {project.turbine_capex/1e6:.0f} M\")\n", + "print(f\"Soft CapEx: {project.soft_capex/1e6:.0f} M\")\n", + "print(f\"Total CapEx: {project.total_capex/1e6:.0f} M\")\n", + "\n", + "print(f\"\\nInstallation Time: {project.installation_time:.0f} h\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CapEx Breakdown" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Array System': 94.97179434403438,\n", + " 'Export System': 432.13532047999996,\n", + " 'Substructure': 1051.1827276666668,\n", + " 'Mooring System': 545.7798,\n", + " 'Offshore Substation': 97.56143656207396,\n", + " 'Array System Installation': 105.04624474280226,\n", + " 'Export System Installation': 246.79354615177581,\n", + " 'Substructure Installation': 208.2509277379141,\n", + " 'Mooring System Installation': 80.80888508371386,\n", + " 'Offshore Substation Installation': 11.784658802638255,\n", + " 'Turbine Installation': 212.89678462709279,\n", + " 'Turbine': 1300.0,\n", + " 'Soft': 645.0,\n", + " 'Project': 252.08333333333334}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project.capex_breakdown_per_kw" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Installation Actions" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
cost_multiplieragentactiondurationcostleveltimephaselocationsite_depthhub_heightphase_namemax_waveheightmax_windspeedtransit_speednum_vesselsnum_ahts_vessels
00.5Array Cable Installation VesselMobilize72.0000003.375000e+05ACTION0.000000ArrayCableInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
10.5Export Cable Installation VesselMobilize72.0000003.375000e+05ACTION0.000000ExportCableInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
2NaNOnshore ConstructionOnshore Construction0.0000001.665604e+06ACTION0.000000ExportCableInstallationLandfallNaNNaNNaNNaNNaNNaNNaNNaN
31.0Mooring System Installation VesselMobilize168.0000007.000000e+05ACTION0.000000MooringSystemInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
4NaNSubstation Assembly Line 1Substation Substructure Assembly0.0000000.000000e+00ACTION0.000000FloatingSubstationInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
......................................................
4458NaNExport Cable Installation VesselPull In Cable5.5000005.156250e+04ACTION12017.280762ExportCableInstallationNaNNaNNaNExportCableInstallationNaNNaNNaNNaNNaN
4459NaNExport Cable Installation VesselTerminate Cable5.5000005.156250e+04ACTION12022.780762ExportCableInstallationNaNNaNNaNExportCableInstallationNaNNaNNaNNaNNaN
4460NaNExport Cable Installation VesselTransit8.0000007.500000e+04ACTION12030.780762ExportCableInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
4461NaNExport Cable Installation VesselDelay26.0000002.437500e+05ACTION12056.780762ExportCableInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
4462NaNExport Cable Installation VesselTransit0.6956526.521739e+03ACTION12057.476414ExportCableInstallationNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", + "

4463 rows \u00d7 17 columns

\n", + "
" + ], + "text/plain": [ + " cost_multiplier agent \\\n", + "0 0.5 Array Cable Installation Vessel \n", + "1 0.5 Export Cable Installation Vessel \n", + "2 NaN Onshore Construction \n", + "3 1.0 Mooring System Installation Vessel \n", + "4 NaN Substation Assembly Line 1 \n", + "... ... ... \n", + "4458 NaN Export Cable Installation Vessel \n", + "4459 NaN Export Cable Installation Vessel \n", + "4460 NaN Export Cable Installation Vessel \n", + "4461 NaN Export Cable Installation Vessel \n", + "4462 NaN Export Cable Installation Vessel \n", + "\n", + " action duration cost level \\\n", + "0 Mobilize 72.000000 3.375000e+05 ACTION \n", + "1 Mobilize 72.000000 3.375000e+05 ACTION \n", + "2 Onshore Construction 0.000000 1.665604e+06 ACTION \n", + "3 Mobilize 168.000000 7.000000e+05 ACTION \n", + "4 Substation Substructure Assembly 0.000000 0.000000e+00 ACTION \n", + "... ... ... ... ... \n", + "4458 Pull In Cable 5.500000 5.156250e+04 ACTION \n", + "4459 Terminate Cable 5.500000 5.156250e+04 ACTION \n", + "4460 Transit 8.000000 7.500000e+04 ACTION \n", + "4461 Delay 26.000000 2.437500e+05 ACTION \n", + "4462 Transit 0.695652 6.521739e+03 ACTION \n", + "\n", + " time phase location site_depth \\\n", + "0 0.000000 ArrayCableInstallation NaN NaN \n", + "1 0.000000 ExportCableInstallation NaN NaN \n", + "2 0.000000 ExportCableInstallation Landfall NaN \n", + "3 0.000000 MooringSystemInstallation NaN NaN \n", + "4 0.000000 FloatingSubstationInstallation NaN NaN \n", + "... ... ... ... ... \n", + "4458 12017.280762 ExportCableInstallation NaN NaN \n", + "4459 12022.780762 ExportCableInstallation NaN NaN \n", + "4460 12030.780762 ExportCableInstallation NaN NaN \n", + "4461 12056.780762 ExportCableInstallation NaN NaN \n", + "4462 12057.476414 ExportCableInstallation NaN NaN \n", + "\n", + " hub_height phase_name max_waveheight max_windspeed \\\n", + "0 NaN NaN NaN NaN \n", + "1 NaN NaN NaN NaN \n", + "2 NaN NaN NaN NaN \n", + "3 NaN NaN NaN NaN \n", + "4 NaN NaN NaN NaN \n", + "... ... ... ... ... \n", + "4458 NaN ExportCableInstallation NaN NaN \n", + "4459 NaN ExportCableInstallation NaN NaN \n", + "4460 NaN NaN NaN NaN \n", + "4461 NaN NaN NaN NaN \n", + "4462 NaN NaN NaN NaN \n", + "\n", + " transit_speed num_vessels num_ahts_vessels \n", + "0 NaN NaN NaN \n", + "1 NaN NaN NaN \n", + "2 NaN NaN NaN \n", + "3 NaN NaN NaN \n", + "4 NaN NaN NaN \n", + "... ... ... ... \n", + "4458 NaN NaN NaN \n", + "4459 NaN NaN NaN \n", + "4460 NaN NaN NaN \n", + "4461 NaN NaN NaN \n", + "4462 NaN NaN NaN \n", + "\n", + "[4463 rows x 17 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.DataFrame(project.actions)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'deck_space': 1,\n", + " 'length': 0,\n", + " 'mass': 1192.0,\n", + " 'type': 'Floating',\n", + " 'unit_cost': 3576000.0}\n", + "{'deck_space': 1, 'mass': 2980.0, 'unit_cost': 48411504.33724438}\n", + "{'anchor_cost': 139426.2,\n", + " 'anchor_mass': 20,\n", + " 'anchor_type': 'Drag Embedment',\n", + " 'line_cost': 1497913.2,\n", + " 'line_diam': 0.15,\n", + " 'line_length': 1755.71,\n", + " 'line_mass': 579.8762530880001,\n", + " 'mooring_type': 'SemiTaut',\n", + " 'num_lines': 4,\n", + " 'system_cost': 327467880.0}\n", + "'Mooring System: $/kW'\n", + "545.7798\n", + "80.80888508371386\n" + ] + } + ], + "source": [ + "pprint(project.design_results[\"offshore_substation_substructure\"])\n", + "pprint(project.design_results[\"offshore_substation_topside\"])\n", + "pprint(project.design_results[\"mooring_system\"])\n", + "\n", + "pprint(\"Mooring System: $/kW\")\n", + "pprint(project.capex_breakdown_per_kw['Mooring System'])\n", + "\n", + "pprint(project.capex_breakdown_per_kw['Mooring System Installation'])" ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" } - ], - "source": [ - "pd.DataFrame(project.actions)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.18" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.10" - } - }, - "nbformat": 4, - "nbformat_minor": 4 + "nbformat": 4, + "nbformat_minor": 4 } diff --git a/examples/configs/example_floating_project.yaml b/examples/configs/example_floating_project.yaml index baad61c0..28e7fd24 100644 --- a/examples/configs/example_floating_project.yaml +++ b/examples/configs/example_floating_project.yaml @@ -19,6 +19,7 @@ export_cable_install_vessel: example_cable_lay_vessel mooring_install_vessel: example_support_vessel oss_install_vessel: floating_heavy_lift_vessel support_vessel: example_support_vessel +ahts_vessel: example_ahts_vessel towing_vessel: example_towing_vessel towing_vessel_groups: station_keeping_vessels: 2 @@ -27,8 +28,11 @@ wtiv: floating_heavy_lift_vessel # Module Specific substructure: takt_time: 168 -OffshoreSubstationInstallation: - feeder: floating_barge +substation_design: + oss_substructure_type: Floating +mooring_system_design: + anchor_type: Drag Embedment + mooring_type: SemiTaut array_system: free_cable_length: 0.5 array_system_design: @@ -40,16 +44,16 @@ export_system_design: # Configured Phases design_phases: - ArraySystemDesign -- ExportSystemDesign +- ElectricalDesign - MooringSystemDesign -- OffshoreSubstationDesign +- OffshoreFloatingSubstationDesign - SemiSubmersibleDesign install_phases: ArrayCableInstallation: 0 ExportCableInstallation: 0 MooredSubInstallation: 0 MooringSystemInstallation: 0 - OffshoreSubstationInstallation: 0 + FloatingSubstationInstallation: 0 TurbineInstallation: 0 # Project Inputs turbine: 12MW_generic diff --git a/examples/configs/example_floating_project_SemiTaut.yaml b/examples/configs/example_floating_project_SemiTaut.yaml new file mode 100644 index 00000000..cba486b6 --- /dev/null +++ b/examples/configs/example_floating_project_SemiTaut.yaml @@ -0,0 +1,58 @@ +# Site + Plant Parameters +site: + depth: 900 + distance: 100 + distance_to_landfall: 100 +plant: + layout: ring + num_turbines: 50 + row_spacing: 7 + substation_distance: 1 + turbine_spacing: 7 +port: + monthly_rate: 2000000.0 + sub_assembly_lines: 1 + turbine_assembly_cranes: 1 +# Vessels +array_cable_install_vessel: example_cable_lay_vessel +export_cable_install_vessel: example_cable_lay_vessel +mooring_install_vessel: example_support_vessel +oss_install_vessel: floating_heavy_lift_vessel +support_vessel: example_support_vessel +#ahts_vessel: example_ahts_vessel +towing_vessel: example_towing_vessel +towing_vessel_groups: + station_keeping_vessels: 2 + ahts_vessels: 1 + towing_vessels: 3 +wtiv: floating_heavy_lift_vessel +# Module Specific +substructure: + takt_time: 168 +substation_design: + oss_substructure_type: Floating +array_system: + free_cable_length: 0.5 +array_system_design: + cables: + - XLPE_630mm_66kV +export_system_design: + cables: XLPE_500mm_132kV + percent_added_length: 0.0 +offshore_substation_substructure: + type: Floating +# Configured Phases +design_phases: +- ArraySystemDesign +- ElectricalDesign +- SemiTautMooringSystemDesign +#- OffshoreFloatingSubstationDesign +- SemiSubmersibleDesign +install_phases: + ArrayCableInstallation: 0 + ExportCableInstallation: 0 + MooredSubInstallation: 0 + MooringSystemInstallation: 0 + FloatingSubstationInstallation: 0 +# Project Inputs +turbine: 12MW_generic diff --git a/library/cables/XLPE_1000m_220kV_dynamic.yaml b/library/cables/XLPE_1000m_220kV_dynamic.yaml new file mode 100644 index 00000000..d179ae21 --- /dev/null +++ b/library/cables/XLPE_1000m_220kV_dynamic.yaml @@ -0,0 +1,10 @@ +ac_resistance: 0.16 # ohm/km +capacitance: 190 # nF/km +conductor_size: 1000 # mm^2 +cost_per_km: 1020000 # $ (20% adder over XLPE_1000m_220kV) +current_capacity: 825 # A +inductance: 0.38 # mH/km +linear_density: 115 # t/km +cable_type: HVAC # HVDC vs HVAC +name: XLPE_1000m_220kV +rated_voltage: 220 diff --git a/library/cables/XLPE_185mm_66kV_dynamic.yaml b/library/cables/XLPE_185mm_66kV_dynamic.yaml new file mode 100644 index 00000000..5a9e706e --- /dev/null +++ b/library/cables/XLPE_185mm_66kV_dynamic.yaml @@ -0,0 +1,9 @@ +ac_resistance: 0.128 # +capacitance: 163 # +conductor_size: 185 # +cost_per_km: 240000 # $ (20% adder over XLPE_185mm_66kV) +current_capacity: 445 # At 2m burial depth +inductance: 0.443 # +linear_density: 26.1 # +name: XLPE_185mm_66kV +rated_voltage: 66 diff --git a/library/cables/XLPE_500mm_132kV.yaml b/library/cables/XLPE_500mm_132kV.yaml index e58a7a02..af7061c7 100644 --- a/library/cables/XLPE_500mm_132kV.yaml +++ b/library/cables/XLPE_500mm_132kV.yaml @@ -1,7 +1,7 @@ ac_resistance: 0.06 capacitance: 200 conductor_size: 500 -cost_per_km: 200000 +cost_per_km: 500000 current_capacity: 625 inductance: 0.4 linear_density: 50 diff --git a/library/cables/XLPE_500mm_132kV_dynamic.yaml b/library/cables/XLPE_500mm_132kV_dynamic.yaml new file mode 100644 index 00000000..b0b1ec59 --- /dev/null +++ b/library/cables/XLPE_500mm_132kV_dynamic.yaml @@ -0,0 +1,10 @@ +ac_resistance: 0.06 +capacitance: 200 +conductor_size: 500 +cost_per_km: 600000 # $ (20% adder over XLPE_500mm_132kV) +current_capacity: 625 +inductance: 0.4 +linear_density: 50 +cable_type: HVAC +name: XLPE_500mm_132kV +rated_voltage: 132 diff --git a/library/cables/XLPE_630mm_66kV_dynamic.yaml b/library/cables/XLPE_630mm_66kV_dynamic.yaml new file mode 100644 index 00000000..69d857c3 --- /dev/null +++ b/library/cables/XLPE_630mm_66kV_dynamic.yaml @@ -0,0 +1,11 @@ +ac_resistance: 0.04 +capacitance: 300 +conductor_size: 630 +cost_per_km: 480000 # $ (20% adder over XLPE_630mm_66kV) +current_capacity: 775 +inductance: 0.35 +linear_density: 42.5 +name: XLPE_630mm_66kV +rated_voltage: 66 +switchgear_cost: 1e6 +cable_type: HVAC # HVDC vs HVAC diff --git a/library/vessels/example_ahts_vessel.yaml b/library/vessels/example_ahts_vessel.yaml new file mode 100644 index 00000000..66c2eeda --- /dev/null +++ b/library/vessels/example_ahts_vessel.yaml @@ -0,0 +1,6 @@ +transport_specs: + max_waveheight: 3.0 # m + max_windspeed: 15 # m/s + transit_speed: 14 # km/h +vessel_specs: + day_rate: 100000 # USD/day diff --git a/library/vessels/example_cable_lay_vessel.yaml b/library/vessels/example_cable_lay_vessel.yaml index 7813dc7b..3fd571c8 100644 --- a/library/vessels/example_cable_lay_vessel.yaml +++ b/library/vessels/example_cable_lay_vessel.yaml @@ -4,7 +4,8 @@ transport_specs: transit_speed: 11.5 # km/hr vessel_specs: day_rate: 225000 # USD/day, cost of operating vessel with crew - min_draft: 4.8 # m - overall_length: 99.0 # m + min_draft: 8.5 # m + overall_length: 171.0 # m + cable_lay_bury_speed: 0.0625 # km/hr cable_storage: - max_mass: 4000 # t + max_mass: 13000 # t diff --git a/library/vessels/example_towing_vessel.yaml b/library/vessels/example_towing_vessel.yaml index 5aa4bf58..0dcdc3ef 100644 --- a/library/vessels/example_towing_vessel.yaml +++ b/library/vessels/example_towing_vessel.yaml @@ -1,6 +1,6 @@ transport_specs: - max_waveheight: 2.5 # m - max_windspeed: 20 # m/s - transit_speed: 6 # km/h + max_waveheight: 3.0 # m + max_windspeed: 15 # m/s + transit_speed: 14 # km/h vessel_specs: - day_rate: 30000 # USD/day + day_rate: 35000 # USD/day diff --git a/tests/data/library/project/config/complete_floating_project.yaml b/tests/data/library/project/config/complete_floating_project.yaml index 45fbc3ec..dff5b427 100644 --- a/tests/data/library/project/config/complete_floating_project.yaml +++ b/tests/data/library/project/config/complete_floating_project.yaml @@ -42,6 +42,7 @@ site: substructure: takt_time: 168 support_vessel: test_support_vessel +ahts_vessel: test_ahts_vessel towing_vessel: test_towing_vessel towing_vessel_groups: station_keeping_vessels: 2 diff --git a/tests/data/library/project/config/floating_oss_install.yaml b/tests/data/library/project/config/floating_oss_install.yaml index 3b10e637..816b2679 100644 --- a/tests/data/library/project/config/floating_oss_install.yaml +++ b/tests/data/library/project/config/floating_oss_install.yaml @@ -6,6 +6,10 @@ offshore_substation_substructure: mooring_cost: 5e6 offshore_substation_topside: unit_cost: 100e6 +mooring_system: + num_lines: 3 + line_cost: 1e4 + anchor_cost: 1e5 site: depth: 500 distance: 40 diff --git a/tests/data/library/project/config/moored_install.yaml b/tests/data/library/project/config/moored_install.yaml index 47cd8bb0..242bb802 100644 --- a/tests/data/library/project/config/moored_install.yaml +++ b/tests/data/library/project/config/moored_install.yaml @@ -12,10 +12,12 @@ substructure: takt_time: 168 towing_speed: 6 unit_cost: 12e6 +ahts_vessel: test_ahts_vessel support_vessel: test_support_vessel towing_vessel: test_towing_vessel towing_vessel_groups: num_groups: 1 + ahts_vessels: 1 station_keeping_vessels: 3 towing_vessels: 1 turbine: 12MW_generic diff --git a/tests/data/library/project/config/moored_install_multi_assembly.yaml b/tests/data/library/project/config/moored_install_multi_assembly.yaml new file mode 100644 index 00000000..2f196638 --- /dev/null +++ b/tests/data/library/project/config/moored_install_multi_assembly.yaml @@ -0,0 +1,21 @@ +plant: + num_turbines: 50 +port: + assembly_storage: 1 + sub_assembly_lines: 3 + sub_storage: 1 + turbine_assembly_cranes: 1 +site: + depth: 500 + distance: 50 +substructure: + takt_time: 168 + towing_speed: 6 + unit_cost: 12e6 +ahts_vessel: test_ahts_vessel +towing_vessel: test_towing_vessel +towing_vessel_groups: + num_groups: 1 + ahts_vessels: 1 + towing_vessels: 1 +turbine: 12MW_generic diff --git a/tests/data/library/project/config/moored_install_multi_assembly_multi_tow.yaml b/tests/data/library/project/config/moored_install_multi_assembly_multi_tow.yaml new file mode 100644 index 00000000..2f196638 --- /dev/null +++ b/tests/data/library/project/config/moored_install_multi_assembly_multi_tow.yaml @@ -0,0 +1,21 @@ +plant: + num_turbines: 50 +port: + assembly_storage: 1 + sub_assembly_lines: 3 + sub_storage: 1 + turbine_assembly_cranes: 1 +site: + depth: 500 + distance: 50 +substructure: + takt_time: 168 + towing_speed: 6 + unit_cost: 12e6 +ahts_vessel: test_ahts_vessel +towing_vessel: test_towing_vessel +towing_vessel_groups: + num_groups: 1 + ahts_vessels: 1 + towing_vessels: 1 +turbine: 12MW_generic diff --git a/tests/data/library/project/config/moored_install_multi_tow.yaml b/tests/data/library/project/config/moored_install_multi_tow.yaml new file mode 100644 index 00000000..ae4585e5 --- /dev/null +++ b/tests/data/library/project/config/moored_install_multi_tow.yaml @@ -0,0 +1,21 @@ +plant: + num_turbines: 50 +port: + assembly_storage: 1 + sub_assembly_lines: 1 + sub_storage: 1 + turbine_assembly_cranes: 1 +site: + depth: 500 + distance: 50 +substructure: + takt_time: 168 + towing_speed: 6 + unit_cost: 12e6 +ahts_vessel: test_ahts_vessel +towing_vessel: test_towing_vessel +towing_vessel_groups: + num_groups: 3 + ahts_vessels: 1 + towing_vessels: 1 +turbine: 12MW_generic diff --git a/tests/data/library/project/config/moored_install_no_supply.yaml b/tests/data/library/project/config/moored_install_no_supply.yaml index 249abd85..a91dd2ba 100644 --- a/tests/data/library/project/config/moored_install_no_supply.yaml +++ b/tests/data/library/project/config/moored_install_no_supply.yaml @@ -10,10 +10,12 @@ substructure: takt_time: 0 towing_speed: 6 unit_cost: 12e6 +ahts_vessel: test_ahts_vessel support_vessel: test_support_vessel towing_vessel: test_towing_vessel towing_vessel_groups: num_groups: 1 station_keeping_vessels: 3 + ahts_vessels: 1 towing_vessels: 1 turbine: 12MW_generic diff --git a/tests/data/library/vessels/test_ahts_vessel.yaml b/tests/data/library/vessels/test_ahts_vessel.yaml new file mode 100644 index 00000000..66c2eeda --- /dev/null +++ b/tests/data/library/vessels/test_ahts_vessel.yaml @@ -0,0 +1,6 @@ +transport_specs: + max_waveheight: 3.0 # m + max_windspeed: 15 # m/s + transit_speed: 14 # km/h +vessel_specs: + day_rate: 100000 # USD/day diff --git a/tests/data/library/vessels/test_cable_lay_vessel.yaml b/tests/data/library/vessels/test_cable_lay_vessel.yaml index 73bfb1a8..ee0f1b34 100644 --- a/tests/data/library/vessels/test_cable_lay_vessel.yaml +++ b/tests/data/library/vessels/test_cable_lay_vessel.yaml @@ -6,5 +6,6 @@ vessel_specs: day_rate: 50000 # USD/day, cost of operating vessel with crew min_draft: 4.8 # m overall_length: 99.0 # m + cable_lay_bury_speed: 0.0625 # km/hr cable_storage: max_mass: 6000 diff --git a/tests/data/library/vessels/test_towing_vessel.yaml b/tests/data/library/vessels/test_towing_vessel.yaml index 5aa4bf58..0dcdc3ef 100644 --- a/tests/data/library/vessels/test_towing_vessel.yaml +++ b/tests/data/library/vessels/test_towing_vessel.yaml @@ -1,6 +1,6 @@ transport_specs: - max_waveheight: 2.5 # m - max_windspeed: 20 # m/s - transit_speed: 6 # km/h + max_waveheight: 3.0 # m + max_windspeed: 15 # m/s + transit_speed: 14 # km/h vessel_specs: - day_rate: 30000 # USD/day + day_rate: 35000 # USD/day diff --git a/tests/phases/design/test_electrical_design.py b/tests/phases/design/test_electrical_design.py index 2ca93e26..45e03155 100644 --- a/tests/phases/design/test_electrical_design.py +++ b/tests/phases/design/test_electrical_design.py @@ -20,7 +20,9 @@ "plant": {"capacity": 500}, "export_system_design": {"cables": "XLPE_630mm_220kV"}, "landfall": {}, - "substation_design": {}, + "substation_design": { + "oss_pile_cost_rate": 1200, # need to set this for kwarg tests + }, } @@ -41,22 +43,114 @@ def test_parameter_sweep(distance_to_landfall, depth, plant_cap, cable): "substation_design": {}, } - o = ElectricalDesign(config) - o.run() + elect = ElectricalDesign(config) + elect.run() # Check valid substructure length - assert 10 <= o._outputs["offshore_substation_substructure"]["length"] <= 80 + assert ( + 10 + <= elect._outputs["offshore_substation_substructure"]["length"] + <= 80 + ) # Check valid substructure mass assert ( - 200 <= o._outputs["offshore_substation_substructure"]["mass"] <= 2700 + 200 + <= elect._outputs["offshore_substation_substructure"]["mass"] + <= 2700 ) # Check valid topside mass - assert 200 <= o._outputs["offshore_substation_topside"]["mass"] <= 5500 + assert 200 <= elect._outputs["offshore_substation_topside"]["mass"] <= 5500 # Check valid substation cost - assert 1e6 <= o.total_substation_cost <= 1e9 + assert 1e6 <= elect.total_substation_cost <= 1e9 + + +def test_detailed_design_length(): + """Ensure that the same # of output variables are used for a floating and fixed offshore substation.""" + + elect = ElectricalDesign(base) + elect.run() + + floating = deepcopy(base) + floating["substation_design"]["oss_substructure_type"] = "Floating" + elect_floating = ElectricalDesign(floating) + elect_floating.run() + + assert len(elect.detailed_output) == len(elect_floating.detailed_output) + + +def test_calc_substructure_mass_and_cost(): + + elect = ElectricalDesign(base) + elect.run() + + floating = deepcopy(base) + floating["substation_design"]["oss_substructure_type"] = "Floating" + elect_floating = ElectricalDesign(floating) + elect_floating.run() + + assert ( + elect.detailed_output["substation_substructure_cost"] + != elect_floating.detailed_output["substation_substructure_cost"] + ) + assert ( + elect.detailed_output["substation_substructure_mass"] + != elect_floating.detailed_output["substation_substructure_mass"] + ) + + +def test_calc_topside_mass_and_cost(): + """Test topside mass and cost for HVDC compared to HVDC-Monopole and HVDC-Bipole""" + + elect = ElectricalDesign(base) + elect.run() + + base_dc = deepcopy(base) + cables = ["HVDC_2000mm_320kV", "HVDC_2500mm_525kV"] + + for cable in cables: + base_dc["export_system_design"]["cables"] = cable + + elect_dc = ElectricalDesign(base_dc) + elect_dc.run() + + assert ( + elect.detailed_output["substation_topside_mass"] + == elect_dc.detailed_output["substation_topside_mass"] + ) + assert ( + elect.detailed_output["substation_topside_cost"] + != elect_dc.detailed_output["substation_topside_cost"] + ) + + +def test_oss_substructure_kwargs(): + test_kwargs = { + "oss_substructure_type": "Floating", + "oss_substructure_cost_rate": 7250, + "oss_pile_cost_rate": 2500, + "num_substations": 4, + } + + elect = ElectricalDesign(base) + elect.run() + base_cost_total = elect.detailed_output["total_substation_cost"] + base_cost_subst = elect.detailed_output["substation_substructure_cost"] + + for k, v in test_kwargs.items(): + config = deepcopy(base) + config["substation_design"] = {} + config["substation_design"][k] = v + + elect = ElectricalDesign(config) + elect.run() + cost_total = elect.detailed_output["total_substation_cost"] + cost_subst = elect.detailed_output["substation_substructure_cost"] + + assert cost_total != base_cost_total + assert cost_subst != base_cost_subst def test_ac_oss_kwargs(): @@ -70,23 +164,21 @@ def test_ac_oss_kwargs(): "workspace_cost": 3e6, "other_ancillary_cost": 4e6, "topside_assembly_factor": 0.09, - "oss_substructure_cost_rate": 7250, - "oss_pile_cost_rate": 2500, "num_substations": 4, } - o = ElectricalDesign(base) - o.run() - base_cost = o.detailed_output["total_substation_cost"] + elect = ElectricalDesign(base) + elect.run() + base_cost = elect.detailed_output["total_substation_cost"] for k, v in test_kwargs.items(): config = deepcopy(base) config["substation_design"] = {} config["substation_design"][k] = v - o = ElectricalDesign(config) - o.run() - cost = o.detailed_output["total_substation_cost"] + elect = ElectricalDesign(config) + elect.run() + cost = elect.detailed_output["total_substation_cost"] print("passed") assert cost != base_cost @@ -96,9 +188,9 @@ def test_dc_oss_kwargs(): dc_base = deepcopy(base) dc_base["export_system_design"]["cables"] = "HVDC_2000mm_320kV" - o = ElectricalDesign(dc_base) - o.run() - base_cost = o.detailed_output["total_substation_cost"] + elect = ElectricalDesign(dc_base) + elect.run() + base_cost = elect.detailed_output["total_substation_cost"] for k, v in test_kwargs.items(): config = deepcopy(base) @@ -106,10 +198,10 @@ def test_dc_oss_kwargs(): config["substation_design"] = {} config["substation_design"][k] = v - o = ElectricalDesign(config) - o.run() - cost = o.detailed_output["total_substation_cost"] - print("passed") + elect = ElectricalDesign(config) + elect.run() + cost = elect.detailed_output["total_substation_cost"] + assert cost != base_cost @@ -146,35 +238,35 @@ def test_new_old_hvac_substation(): def test_hvdc_substation(): config = deepcopy(base) config["export_system_design"] = {"cables": "HVDC_2000mm_320kV"} - o = ElectricalDesign(config) - o.run() - assert o.converter_cost != 0 - assert o.shunt_reactor_cost == 0 - assert o.dc_breaker_cost != 0 - assert o.switchgear_cost == 0 - assert o.mpt_cost == 0 - # assert o.num_cables / o.num_converters == 2 # breaks + elect = ElectricalDesign(config) + elect.run() + assert elect.converter_cost != 0 + assert elect.shunt_reactor_cost == 0 + assert elect.dc_breaker_cost != 0 + assert elect.switchgear_cost == 0 + assert elect.mpt_cost == 0 + # assert elect.num_cables / elect.num_converters == 2 # breaks config = deepcopy(base) config["export_system_design"] = {"cables": "HVDC_2500mm_525kV"} - o = ElectricalDesign(config) - o.run() + elect = ElectricalDesign(config) + elect.run() - # assert o.num_converters == o.num_cables # breaks + # assert elect.num_converters == elect.num_cables # breaks def test_onshore_substation(): config = deepcopy(base) - o = ElectricalDesign(config) - o.run() - assert o.onshore_cost == pytest.approx(95.487e6, abs=1e2) # 109.32e6 + elect = ElectricalDesign(config) + elect.run() + assert elect.onshore_cost == pytest.approx(95.487e6, abs=1e2) # 109.32e6 config_mono = deepcopy(config) config_mono["export_system_design"] = {"cables": "HVDC_2000mm_320kV"} - o_mono = ElectricalDesign(config_mono) - o_mono.run() - assert o_mono.onshore_cost == 244.3e6 + o_monelect = ElectricalDesign(config_mono) + o_monelect.run() + assert o_monelect.onshore_cost == 244.3e6 config_bi = deepcopy(config) config_bi["export_system_design"] = {"cables": "HVDC_2500mm_525kV"} @@ -194,18 +286,18 @@ def test_export_kwargs(): # "interconnection_distance": 6, } - o = ElectricalDesign(base) - o.run() - base_cost = o.total_cost + elect = ElectricalDesign(base) + elect.run() + base_cost = elect.total_cost for k, v in test_kwargs.items(): config = deepcopy(base) config["export_system_design"] = {"cables": "XLPE_630mm_220kV"} config["export_system_design"][k] = v - o = ElectricalDesign(config) - o.run() - cost = o.total_cost + elect = ElectricalDesign(config) + elect.run() + cost = elect.total_cost assert cost != base_cost @@ -263,6 +355,13 @@ def test_total_cable(): assert export.total_length == pytest.approx(length * 9, abs=1e-10) +def test_total_cable_cost(): + export = ElectricalDesign(config) + export.run() + + assert export.total_cable_cost == 135068310.0 + + def test_cables_property(): export = ElectricalDesign(config) export.run() diff --git a/tests/phases/design/test_mooring_system_design.py b/tests/phases/design/test_mooring_system_design.py index 88a7a747..bf0e7021 100644 --- a/tests/phases/design/test_mooring_system_design.py +++ b/tests/phases/design/test_mooring_system_design.py @@ -10,12 +10,16 @@ import pytest -from ORBIT.phases.design import MooringSystemDesign +from ORBIT.phases.design import ( + MooringSystemDesign, + SemiTautMooringSystemDesign, +) base = { "site": {"depth": 200}, "turbine": {"turbine_rating": 6}, "plant": {"num_turbines": 50}, + "mooring_system_design": {}, } @@ -25,40 +29,138 @@ def test_depth_sweep(depth): config = deepcopy(base) config["site"]["depth"] = depth - m = MooringSystemDesign(config) - m.run() + moor = MooringSystemDesign(config) + moor.run() - assert m.design_result - assert m.total_cost + assert moor.design_result + assert moor.total_cost @pytest.mark.parametrize("rating", range(3, 15, 1)) -def test_rating_sweeip(rating): +def test_rating_sweep(rating): config = deepcopy(base) config["turbine"]["turbine_rating"] = rating - m = MooringSystemDesign(config) - m.run() + moor = MooringSystemDesign(config) + moor.run() - assert m.design_result - assert m.total_cost + assert moor.design_result + assert moor.total_cost + + +def test_mooring_system_defaults(): + + moor_base = MooringSystemDesign(base) + moor_base.run() + + base_cost = moor_base.detailed_output["system_cost"] + + config_defs = deepcopy(base) + config_defs["mooring_system_design"] = {} + config_defs["mooring_system_design"]["mooring_type"] = "Catenary" + config_defs["mooring_system_design"]["anchor_type"] = "Suction Pile" + + moor_defs = MooringSystemDesign(config_defs) + moor_defs.run() + + assert moor_defs.detailed_output["system_cost"] == base_cost + + +def test_catenary_mooring_system_kwargs(): + + test_kwargs = { + "num_lines": 6, + "anchor_type": "Drag Embedment", + "mooring_line_cost_rate": 2500, + } + + moor = MooringSystemDesign(base) + moor.run() + + base_cost = moor.detailed_output["system_cost"] + + for k, v in test_kwargs.items(): + config = deepcopy(base) + config["mooring_system_design"] = {} + config["mooring_system_design"][k] = v + + moor = MooringSystemDesign(config) + moor.run() + + assert moor.detailed_output["system_cost"] != base_cost + + +def test_semitaut_mooring_system_kwargs(): + + semi_base = deepcopy(base) + semi_base["mooring_system_design"]["mooring_type"] = "SemiTaut" + + test_kwargs = { + "num_lines": 6, + "anchor_type": "Drag Embedment", + "chain_density": 10000, + "rope_density": 1000, + } + + moor = MooringSystemDesign(semi_base) + moor.run() + + base_cost = moor.detailed_output["system_cost"] + + for k, v in test_kwargs.items(): + config = deepcopy(semi_base) + config["mooring_system_design"] = {} + config["mooring_system_design"][k] = v + + moor = MooringSystemDesign(config) + moor.run() + + assert moor.detailed_output["system_cost"] != base_cost + + +def test_tlp_mooring_system_kwargs(): + + tlp_base = deepcopy(base) + tlp_base["mooring_system_design"]["mooring_type"] = "TLP" + + test_kwargs = { + "num_lines": 6, + "anchor_type": "Drag Embedment", + "mooring_line_cost_rate": 2500, + "draft_depth": 10, + } + + moor = MooringSystemDesign(tlp_base) + moor.run() + + base_cost = moor.detailed_output["system_cost"] + + for k, v in test_kwargs.items(): + config = deepcopy(tlp_base) + config["mooring_system_design"] = {} + config["mooring_system_design"][k] = v + + moor = MooringSystemDesign(config) + moor.run() + + assert moor.detailed_output["system_cost"] != base_cost def test_drag_embedment_fixed_length(): - m = MooringSystemDesign(base) - m.run() + moor = MooringSystemDesign(base) + moor.run() - baseline = m.line_length + baseline = moor.line_length default = deepcopy(base) default["mooring_system_design"] = {"anchor_type": "Drag Embedment"} - m = MooringSystemDesign(default) - m.run() + moor = MooringSystemDesign(default) + moor.run() - with_default = m.line_length + with_default = moor.line_length assert with_default > baseline custom = deepcopy(base) @@ -67,11 +169,11 @@ def test_drag_embedment_fixed_length(): "drag_embedment_fixed_length": 1000, } - m = MooringSystemDesign(custom) - m.run() + moor = MooringSystemDesign(custom) + moor.run() - assert m.line_length > with_default - assert m.line_length > baseline + assert moor.line_length > with_default + assert moor.line_length > baseline def test_custom_num_lines(): @@ -79,7 +181,34 @@ def test_custom_num_lines(): config = deepcopy(base) config["mooring_system_design"] = {"num_lines": 5} - m = MooringSystemDesign(config) - m.run() + moor = MooringSystemDesign(config) + moor.run() + + assert moor.design_result["mooring_system"]["num_lines"] == 5 + - assert m.design_result["mooring_system"]["num_lines"] == 5 +def test_new_old_semitaut_mooring_system(): + """Temporary test until we delete the SemiTaut_mooring_system""" + + config = deepcopy(base) + config["site"]["depth"] = 900.0 + config["mooring_system_design"]["mooring_type"] = "SemiTaut" + config["mooring_system_design"]["anchor_type"] = "Drag Embedment" + + old = SemiTautMooringSystemDesign(config) + old.run() + old_anchor_cost = old.anchor_cost.item() + old_line_cost = old.line_cost.item() + + new = MooringSystemDesign(config) + new.run() + + # same values + assert old.total_cost == new.total_cost + assert old_anchor_cost == new.anchor_cost + assert old.anchor_mass == new.anchor_mass + assert old_line_cost == new.line_cost + assert old.line_length == new.line_length + + # different values + assert len(old.detailed_output) != len(new.detailed_output) diff --git a/tests/phases/design/test_oss_design.py b/tests/phases/design/test_oss_design.py index b2dd6316..b6257e2d 100644 --- a/tests/phases/design/test_oss_design.py +++ b/tests/phases/design/test_oss_design.py @@ -62,6 +62,7 @@ def test_oss_kwargs(): "workspace_cost": 3e6, "other_ancillary_cost": 4e6, "topside_assembly_factor": 0.08, + "oss_substructure_type": "Floating", "oss_substructure_cost_rate": 7250, "oss_pile_cost_rate": 2500, "num_substations": 2, @@ -75,6 +76,7 @@ def test_oss_kwargs(): config = deepcopy(base) config["substation_design"] = {} + config["substation_design"]["oss_pile_cost_rate"] = 1500 config["substation_design"][k] = v o = OffshoreSubstationDesign(config) diff --git a/tests/phases/install/cable_install/test_array_install.py b/tests/phases/install/cable_install/test_array_install.py index 5a01c14b..dc94153e 100644 --- a/tests/phases/install/cable_install/test_array_install.py +++ b/tests/phases/install/cable_install/test_array_install.py @@ -12,7 +12,6 @@ import pandas as pd import pytest - from ORBIT import ProjectManager from tests.data import test_weather from ORBIT.core.library import extract_library_specs @@ -28,7 +27,6 @@ "config", (base_config, simul_config), ids=["separate", "simultaneous"] ) def test_simulation_setup(config): - sim = ArrayCableInstallation(config) assert sim.env @@ -37,7 +35,6 @@ def test_simulation_setup(config): "config", (base_config, simul_config), ids=["separate", "simultaneous"] ) def test_vessel_initialization(config): - sim = ArrayCableInstallation(config) assert sim.install_vessel assert sim.install_vessel.cable_storage @@ -53,7 +50,6 @@ def test_vessel_initialization(config): "weather", (None, test_weather), ids=["no_weather", "test_weather"] ) def test_for_complete_logging(config, weather): - sim = ArrayCableInstallation(config, weather=weather) sim.run() @@ -72,24 +68,21 @@ def test_for_complete_logging(config, weather): def test_simultaneous_speed_kwargs(): - sim = ArrayCableInstallation(simul_config) sim.run() baseline = sim.total_phase_time - key = "cable_lay_bury_speed" - val = pt[key] * 0.1 - - kwargs = {key: val} + sim.install_vessel._vessel_specs["cable_lay_bury_speed"] = ( + sim.install_vessel._vessel_specs["cable_lay_bury_speed"] * 0.1 + ) - sim = ArrayCableInstallation(simul_config, **kwargs) + sim = ArrayCableInstallation(simul_config) sim.run() assert sim.total_phase_time > baseline def test_separate_speed_kwargs(): - sim = ArrayCableInstallation(base_config) sim.run() df = pd.DataFrame(sim.env.actions) @@ -114,7 +107,6 @@ def test_separate_speed_kwargs(): def test_kwargs_for_array_install(): - sim = ArrayCableInstallation(base_config) sim.run() baseline = sim.total_phase_time @@ -131,7 +123,6 @@ def test_kwargs_for_array_install(): failed = [] for kw in keywords: - default = pt[kw] if "speed" in kw: @@ -163,7 +154,6 @@ def test_kwargs_for_array_install(): def test_kwargs_for_array_install_in_ProjectManager(): - base = deepcopy(base_config) base["install_phases"] = ["ArrayCableInstallation"] @@ -183,7 +173,6 @@ def test_kwargs_for_array_install_in_ProjectManager(): failed = [] for kw in keywords: - default = pt[kw] if "speed" in kw: diff --git a/tests/phases/install/cable_install/test_export_install.py b/tests/phases/install/cable_install/test_export_install.py index 36f2dd8e..b509e060 100644 --- a/tests/phases/install/cable_install/test_export_install.py +++ b/tests/phases/install/cable_install/test_export_install.py @@ -29,7 +29,6 @@ "config", (base_config, simul_config), ids=["separate", "simultaneous"] ) def test_simulation_setup(config): - sim = ExportCableInstallation(config) assert sim.env assert sim.cable @@ -43,7 +42,6 @@ def test_simulation_setup(config): "config", (base_config, simul_config), ids=["separate", "simultaneous"] ) def test_vessel_initialization(config): - sim = ExportCableInstallation(config) assert sim.install_vessel assert sim.install_vessel.cable_storage @@ -59,7 +57,6 @@ def test_vessel_initialization(config): "weather", (None, test_weather), ids=["no_weather", "test_weather"] ) def test_for_complete_logging(config, weather): - sim = ExportCableInstallation(config, weather=weather) sim.run() @@ -78,24 +75,21 @@ def test_for_complete_logging(config, weather): def test_simultaneous_speed_kwargs(): - sim = ExportCableInstallation(simul_config) sim.run() baseline = sim.total_phase_time - key = "cable_lay_bury_speed" - val = pt[key] * 0.1 - - kwargs = {key: val} + sim.install_vessel._vessel_specs["cable_lay_bury_speed"] = ( + sim.install_vessel._vessel_specs["cable_lay_bury_speed"] * 0.1 + ) - sim = ExportCableInstallation(simul_config, **kwargs) + sim = ExportCableInstallation(simul_config) sim.run() assert sim.total_phase_time > baseline def test_separate_speed_kwargs(): - sim = ExportCableInstallation(base_config) sim.run() df = pd.DataFrame(sim.env.actions) @@ -120,7 +114,6 @@ def test_separate_speed_kwargs(): def test_kwargs_for_export_install(): - new_export_system = { "cable": {"linear_density": 50.0, "sections": [1000], "number": 1}, "system_cost": 200e6, @@ -151,7 +144,6 @@ def test_kwargs_for_export_install(): failed = [] for kw in keywords: - default = pt[kw] if "speed" in kw: @@ -183,7 +175,6 @@ def test_kwargs_for_export_install(): def test_kwargs_for_export_install_in_ProjectManager(): - new_export_system = { "cable": {"linear_density": 50.0, "sections": [1000], "number": 1}, "system_cost": 200e6, @@ -214,7 +205,6 @@ def test_kwargs_for_export_install_in_ProjectManager(): failed = [] for kw in keywords: - default = pt[kw] if "speed" in kw: diff --git a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py index dafad84b..84c57b9d 100644 --- a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py +++ b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py @@ -5,6 +5,7 @@ __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" +from copy import deepcopy import pandas as pd import pytest @@ -56,3 +57,20 @@ def test_for_complete_logging(weather, config): assert ~df["cost"].isnull().any() _ = sim.agent_efficiencies _ = sim.detailed_output + + +def test_deprecated_vessel(): + + deprecated = deepcopy(config) + deprecated["support_vessel"] = "test_support_vessel" + + with pytest.deprecated_call(): + sim = GravityBasedInstallation(deprecated) + sim.run() + + deprecated2 = deepcopy(config) + deprecated2["towing_vessel_groups"]["station_keeping_vessels"] = 2 + + with pytest.deprecated_call(): + sim = GravityBasedInstallation(deprecated2) + sim.run() diff --git a/tests/phases/install/quayside_assembly_tow/test_moored.py b/tests/phases/install/quayside_assembly_tow/test_moored.py index e1fc0af7..8f36ef4c 100644 --- a/tests/phases/install/quayside_assembly_tow/test_moored.py +++ b/tests/phases/install/quayside_assembly_tow/test_moored.py @@ -5,6 +5,8 @@ __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" +import warnings +from copy import deepcopy import pandas as pd import pytest @@ -16,14 +18,30 @@ config = extract_library_specs("config", "moored_install") no_supply = extract_library_specs("config", "moored_install_no_supply") +multi_assembly = extract_library_specs( + "config", "moored_install_multi_assembly" +) +multi_tow = extract_library_specs("config", "moored_install_multi_tow") +multi_assembly_multi_tow = extract_library_specs( + "config", "moored_install_multi_assembly_multi_tow" +) -def test_simulation_setup(): +@pytest.mark.parametrize( + "config", + (config, multi_assembly, multi_tow, multi_assembly_multi_tow), + ids=[ + "1 assembly, 1 tow", + "3 assembly, 1 tow", + "1 assembly, 3 tow", + "3 assembly, 3 tow", + ], +) +def test_simulation_setup(config): sim = MooredSubInstallation(config) assert sim.config == config assert sim.env - assert sim.support_vessel assert len(sim.sub_assembly_lines) == config["port"]["sub_assembly_lines"] assert ( len(sim.turbine_assembly_lines) @@ -39,9 +57,18 @@ def test_simulation_setup(): @pytest.mark.parametrize( "weather", (None, test_weather), ids=["no_weather", "test_weather"] ) -@pytest.mark.parametrize("config", (config, no_supply)) +@pytest.mark.parametrize( + "config", + (config, multi_assembly, multi_tow, multi_assembly_multi_tow, no_supply), + ids=[ + "1 assembly, 1 tow", + "3 assembly, 1 tow", + "1 assembly, 3 tow", + "3 assembly, 3 tow", + "no supply", + ], +) def test_for_complete_logging(weather, config): - sim = MooredSubInstallation(config, weather=weather) sim.run() @@ -56,3 +83,25 @@ def test_for_complete_logging(weather, config): assert ~df["cost"].isnull().any() _ = sim.agent_efficiencies _ = sim.detailed_output + + installed_mooring_lines = len( + [a for a in sim.env.actions if a["action"] == "Position Substructure"] + ) + assert installed_mooring_lines == sim.num_turbines + + +def test_deprecated_vessel(): + + deprecated = deepcopy(config) + deprecated["support_vessel"] = "test_support_vessel" + + with pytest.deprecated_call(): + sim = MooredSubInstallation(deprecated) + sim.run() + + deprecated2 = deepcopy(config) + deprecated2["towing_vessel_groups"]["station_keeping_vessels"] = 2 + + with pytest.deprecated_call(): + sim = MooredSubInstallation(deprecated2) + sim.run()