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", + " | cost_multiplier | \n", + "agent | \n", + "action | \n", + "duration | \n", + "cost | \n", + "level | \n", + "time | \n", + "phase | \n", + "location | \n", + "phase_name | \n", + "max_waveheight | \n", + "max_windspeed | \n", + "transit_speed | \n", + "num_vessels | \n", + "num_ahts_vessels | \n", + "
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", + "0.5 | \n", + "Array Cable Installation Vessel | \n", + "Mobilize | \n", + "72.000000 | \n", + "3.375000e+05 | \n", + "ACTION | \n", + "0.000000 | \n", + "ArrayCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
1 | \n", + "0.5 | \n", + "Export Cable Installation Vessel | \n", + "Mobilize | \n", + "72.000000 | \n", + "3.375000e+05 | \n", + "ACTION | \n", + "0.000000 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2 | \n", + "NaN | \n", + "Onshore Construction | \n", + "Onshore Construction | \n", + "0.000000 | \n", + "1.665604e+06 | \n", + "ACTION | \n", + "0.000000 | \n", + "ExportCableInstallation | \n", + "Landfall | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
3 | \n", + "1.0 | \n", + "Mooring System Installation Vessel | \n", + "Mobilize | \n", + "168.000000 | \n", + "7.000000e+05 | \n", + "ACTION | \n", + "0.000000 | \n", + "MooringSystemInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4 | \n", + "NaN | \n", + "Substation Assembly Line 1 | \n", + "Substation Substructure Assembly | \n", + "0.000000 | \n", + "0.000000e+00 | \n", + "ACTION | \n", + "0.000000 | \n", + "FloatingSubstationInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "
2988 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Pull In Cable | \n", + "5.500000 | \n", + "5.156250e+04 | \n", + "ACTION | \n", + "12017.280762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2989 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Terminate Cable | \n", + "5.500000 | \n", + "5.156250e+04 | \n", + "ACTION | \n", + "12022.780762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2990 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Transit | \n", + "8.000000 | \n", + "7.500000e+04 | \n", + "ACTION | \n", + "12030.780762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2991 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Delay | \n", + "26.000000 | \n", + "2.437500e+05 | \n", + "ACTION | \n", + "12056.780762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2992 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Transit | \n", + "0.695652 | \n", + "6.521739e+03 | \n", + "ACTION | \n", + "12057.476414 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2993 rows \u00d7 15 columns
\n", + "\n", - " | cost_multiplier | \n", - "agent | \n", - "action | \n", - "duration | \n", - "cost | \n", - "level | \n", - "time | \n", - "phase | \n", - "location | \n", - "site_depth | \n", - "hub_height | \n", - "phase_name | \n", - "max_waveheight | \n", - "max_windspeed | \n", - "transit_speed | \n", - "num_vessels | \n", - "
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", - "0.5 | \n", - "Array Cable Installation Vessel | \n", - "Mobilize | \n", - "72.000000 | \n", - "1.800000e+05 | \n", - "ACTION | \n", - "0.000000 | \n", - "ArrayCableInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
1 | \n", - "0.5 | \n", - "Export Cable Installation Vessel | \n", - "Mobilize | \n", - "72.000000 | \n", - "1.800000e+05 | \n", - "ACTION | \n", - "0.000000 | \n", - "ExportCableInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
2 | \n", - "NaN | \n", - "Onshore Construction | \n", - "Onshore Construction | \n", - "0.000000 | \n", - "1.075454e+08 | \n", - "ACTION | \n", - "0.000000 | \n", - "ExportCableInstallation | \n", - "Landfall | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
3 | \n", - "1.0 | \n", - "Mooring System Installation Vessel | \n", - "Mobilize | \n", - "168.000000 | \n", - "7.000000e+05 | \n", - "ACTION | \n", - "0.000000 | \n", - "MooringSystemInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
4 | \n", - "0.5 | \n", - "Heavy Lift Vessel | \n", - "Mobilize | \n", - "72.000000 | \n", - "7.500000e+05 | \n", - "ACTION | \n", - "0.000000 | \n", - "OffshoreSubstationInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "... | \n", - "
4405 | \n", - "NaN | \n", - "Multi-Purpose Support Vessel | \n", - "Connect Mooring Lines | \n", - "22.000000 | \n", - "9.166667e+04 | \n", - "ACTION | \n", - "8554.500000 | \n", - "MooredSubInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
4406 | \n", - "NaN | \n", - "Multi-Purpose Support Vessel | \n", - "Check Mooring Lines | \n", - "12.000000 | \n", - "5.000000e+04 | \n", - "ACTION | \n", - "8566.500000 | \n", - "MooredSubInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
4407 | \n", - "NaN | \n", - "Towing Group 1 | \n", - "Positioning Support | \n", - "42.000000 | \n", - "1.050000e+05 | \n", - "ACTION | \n", - "8566.500000 | \n", - "MooredSubInstallation | \n", - "site | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "2.0 | \n", - "
4408 | \n", - "NaN | \n", - "Multi-Purpose Support Vessel | \n", - "Transit | \n", - "10.000000 | \n", - "4.166667e+04 | \n", - "ACTION | \n", - "8576.500000 | \n", - "MooredSubInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "
4409 | \n", - "NaN | \n", - "Towing Group 1 | \n", - "Transit | \n", - "16.666667 | \n", - "6.250000e+04 | \n", - "ACTION | \n", - "8583.166667 | \n", - "MooredSubInstallation | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "NaN | \n", - "3.0 | \n", - "
4410 rows × 16 columns
\n", - "\n", + " | cost_multiplier | \n", + "agent | \n", + "action | \n", + "duration | \n", + "cost | \n", + "level | \n", + "time | \n", + "phase | \n", + "location | \n", + "site_depth | \n", + "hub_height | \n", + "phase_name | \n", + "max_waveheight | \n", + "max_windspeed | \n", + "transit_speed | \n", + "num_vessels | \n", + "num_ahts_vessels | \n", + "
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", + "0.5 | \n", + "Array Cable Installation Vessel | \n", + "Mobilize | \n", + "72.000000 | \n", + "3.375000e+05 | \n", + "ACTION | \n", + "0.000000 | \n", + "ArrayCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
1 | \n", + "0.5 | \n", + "Export Cable Installation Vessel | \n", + "Mobilize | \n", + "72.000000 | \n", + "3.375000e+05 | \n", + "ACTION | \n", + "0.000000 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
2 | \n", + "NaN | \n", + "Onshore Construction | \n", + "Onshore Construction | \n", + "0.000000 | \n", + "1.665604e+06 | \n", + "ACTION | \n", + "0.000000 | \n", + "ExportCableInstallation | \n", + "Landfall | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
3 | \n", + "1.0 | \n", + "Mooring System Installation Vessel | \n", + "Mobilize | \n", + "168.000000 | \n", + "7.000000e+05 | \n", + "ACTION | \n", + "0.000000 | \n", + "MooringSystemInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4 | \n", + "NaN | \n", + "Substation Assembly Line 1 | \n", + "Substation Substructure Assembly | \n", + "0.000000 | \n", + "0.000000e+00 | \n", + "ACTION | \n", + "0.000000 | \n", + "FloatingSubstationInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "
4458 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Pull In Cable | \n", + "5.500000 | \n", + "5.156250e+04 | \n", + "ACTION | \n", + "12017.280762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4459 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Terminate Cable | \n", + "5.500000 | \n", + "5.156250e+04 | \n", + "ACTION | \n", + "12022.780762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4460 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Transit | \n", + "8.000000 | \n", + "7.500000e+04 | \n", + "ACTION | \n", + "12030.780762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4461 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Delay | \n", + "26.000000 | \n", + "2.437500e+05 | \n", + "ACTION | \n", + "12056.780762 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4462 | \n", + "NaN | \n", + "Export Cable Installation Vessel | \n", + "Transit | \n", + "0.695652 | \n", + "6.521739e+03 | \n", + "ACTION | \n", + "12057.476414 | \n", + "ExportCableInstallation | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "NaN | \n", + "
4463 rows \u00d7 17 columns
\n", + "