From 8fa9a06b9f5a577527203fc5f10c32b2de835342 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Mon, 10 Feb 2020 15:29:55 -0700 Subject: [PATCH 01/43] Adding initial mooring system design module (from old OffshoreBOS). --- ORBIT/phases/design/mooring_system_design.py | 122 +++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 ORBIT/phases/design/mooring_system_design.py diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py new file mode 100644 index 00000000..a0a38e5f --- /dev/null +++ b/ORBIT/phases/design/mooring_system_design.py @@ -0,0 +1,122 @@ +"""`MooringSystemDesign` and related functionality.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +from ORBIT.phases.design import DesignPhase + + +class MooringSystemDesign(DesignPhase): + """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)", + "anchor_type": "str (optional)", + "mooring_line_cost_rate": "int | float (optional)", + "drag_embedment_fixed_length": "int | float (optional)", + }, + } + + output_config = {"mooring": {"anchor_type": "str", "lines": "int"}} + + def __init__(self, config, **kwargs): + """ + Creates an instance of MooringSystemDesign. + + Parameters + ---------- + config : dict + """ + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self._design = self.config.get("mooring_system_design", {}) + self.num_lines = self.config["plant"]["num_turbines"] + self.extract_defaults() + self._outputs = {} + + def run(self): + """ + Main run function. + """ + + self.determine_mooring_line() + self.calculate_breaking_load() + self.calculate_line_length() + self.calculate_anchor_cost() + + def determine_mooring_line(self): + """ + Returns the diameter of the mooring lines based on the turbine rating. + """ + + tr = self.config["turbine"]["turbine_rating"] + fit = -0.0004 * (tr ** 2) + 0.0132 * tr + 0.0536 + + if fit <= 0.09: + self.line_diam = 0.09 + self.line_cost_rate = 399.0 + + elif fit <= 0.12: + self.line_diam = 0.12 + self.line_cost_rate = 721.0 + + else: + self.line_diam = 0.15 + self.line_cost_rate = 1088.0 + + def calculate_breaking_load(self): + """ + Returns the mooring line breaking load. + """ + + self.breaking_load = ( + 419449 * (self.line_diam ** 2) + 93415 * self.line_diam - 3577.9 + ) + + def calculate_line_length(self): + """ + Returns the mooring line length. + """ + + depth = self.config["site"]["depth"] + fixed = self._design.get("drag_embedment_fixed_length", 0.5) + self.line_length = ( + 0.0002 * (depth ** 2) + 1.264 * depth + 47.776 + fixed + ) + + def calculate_anchor_cost(self): + """ + Returns the cost of drag embedment anchors. + """ + + self.anchor_cost = self.breaking_load / 9.81 / 20.0 * 2000.0 + + def calculate_total_cost(self): + """ + Returns the total cost of the mooring system. + """ + + return self.num_lines * ( + self.anchor_cost + self.line_length * self.line_cost_rate + ) + + @property + def design_result(self): + """ + TODO: + """ + + return { + "num_lines": self.num_lines, + "line_length": self.line_length, + "anchor_cost": self.anchor_cost, + "total_cost": self.calculate_total_cost(), + } From 8bd98cb428ea9c55e7c35860a56ccb79fbfe3610 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Thu, 13 Feb 2020 14:36:28 -0700 Subject: [PATCH 02/43] Adding recent work. --- ORBIT/phases/design/__init__.py | 1 + ORBIT/phases/design/mooring_system_design.py | 33 +++++++++++++-- mooring_research.ipynb | 43 ++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 mooring_research.ipynb diff --git a/ORBIT/phases/design/__init__.py b/ORBIT/phases/design/__init__.py index 15cccc58..2894b6ad 100644 --- a/ORBIT/phases/design/__init__.py +++ b/ORBIT/phases/design/__init__.py @@ -12,4 +12,5 @@ from .array_system_design import ArraySystemDesign, CustomArraySystemDesign from .project_development import ProjectDevelopment from .export_system_design import ExportSystemDesign +from .mooring_system_design import MooringSystemDesign from .scour_protection_design import ScourProtectionDesign diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py index a0a38e5f..8c42d283 100644 --- a/ORBIT/phases/design/mooring_system_design.py +++ b/ORBIT/phases/design/mooring_system_design.py @@ -37,8 +37,9 @@ 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._design = self.config.get("mooring_system_design", {}) - self.num_lines = self.config["plant"]["num_turbines"] + self.num_lines = self._design.get("num_lines", 4) self.extract_defaults() self._outputs = {} @@ -104,8 +105,10 @@ def calculate_total_cost(self): Returns the total cost of the mooring system. """ - return self.num_lines * ( - self.anchor_cost + self.line_length * self.line_cost_rate + return ( + self.num_lines + * self.num_turbines + * (self.anchor_cost + self.line_length * self.line_cost_rate) ) @property @@ -120,3 +123,27 @@ def design_result(self): "anchor_cost": self.anchor_cost, "total_cost": self.calculate_total_cost(), } + + @property + def total_phase_cost(self): + """Returns total phase cost in $USD.""" + + _design = self.config.get("monopile_design", {}) + design_cost = _design.get("design_cost", 0.0) + material_cost = sum([v for _, v in self.material_cost.items()]) + + return design_cost + material_cost + + @property + def total_phase_time(self): + """Returns total phase time in hours.""" + + _design = self.config.get("monopile_design", {}) + phase_time = _design.get("design_time", 0.0) + return phase_time + + @property + def detailed_output(self): + """Returns detailed phase information.""" + + return {} diff --git a/mooring_research.ipynb b/mooring_research.ipynb new file mode 100644 index 00000000..16218363 --- /dev/null +++ b/mooring_research.ipynb @@ -0,0 +1,43 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Jake Nunemaker\n", + "\n", + "National Renewable Energy Lab\n", + "\n", + "2/13/2020" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 988258ac9e4310754ac87bdfb775e2287391b88c Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 18 Mar 2020 16:14:00 -0600 Subject: [PATCH 03/43] Added file structure for tow-out installation modules. --- ORBIT/phases/install/quayside_assembly_tow/__init__.py | 6 ++++++ ORBIT/phases/install/quayside_assembly_tow/common.py | 7 +++++++ ORBIT/phases/install/quayside_assembly_tow/gravity_base.py | 6 ++++++ ORBIT/phases/install/quayside_assembly_tow/moored.py | 6 ++++++ 4 files changed, 25 insertions(+) create mode 100644 ORBIT/phases/install/quayside_assembly_tow/__init__.py create mode 100644 ORBIT/phases/install/quayside_assembly_tow/common.py create mode 100644 ORBIT/phases/install/quayside_assembly_tow/gravity_base.py create mode 100644 ORBIT/phases/install/quayside_assembly_tow/moored.py diff --git a/ORBIT/phases/install/quayside_assembly_tow/__init__.py b/ORBIT/phases/install/quayside_assembly_tow/__init__.py new file mode 100644 index 00000000..f99a0f67 --- /dev/null +++ b/ORBIT/phases/install/quayside_assembly_tow/__init__.py @@ -0,0 +1,6 @@ +"""Quayside assembly and tow-out modules.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py new file mode 100644 index 00000000..e51720cf --- /dev/null +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -0,0 +1,7 @@ +"""Common processes and cargo types for quayside assembly and tow-out +installations""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py new file mode 100644 index 00000000..9fde68ff --- /dev/null +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -0,0 +1,6 @@ +"""Installation strategies for gravity-base substructures.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py new file mode 100644 index 00000000..908056c5 --- /dev/null +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -0,0 +1,6 @@ +"""Installation strategies for moored floating systems.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" From 9b8c564b3dd930236749b0fd65fdac91766f6957 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 25 Mar 2020 22:17:43 -0600 Subject: [PATCH 04/43] Initial commit for MooredSubInstallation class. Working substructure production with multiple assembly lines. --- ORBIT/core/__init__.py | 2 +- ORBIT/core/port.py | 17 ++ ORBIT/phases/install/__init__.py | 1 + .../install/quayside_assembly_tow/__init__.py | 3 + .../install/quayside_assembly_tow/moored.py | 169 ++++++++++++++++++ 5 files changed, 191 insertions(+), 1 deletion(-) diff --git a/ORBIT/core/__init__.py b/ORBIT/core/__init__.py index f9d49b9c..61b8e99e 100644 --- a/ORBIT/core/__init__.py +++ b/ORBIT/core/__init__.py @@ -6,7 +6,7 @@ __email__ = "jake.nunemaker@nrel.gov" -from .port import Port +from .port import Port, WetStorage from .cargo import Cargo from .vessel import Vessel from .components import Crane, JackingSys diff --git a/ORBIT/core/port.py b/ORBIT/core/port.py index e2b9fc53..9223221f 100644 --- a/ORBIT/core/port.py +++ b/ORBIT/core/port.py @@ -63,3 +63,20 @@ def get_item(self, _type): else: res = self.get(lambda x: x == target) return res.value + + +class WetStorage(simpy.Store): + """Storage infrastructure for floating substructures.""" + + def __init__(self, env): # , berths=1): + """ + Creates an instance of WetStorage. + + Parameters + ---------- + berths : int + TODO + Number of substructures that can be stored. + """ + + super().__init__(env) diff --git a/ORBIT/phases/install/__init__.py b/ORBIT/phases/install/__init__.py index 121e069d..d503fec8 100644 --- a/ORBIT/phases/install/__init__.py +++ b/ORBIT/phases/install/__init__.py @@ -10,4 +10,5 @@ from .cable_install import ArrayCableInstallation, ExportCableInstallation from .turbine_install import TurbineInstallation from .monopile_install import MonopileInstallation +from .quayside_assembly_tow import MooredSubInstallation from .scour_protection_install import ScourProtectionInstallation diff --git a/ORBIT/phases/install/quayside_assembly_tow/__init__.py b/ORBIT/phases/install/quayside_assembly_tow/__init__.py index f99a0f67..4c8daac7 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/__init__.py +++ b/ORBIT/phases/install/quayside_assembly_tow/__init__.py @@ -4,3 +4,6 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" + + +from .moored import MooredSubInstallation diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 908056c5..304867e8 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -4,3 +4,172 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" + + +from marmot import Agent, process + +from ORBIT.core import Cargo, WetStorage +from ORBIT.phases.install import InstallPhase + + +class MooredSubInstallation(InstallPhase): + """ + TODO + """ + + phase = "Moored Substructure Installation" + + #: + expected_config = { + "tow_vessel_group": "dict | str", + "substructure": {"takt_time": "int | float"}, + "site": {"depth": "m", "distance": "km"}, + "plant": {"num_turbines": "int"}, + "port": { + "sub_assembly_lines": "int", + "sub_storage_berths": "int", + "turbine_assembly_cranes": "int", + "monthly_rate": "USD/mo (optional)", + "name": "str (optional)", + }, + } + + def __init__(self, config, weather=None, **kwargs): + """ + Creates an instance of MooredSubInstallation. + + Parameters + ---------- + config : dict + Simulation specific configuration. + weather : np.array + Weather data at site. + """ + + super().__init__(weather, **kwargs) + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self.extract_defaults() + + self.setup_simulation(**kwargs) + + def setup_simulation(self, **kwargs): + """ + TODO + """ + + self.initialize_substructure_production() + + def initialize_substructure_production(self): + """ + + """ + + self.wet_storage = WetStorage(self.env) + + time = self.config["substructure"]["takt_time"] + lines = self.config["port"]["sub_assembly_lines"] + num = self.config["plant"]["num_turbines"] + + to_assemble = [1] * num + + for i in range(lines): + a = SubstructureAssemblyLine( + to_assemble, time, self.wet_storage, i + 1 + ) + + self.env.register(a) + a.run() + + def initialize_substructure_storage(self): + """ + + """ + + def initialize_turbine_assembly(self): + """ + + """ + pass + + def initialize_assembly_storage(self): + """ + + """ + pass + + def initialize_towing_groups(self): + """ + + """ + pass + + @property + def detailed_output(self): + """""" + + # TODO: + return {} + + +class FloatingSubstructure: + """""" + + def __init__(self): + """ + TODO + """ + pass + + +class SubstructureAssemblyLine(Agent): + """""" + + def __init__(self, assigned, time, target, num): + """ + Creates an instance of SubstructureAssemblyLine. + + Parameters + ---------- + time : int | float + Hours required to produce one substructure. + target : simpy.Store + Target storage. + num : int + Assembly line number designation. + """ + + super().__init__(f"Substructure Assembly Line {num}") + + self.assigned = assigned + self.time = time + self.target = target + + @process + def assemble_substructure(self): + """ + Simulation process for assembling a substructure. + """ + + yield self.task("Substructure Assembly", self.time) + substructure = FloatingSubstructure() + + start = self.env.now + yield self.target.put(substructure) + delay = self.env.now - start + + if delay > 0: + self.submit_action_log("Delay: No Wet Storage Available") + + @process + def run(self): + """""" + + while True: + try: + _ = self.assigned.pop(0) + yield self.assemble_substructure() + + except IndexError: + break From 6b6cbeff3f10c6594da59e168bd194d62231aaf6 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Mon, 30 Mar 2020 12:22:56 -0600 Subject: [PATCH 05/43] Moved substructure assembly to common. Added tests for substructure assembly. Expanded test structure for new modules. --- .../install/quayside_assembly_tow/common.py | 70 ++++++++++++++++ .../install/quayside_assembly_tow/moored.py | 83 ++++--------------- .../library/project/config/gbf_install.yaml | 0 .../project/config/moored_install.yaml | 0 .../quayside_assembly_tow/test_common.py | 41 +++++++++ .../test_gravity_based.py | 0 .../quayside_assembly_tow/test_moored.py | 17 ++++ 7 files changed, 143 insertions(+), 68 deletions(-) create mode 100644 tests/data/library/project/config/gbf_install.yaml create mode 100644 tests/data/library/project/config/moored_install.yaml create mode 100644 tests/phases/install/quayside_assembly_tow/test_common.py create mode 100644 tests/phases/install/quayside_assembly_tow/test_gravity_based.py create mode 100644 tests/phases/install/quayside_assembly_tow/test_moored.py diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index e51720cf..6b56043e 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -5,3 +5,73 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" + + +from marmot import Agent, process + + +class Substructure: + """Floating Substructure Class.""" + + def __init__(self): + """Creates an instance of `Substructure`.""" + + pass + + +class SubstructureAssemblyLine(Agent): + """Substructure Assembly Line Class.""" + + def __init__(self, assigned, time, target, num): + """ + Creates an instance of `SubstructureAssemblyLine`. + + Parameters + ---------- + assigned : list + List of assigned tasks. Can be shared with other assembly lines. + time : int | float + Hours required to produce one substructure. + target : simpy.Store + Target storage. + num : int + Assembly line number designation. + """ + + super().__init__(f"Substructure Assembly Line {num}") + + self.assigned = assigned + self.time = time + self.target = target + + @process + def assemble_substructure(self): + """ + Simulation process for assembling a substructure. + """ + + yield self.task("Substructure Assembly", self.time) + substructure = Substructure() + + start = self.env.now + yield self.target.put(substructure) + delay = self.env.now - start + + if delay > 0: + self.submit_action_log("Delay: No Wet Storage Available") + + @process + def start(self): + """ + Trigger the assembly line to run. Will attempt to pull a task from + self.assigned and timeout for the assembly time. Shuts down after + self.assigned is empty. + """ + + while True: + try: + _ = self.assigned.pop(0) + yield self.assemble_substructure() + + except IndexError: + break diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 304867e8..27b8a174 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -6,15 +6,16 @@ __email__ = "jake.nunemaker@nrel.gov" -from marmot import Agent, process - -from ORBIT.core import Cargo, WetStorage +from ORBIT.core import WetStorage from ORBIT.phases.install import InstallPhase +from .common import SubstructureAssemblyLine + class MooredSubInstallation(InstallPhase): """ - TODO + Installation module to model the quayside assembly, tow-out and + installation at sea of moored substructures. """ phase = "Moored Substructure Installation" @@ -56,14 +57,22 @@ def __init__(self, config, weather=None, **kwargs): def setup_simulation(self, **kwargs): """ - TODO + Sets up simulation infrastructure. + - Initializes substructure production + - Initializes turbine assembly processes + - Initializes towing groups """ self.initialize_substructure_production() def initialize_substructure_production(self): """ + Initializes the production of substructures at port. The number of + independent assembly lines and production time associated with a + substructure can be configured with the following parameters: + - self.config["substructure"]["takt_time"] + - self.config["port"]["sub_assembly_lines"] """ self.wet_storage = WetStorage(self.env) @@ -80,7 +89,7 @@ def initialize_substructure_production(self): ) self.env.register(a) - a.run() + a.start() def initialize_substructure_storage(self): """ @@ -111,65 +120,3 @@ def detailed_output(self): # TODO: return {} - - -class FloatingSubstructure: - """""" - - def __init__(self): - """ - TODO - """ - pass - - -class SubstructureAssemblyLine(Agent): - """""" - - def __init__(self, assigned, time, target, num): - """ - Creates an instance of SubstructureAssemblyLine. - - Parameters - ---------- - time : int | float - Hours required to produce one substructure. - target : simpy.Store - Target storage. - num : int - Assembly line number designation. - """ - - super().__init__(f"Substructure Assembly Line {num}") - - self.assigned = assigned - self.time = time - self.target = target - - @process - def assemble_substructure(self): - """ - Simulation process for assembling a substructure. - """ - - yield self.task("Substructure Assembly", self.time) - substructure = FloatingSubstructure() - - start = self.env.now - yield self.target.put(substructure) - delay = self.env.now - start - - if delay > 0: - self.submit_action_log("Delay: No Wet Storage Available") - - @process - def run(self): - """""" - - while True: - try: - _ = self.assigned.pop(0) - yield self.assemble_substructure() - - except IndexError: - break diff --git a/tests/data/library/project/config/gbf_install.yaml b/tests/data/library/project/config/gbf_install.yaml new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/library/project/config/moored_install.yaml b/tests/data/library/project/config/moored_install.yaml new file mode 100644 index 00000000..e69de29b diff --git a/tests/phases/install/quayside_assembly_tow/test_common.py b/tests/phases/install/quayside_assembly_tow/test_common.py new file mode 100644 index 00000000..05b35852 --- /dev/null +++ b/tests/phases/install/quayside_assembly_tow/test_common.py @@ -0,0 +1,41 @@ +"""Tests for common infrastructure for quayside assembly tow-out simulations""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +import pytest + +from ORBIT.core import WetStorage +from ORBIT.phases.install.quayside_assembly_tow.common import ( + SubstructureAssemblyLine, +) + + +@pytest.mark.parametrize( + "num, assigned, expected", + [ + (1, [], 0), + (1, [1] * 10, 100), + (2, [1] * 10, 50), + (3, [1] * 10, 40), + (5, [1] * 10, 20), + (10, [1] * 10, 10), + ], +) +def test_SubstructureAssemblyLine(env, num, assigned, expected): + + _assigned = len(assigned) + storage = WetStorage(env) + + for a in range(num): + assembly = SubstructureAssemblyLine(assigned, 10, storage, a + 1) + env.register(assembly) + assembly.start() + + env.run() + + assert len(env.actions) == _assigned + assert env.now == expected diff --git a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/phases/install/quayside_assembly_tow/test_moored.py b/tests/phases/install/quayside_assembly_tow/test_moored.py new file mode 100644 index 00000000..f24f6dfe --- /dev/null +++ b/tests/phases/install/quayside_assembly_tow/test_moored.py @@ -0,0 +1,17 @@ +"""Tests for the `MooredSubInstallation` class and related infrastructure.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +from copy import deepcopy + +import pytest + +from tests.data import test_weather +from ORBIT.library import initialize_library, extract_library_specs +from ORBIT.phases.install import MooredSubInstallation + +initialize_library(pytest.library) From ec11badd3f72e270ae121b54c95ae6e0236f0a16 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 31 Mar 2020 11:25:49 -0600 Subject: [PATCH 06/43] Added turbine assembly lines + tests to moored/gbf installations. --- ORBIT/core/port.py | 9 +- .../install/quayside_assembly_tow/common.py | 133 +++++++++++++++++- .../install/quayside_assembly_tow/moored.py | 34 +++-- .../quayside_assembly_tow/test_common.py | 78 +++++++++- 4 files changed, 233 insertions(+), 21 deletions(-) diff --git a/ORBIT/core/port.py b/ORBIT/core/port.py index 9223221f..dbfc152a 100644 --- a/ORBIT/core/port.py +++ b/ORBIT/core/port.py @@ -68,15 +68,14 @@ def get_item(self, _type): class WetStorage(simpy.Store): """Storage infrastructure for floating substructures.""" - def __init__(self, env): # , berths=1): + def __init__(self, env, capacity): """ Creates an instance of WetStorage. Parameters ---------- - berths : int - TODO - Number of substructures that can be stored. + capacity : int + Number of substructures or assemblies that can be stored. """ - super().__init__(env) + super().__init__(env, capacity) diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index 6b56043e..cccd7277 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, process +from marmot import Agent, le, process class Substructure: @@ -58,7 +58,7 @@ def assemble_substructure(self): delay = self.env.now - start if delay > 0: - self.submit_action_log("Delay: No Wet Storage Available") + self.submit_action_log("Delay: No Wet Storage Available", delay) @process def start(self): @@ -75,3 +75,132 @@ def start(self): except IndexError: break + + +class TurbineAssemblyLine(Agent): + """Turbine Assembly Line Class.""" + + def __init__(self, feed, target, turbine, num): + """ + Creates an instance of `TurbineAssemblyLine`. + + Parameters + ---------- + feed : simpy.Store + Storage for completed substructures. + target : simpy.Store + Target storage. + num : int + Assembly line number designation. + """ + + super().__init__(f"Turbine Assembly Line {num}") + + self.feed = feed + self.target = target + self.turbine = turbine + + @process + def start(self): + """ + Trigger the assembly line to run. Will attempt to pull a task from + self.assigned and timeout for the assembly time. Shuts down after + self.assigned is empty. + """ + + while True: + start = self.env.now + sub = yield self.feed.get() + delay = self.env.now - start + + if delay > 0: + self.submit_action_log( + "Delay: No Substructures in Wet Storage", delay + ) + + yield self.assemble_turbine() + + @process + def assemble_turbine(self): + """ + Turbine assembly process. Follows a similar process as the + `TurbineInstallation` modules but has fixed lift times + fasten times + instead of calculating the lift times dynamically. + """ + + yield self.prepare_for_assembly() + + sections = self.turbine["tower"].get("sections", 1) + for _ in range(sections): + yield self.lift_and_attach_tower_section() + + yield self.lift_and_attach_nacelle() + + for _ in range(3): + yield self.lift_and_attach_blade() + + yield self.mechanical_completion() + + start = self.env.now + yield self.target.put(1) + delay = self.env.now - start + + if delay > 0: + self.submit_action_log( + "Delay: No Assembly Storage Available", delay + ) + + self.submit_debug_log("Assembly delievered to installation groups.") + + @process + def prepare_for_assembly(self): + """ + Task representing time associated with preparing a substructure for + turbine assembly. + """ + + yield self.task("Prepare for Turbine Assembly", 12) + + @process + def lift_and_attach_tower_section(self): + """ + Task representing time associated with lifting and attaching a tower + section at quayside. + """ + + yield self.task( + "Lift and Attach Tower Section", + 7, + constraints={"windspeed": le(15)}, + ) + + @process + def lift_and_attach_nacelle(self): + """ + Task representing time associated with lifting and attaching a nacelle + at quayside. + """ + + yield self.task( + "Lift and Attach Nacelle", 7, constraints={"windspeed": le(15)} + ) + + @process + def lift_and_attach_blade(self): + """ + Task representing time associated with lifting and attaching a turbine + blade at quayside. + """ + + yield self.task( + "Lift and Attach Blade", 3.5, constraints={"windspeed": le(15)} + ) + + @process + def mechanical_completion(self): + """ + Task representing time associated with performing mechanical compltion + work at quayside. + """ + + yield self.task("Mechanical Completion", 24) diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 27b8a174..81bff5cb 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -9,7 +9,7 @@ from ORBIT.core import WetStorage from ORBIT.phases.install import InstallPhase -from .common import SubstructureAssemblyLine +from .common import TurbineAssemblyLine, SubstructureAssemblyLine class MooredSubInstallation(InstallPhase): @@ -26,10 +26,12 @@ class MooredSubInstallation(InstallPhase): "substructure": {"takt_time": "int | float"}, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, + "turbine": "dict", "port": { "sub_assembly_lines": "int", - "sub_storage_berths": "int", + "sub_storage": "int (optional, default: 2)", "turbine_assembly_cranes": "int", + "assembly_storage": "int (optional, default: 2)", "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, @@ -64,6 +66,7 @@ def setup_simulation(self, **kwargs): """ self.initialize_substructure_production() + self.initialize_turbine_assembly() def initialize_substructure_production(self): """ @@ -75,7 +78,8 @@ def initialize_substructure_production(self): - self.config["port"]["sub_assembly_lines"] """ - self.wet_storage = WetStorage(self.env) + storage = self.config["port"].get("sub_storage", 2) + self.wet_storage = WetStorage(self.env, storage) time = self.config["substructure"]["takt_time"] lines = self.config["port"]["sub_assembly_lines"] @@ -91,22 +95,26 @@ def initialize_substructure_production(self): self.env.register(a) a.start() - def initialize_substructure_storage(self): - """ - - """ - def initialize_turbine_assembly(self): """ + Initializes turbine assembly lines. The number of independent lines + can be configured with the following parameters: + - self.config["port"]["turb_assembly_lines"] """ - pass - def initialize_assembly_storage(self): - """ + storage = self.config["port"].get("assembly_storage", 2) + self.assembly_storage = WetStorage(self.env, storage) - """ - pass + lines = self.config["port"]["turbine_assembly_cranes"] + turbine = self.config["turbine"] + for i in range(lines): + a = TurbineAssemblyLine( + self.wet_storage, self.assembly_storage, turbine, i + 1 + ) + + self.env.register(a) + a.start() def initialize_towing_groups(self): """ diff --git a/tests/phases/install/quayside_assembly_tow/test_common.py b/tests/phases/install/quayside_assembly_tow/test_common.py index 05b35852..00fa2b4e 100644 --- a/tests/phases/install/quayside_assembly_tow/test_common.py +++ b/tests/phases/install/quayside_assembly_tow/test_common.py @@ -6,10 +6,12 @@ __email__ = "jake.nunemaker@nrel.gov" +import pandas as pd import pytest from ORBIT.core import WetStorage from ORBIT.phases.install.quayside_assembly_tow.common import ( + TurbineAssemblyLine, SubstructureAssemblyLine, ) @@ -28,7 +30,7 @@ def test_SubstructureAssemblyLine(env, num, assigned, expected): _assigned = len(assigned) - storage = WetStorage(env) + storage = WetStorage(env, capacity=float("inf")) for a in range(num): assembly = SubstructureAssemblyLine(assigned, 10, storage, a + 1) @@ -39,3 +41,77 @@ def test_SubstructureAssemblyLine(env, num, assigned, expected): assert len(env.actions) == _assigned assert env.now == expected + + +@pytest.mark.parametrize( + "num, assigned", + [ + (1, [1] * 10), + (2, [1] * 10), + (3, [1] * 10), + (5, [1] * 10), + (10, [1] * 10), + ], +) +def test_TurbineAssemblyLine(env, num, assigned): + + _assigned = len(assigned) + feed = WetStorage(env, capacity=float("inf")) + target = WetStorage(env, capacity=float("inf")) + + for i in assigned: + feed.put(0) + + for a in range(num): + assembly = TurbineAssemblyLine( + feed, target, {"tower": {"sections": 1}}, a + 1 + ) + env.register(assembly) + assembly.start() + + env.run() + + df = pd.DataFrame(env.actions) + assert len(df.loc[df["action"] == "Mechanical Completion"]) == len( + assigned + ) + + +@pytest.mark.parametrize( + "sub_lines, turb_lines", + [ + (1, 1), + (1, 10), + (1, 100), + (10, 1), + (10, 10), + (10, 100), + (100, 1), + (100, 10), + (100, 100), + ], +) +def test_Sub_to_Turbine_assembly_interaction(env, sub_lines, turb_lines): + + num_turbines = 50 + assigned = [1] * num_turbines + + feed = WetStorage(env, capacity=2) + target = WetStorage(env, capacity=float("inf")) + + for a in range(sub_lines): + assembly = SubstructureAssemblyLine(assigned, 10, feed, a + 1) + env.register(assembly) + assembly.start() + + for a in range(turb_lines): + assembly = TurbineAssemblyLine( + feed, target, {"tower": {"sections": 1}}, a + 1 + ) + env.register(assembly) + assembly.start() + + env.run() + + df = pd.DataFrame(env.actions) + assert len(df.loc[df["action"] == "Mechanical Completion"]) == num_turbines From a4224dc7620e356c86602ae004be8ec125828524 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 31 Mar 2020 17:47:48 -0600 Subject: [PATCH 07/43] Added preliminary completed logic for MooredSubInstallation. --- ORBIT/library.py | 3 +- .../install/quayside_assembly_tow/common.py | 4 +- .../install/quayside_assembly_tow/moored.py | 70 +++++++++++++++++-- library/vessels/example_towing_vessel.yaml | 10 +++ 4 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 library/vessels/example_towing_vessel.yaml diff --git a/ORBIT/library.py b/ORBIT/library.py index ec6c2d77..40203471 100644 --- a/ORBIT/library.py +++ b/ORBIT/library.py @@ -241,7 +241,7 @@ def export_library_specs(key, filename, data, file_ext="yaml"): return if file_ext == "yaml": f = open(data_path, "w") - yaml.dump( data, f, Dumper=Dumper, default_flow_style=False ) + yaml.dump(data, f, Dumper=Dumper, default_flow_style=False) f.close() elif file_ext == "csv": with open(data_path, "w") as f: @@ -263,6 +263,7 @@ def export_library_specs(key, filename, data, file_ext="yaml"): "trench_dig_vessel": "vessels", "feeder": "vessels", "wtiv": "vessels", + "towing_vessel": "vessels", # cables "cables": "cables", "array_system": "cables", diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index cccd7277..6d3da32d 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -150,7 +150,9 @@ def assemble_turbine(self): "Delay: No Assembly Storage Available", delay ) - self.submit_debug_log("Assembly delievered to installation groups.") + self.submit_debug_log( + message="Assembly delievered to installation groups." + ) @process def prepare_for_assembly(self): diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 81bff5cb..08a1dedc 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -6,7 +6,11 @@ __email__ = "jake.nunemaker@nrel.gov" -from ORBIT.core import WetStorage +from copy import deepcopy + +from marmot import process + +from ORBIT.core import Vessel, WetStorage from ORBIT.phases.install import InstallPhase from .common import TurbineAssemblyLine, SubstructureAssemblyLine @@ -22,7 +26,11 @@ class MooredSubInstallation(InstallPhase): #: expected_config = { - "tow_vessel_group": "dict | str", + "towing_vessel": "str", + "towing_vessel_groups": { + "vessels_per_group": "int", + "num_groups": "int", + }, "substructure": {"takt_time": "int | float"}, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, @@ -67,6 +75,7 @@ def setup_simulation(self, **kwargs): self.initialize_substructure_production() self.initialize_turbine_assembly() + self.initialize_towing_groups() def initialize_substructure_production(self): """ @@ -116,11 +125,31 @@ def initialize_turbine_assembly(self): self.env.register(a) a.start() - def initialize_towing_groups(self): + def initialize_towing_groups(self, **kwargs): """ - + Initializes towing groups to bring completed assemblies to site and + stabilize the assembly during final installation. """ - pass + + distance = self.config["site"]["distance"] + self.installation_groups = [] + + vessel = self.config["towing_vessel"] + group = deepcopy(vessel) + num_groups = self.config["towing_vessel_groups"]["num_groups"] + num_vessels = self.config["towing_vessel_groups"]["vessels_per_group"] + + for i in range(num_groups): + group["vessel_specs"]["day_rate"] *= num_vessels + + g = Vessel(f"Towing Group {i + 1}", group) + self.env.register(g) + g.initialize() + self.installation_groups.append(g) + + install_moored_substructures_from_storage( + g, self.assembly_storage, distance, **kwargs + ) @property def detailed_output(self): @@ -128,3 +157,34 @@ def detailed_output(self): # TODO: return {} + + +@process +def install_moored_substructures_from_storage(group, feed, distance, **kwargs): + """ + Process logic for the towing vessel group. + + Parameters + ---------- + group : Vessel + Towing group. + feed : simpy.Store + Completed assembly storage. + distance : int | float + Distance from port to site. + """ + + 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.transit(distance) + yield group.task("Site Operations", 10) + yield group.transit(distance) diff --git a/library/vessels/example_towing_vessel.yaml b/library/vessels/example_towing_vessel.yaml new file mode 100644 index 00000000..39693ca3 --- /dev/null +++ b/library/vessels/example_towing_vessel.yaml @@ -0,0 +1,10 @@ +transport_specs: + max_waveheight: 2.5 # m + max_windspeed: 20 # m/s + transit_speed: 6 # km/h +vessel_specs: + beam_length: 35 # m + day_rate: 40000 # USD/day + max_draft: 5 # m + min_draft: 4 # m + overall_length: 60 # m From 1fe9e3acad12214a83898cace7587c1af4f06017 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Thu, 2 Apr 2020 17:07:03 -0600 Subject: [PATCH 08/43] Added TowingGroup to common.py. Used for adjusting number of vessels needed for towing/stabilization operations. --- .../install/quayside_assembly_tow/common.py | 95 +++++++++++++++++++ .../install/quayside_assembly_tow/moored.py | 48 +++++++--- 2 files changed, 128 insertions(+), 15 deletions(-) diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index 6d3da32d..48c5b2c4 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -206,3 +206,98 @@ def mechanical_completion(self): """ yield self.task("Mechanical Completion", 24) + + +class TowingGroup(Agent): + """Class to represent an arbitrary group of towing vessels.""" + + def __init__(self, vessel_specs, num=1): + """ + Creates an instance of TowingGroup. + + Parameters + ---------- + vessel_specs : dict + Specs for the individual vessels used in the towing group. + Currently restricted to one vessel specification per group. + """ + + super().__init__(f"Towing Group {num}") + self._specs = vessel_specs + self.day_rate = self._specs["vessel_specs"]["day_rate"] + + def initialize(self): + """Initializes the towing group.""" + + self.submit_debug_log(message="{self.name} initialized.") + + @process + def group_task(self, name, duration, num_vessels, **kwargs): + """ + Submits a group task with any number of towing vessels. + + Parameters + ---------- + name : str + Name of task to complete. Used for submitting action logs. + duration : float | int + Duration of the task. + Rounded up to the nearest int. + num_vessels : int + Number of individual towing vessels needed for the operation. + """ + + kwargs = {**kwargs, "num_vessels": num_vessels} + yield self.task(name, duration, **kwargs) + + def operation_cost(self, hours, **kwargs): + """ + Returns cost of an operation of duration `hours` using number of + vessels, `num_vessels`. + + Parameters + ---------- + hours : int | float + Duration of operation in hours. + vessels : int + Default: 1 + """ + + mult = kwargs.get("cost_multiplier", 1.0) + vessels = kwargs.get("num_vessels", 1) + return (self.day_rate / 24) * vessels * hours * mult + + def submit_action_log(self, action, duration, **kwargs): + """ + Submits a log representing a completed `action` performed over time + `duration`. + + This method overwrites the default `submit_action_log` in + `marmot.Agent`, adding operation cost to every submitted log within + ORBIT. + + Parameters + ---------- + action : str + Performed action. + duration : int | float + Duration of action. + + Raises + ------ + AgentNotRegistered + """ + + if self.env is None: + raise AgentNotRegistered(self) + + else: + payload = { + **kwargs, + "agent": str(self), + "action": action, + "duration": float(duration), + "cost": self.operation_cost(duration, **kwargs), + } + + self.env._submit_log(payload, level="ACTION") diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 08a1dedc..c4077ae5 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -6,14 +6,12 @@ __email__ = "jake.nunemaker@nrel.gov" -from copy import deepcopy - from marmot import process -from ORBIT.core import Vessel, WetStorage +from ORBIT.core import WetStorage from ORBIT.phases.install import InstallPhase -from .common import TurbineAssemblyLine, SubstructureAssemblyLine +from .common import TowingGroup, TurbineAssemblyLine, SubstructureAssemblyLine class MooredSubInstallation(InstallPhase): @@ -28,7 +26,8 @@ class MooredSubInstallation(InstallPhase): expected_config = { "towing_vessel": "str", "towing_vessel_groups": { - "vessels_per_group": "int", + "vessels_for_towing": "int", + "vessels_for_stabilization": "int", "num_groups": "int", }, "substructure": {"takt_time": "int | float"}, @@ -135,20 +134,25 @@ def initialize_towing_groups(self, **kwargs): self.installation_groups = [] vessel = self.config["towing_vessel"] - group = deepcopy(vessel) num_groups = self.config["towing_vessel_groups"]["num_groups"] - num_vessels = self.config["towing_vessel_groups"]["vessels_per_group"] + towing = self.config["towing_vessel_groups"]["vessels_for_towing"] + stabilization = self.config["towing_vessel_groups"][ + "vessels_for_stabilization" + ] for i in range(num_groups): - group["vessel_specs"]["day_rate"] *= num_vessels - - g = Vessel(f"Towing Group {i + 1}", group) + g = TowingGroup(vessel, num=i + 1) self.env.register(g) g.initialize() self.installation_groups.append(g) install_moored_substructures_from_storage( - g, self.assembly_storage, distance, **kwargs + g, + self.assembly_storage, + distance, + towing, + stabilization, + **kwargs, ) @property @@ -160,7 +164,14 @@ def detailed_output(self): @process -def install_moored_substructures_from_storage(group, feed, distance, **kwargs): +def install_moored_substructures_from_storage( + group, + feed, + distance, + vessels_for_towing, + vessels_for_stabilization, + **kwargs, +): """ Process logic for the towing vessel group. @@ -172,6 +183,11 @@ def install_moored_substructures_from_storage(group, feed, distance, **kwargs): Completed assembly storage. distance : int | float Distance from port to site. + vessels_for_towing : int + Number of vessels to use for towing to site. + vessels_for_stabilization : int + Number of vessels to use for substructure stabilization during final + installation at site. """ while True: @@ -185,6 +201,8 @@ def install_moored_substructures_from_storage(group, feed, distance, **kwargs): "Delay: No Completed Assemblies Available", delay ) - yield group.transit(distance) - yield group.task("Site Operations", 10) - yield group.transit(distance) + yield group.group_task("Transit", 10, num_vessels=vessels_for_towing) + yield group.group_task( + "Installation", 10, num_vessels=vessels_for_stabilization + ) + yield group.group_task("Transit", 10, num_vessels=vessels_for_towing) From d2e6ee3df7f5593481355d9333cc7f8d60c43ef1 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Fri, 3 Apr 2020 11:06:03 -0600 Subject: [PATCH 09/43] Updated defaults. Added operational_delay outputs. --- .../install/quayside_assembly_tow/moored.py | 105 +++++++++++++----- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index c4077ae5..11cba10e 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -26,19 +26,19 @@ class MooredSubInstallation(InstallPhase): expected_config = { "towing_vessel": "str", "towing_vessel_groups": { - "vessels_for_towing": "int", - "vessels_for_stabilization": "int", - "num_groups": "int", + "towing_vessels": "int", + "stabilization_vessels": "int", + "num_groups": "int (optional)", }, - "substructure": {"takt_time": "int | float"}, + "substructure": {"takt_time": "int | float (optional, default: 0)"}, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, "turbine": "dict", "port": { - "sub_assembly_lines": "int", - "sub_storage": "int (optional, default: 2)", - "turbine_assembly_cranes": "int", - "assembly_storage": "int (optional, default: 2)", + "sub_assembly_lines": "int (optional, default: 1)", + "sub_storage": "int (optional, default: inf)", + "turbine_assembly_cranes": "int (optional, default: 1)", + "assembly_storage": "int (optional, default: inf)", "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, @@ -86,15 +86,30 @@ def initialize_substructure_production(self): - self.config["port"]["sub_assembly_lines"] """ - storage = self.config["port"].get("sub_storage", 2) + try: + storage = self.config["port"]["sub_storage"] + + except KeyError: + storage = float("inf") + self.wet_storage = WetStorage(self.env, storage) - time = self.config["substructure"]["takt_time"] - lines = self.config["port"]["sub_assembly_lines"] - num = self.config["plant"]["num_turbines"] + try: + time = self.config["substructure"]["takt_time"] + + except KeyError: + time = 0 + try: + lines = self.config["port"]["sub_assembly_lines"] + + except KeyError: + lines = 1 + + num = self.config["plant"]["num_turbines"] to_assemble = [1] * num + self.sub_assembly_lines = [] for i in range(lines): a = SubstructureAssemblyLine( to_assemble, time, self.wet_storage, i + 1 @@ -102,6 +117,7 @@ def initialize_substructure_production(self): self.env.register(a) a.start() + self.sub_assembly_lines.append(a) def initialize_turbine_assembly(self): """ @@ -111,11 +127,22 @@ def initialize_turbine_assembly(self): - self.config["port"]["turb_assembly_lines"] """ - storage = self.config["port"].get("assembly_storage", 2) + try: + storage = self.config["port"]["assembly_storage"] + + except KeyError: + storage = float("inf") + self.assembly_storage = WetStorage(self.env, storage) - lines = self.config["port"]["turbine_assembly_cranes"] + try: + lines = self.config["port"]["turbine_assembly_cranes"] + + except KeyError: + lines = 1 + turbine = self.config["turbine"] + self.turbine_assembly_lines = [] for i in range(lines): a = TurbineAssemblyLine( self.wet_storage, self.assembly_storage, turbine, i + 1 @@ -123,6 +150,7 @@ def initialize_turbine_assembly(self): self.env.register(a) a.start() + self.turbine_assembly_lines.append(a) def initialize_towing_groups(self, **kwargs): """ @@ -134,10 +162,10 @@ def initialize_towing_groups(self, **kwargs): self.installation_groups = [] vessel = self.config["towing_vessel"] - num_groups = self.config["towing_vessel_groups"]["num_groups"] - towing = self.config["towing_vessel_groups"]["vessels_for_towing"] + num_groups = self.config["towing_vessel_groups"].get("num_groups", 1) + towing = self.config["towing_vessel_groups"]["towing_vessels"] stabilization = self.config["towing_vessel_groups"][ - "vessels_for_stabilization" + "stabilization_vessels" ] for i in range(num_groups): @@ -159,18 +187,35 @@ def initialize_towing_groups(self, **kwargs): def detailed_output(self): """""" - # TODO: - return {} + return { + "operational_delays": { + **{ + k: self.operational_delay(str(k)) + for k in self.sub_assembly_lines + }, + **{ + k: self.operational_delay(str(k)) + for k in self.turbine_assembly_lines + }, + **{ + k: self.operational_delay(str(k)) + for k in self.installation_groups + }, + } + } + + def operational_delay(self, name): + """""" + + 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"]) + + return delay @process def install_moored_substructures_from_storage( - group, - feed, - distance, - vessels_for_towing, - vessels_for_stabilization, - **kwargs, + group, feed, distance, towing_vessels, stabilization_vessels, **kwargs ): """ Process logic for the towing vessel group. @@ -183,9 +228,9 @@ def install_moored_substructures_from_storage( Completed assembly storage. distance : int | float Distance from port to site. - vessels_for_towing : int + towing_vessels : int Number of vessels to use for towing to site. - vessels_for_stabilization : int + stabilization_vessels : int Number of vessels to use for substructure stabilization during final installation at site. """ @@ -201,8 +246,8 @@ def install_moored_substructures_from_storage( "Delay: No Completed Assemblies Available", delay ) - yield group.group_task("Transit", 10, num_vessels=vessels_for_towing) + yield group.group_task("Transit", 10, num_vessels=towing_vessels) yield group.group_task( - "Installation", 10, num_vessels=vessels_for_stabilization + "Installation", 10, num_vessels=stabilization_vessels ) - yield group.group_task("Transit", 10, num_vessels=vessels_for_towing) + yield group.group_task("Transit", 10, num_vessels=towing_vessels) From 2705d3920f0a5b010c355835f3d1be90da80009d Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Mon, 6 Apr 2020 14:53:32 -0600 Subject: [PATCH 10/43] Added multi-purpose support vessel to moored substructure installation. --- ORBIT/library.py | 1 + .../install/quayside_assembly_tow/common.py | 8 +- .../quayside_assembly_tow/gravity_base.py | 247 ++++++++++++++++++ .../install/quayside_assembly_tow/moored.py | 177 +++++++++++-- library/vessels/example_support_vessel.yaml | 9 + 5 files changed, 419 insertions(+), 23 deletions(-) create mode 100644 library/vessels/example_support_vessel.yaml diff --git a/ORBIT/library.py b/ORBIT/library.py index 40203471..71b8e692 100644 --- a/ORBIT/library.py +++ b/ORBIT/library.py @@ -264,6 +264,7 @@ def export_library_specs(key, filename, data, file_ext="yaml"): "feeder": "vessels", "wtiv": "vessels", "towing_vessel": "vessels", + "support_vessel": "vessels", # cables "cables": "cables", "array_system": "cables", diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index 48c5b2c4..f337ab54 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -172,7 +172,7 @@ def lift_and_attach_tower_section(self): yield self.task( "Lift and Attach Tower Section", - 7, + 12, constraints={"windspeed": le(15)}, ) @@ -195,7 +195,7 @@ def lift_and_attach_blade(self): """ yield self.task( - "Lift and Attach Blade", 3.5, constraints={"windspeed": le(15)} + "Lift and Attach Blade", 3.5, constraints={"windspeed": le(12)} ) @process @@ -205,7 +205,9 @@ def mechanical_completion(self): work at quayside. """ - yield self.task("Mechanical Completion", 24) + yield self.task( + "Mechanical Completion", 24, constraints={"windspeed": le(18)} + ) class TowingGroup(Agent): diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index 9fde68ff..5e3e6615 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -4,3 +4,250 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" + + +from marmot import process + +from ORBIT.core import WetStorage +from ORBIT.phases.install import InstallPhase + +from .common import TowingGroup, TurbineAssemblyLine, SubstructureAssemblyLine + + +class GravityBasedInstallation(InstallPhase): + """ + Installation module to model the quayside assembly, tow-out and + installation of gravity based foundations. + """ + + phase = "Gravity Based Foundation Installation" + + #: + expected_config = { + "towing_vessel": "str", + "towing_vessel_groups": { + "towing_vessels": "int", + "stabilization_vessels": "int", + "num_groups": "int (optional)", + }, + "substructure": {"takt_time": "int | float (optional, default: 0)"}, + "site": {"depth": "m", "distance": "km"}, + "plant": {"num_turbines": "int"}, + "turbine": "dict", + "port": { + "sub_assembly_lines": "int (optional, default: 1)", + "sub_storage": "int (optional, default: inf)", + "turbine_assembly_cranes": "int (optional, default: 1)", + "assembly_storage": "int (optional, default: inf)", + "monthly_rate": "USD/mo (optional)", + "name": "str (optional)", + }, + } + + def __init__(self, config, weather=None, **kwargs): + """ + Creates an instance of GravityBasedInstallation. + + Parameters + ---------- + config : dict + Simulation specific configuration. + weather : np.array + Weather data at site. + """ + + super().__init__(weather, **kwargs) + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self.extract_defaults() + + self.setup_simulation(**kwargs) + + def setup_simulation(self, **kwargs): + """ + Sets up simulation infrastructure. + - Initializes substructure production + - Initializes turbine assembly processes + - Initializes towing groups + """ + + self.initialize_substructure_production() + self.initialize_turbine_assembly() + self.initialize_towing_groups() + + def initialize_substructure_production(self): + """ + Initializes the production of substructures at port. The number of + independent assembly lines and production time associated with a + substructure can be configured with the following parameters: + + - self.config["substructure"]["takt_time"] + - self.config["port"]["sub_assembly_lines"] + """ + + try: + storage = self.config["port"]["sub_storage"] + + except KeyError: + storage = float("inf") + + self.wet_storage = WetStorage(self.env, storage) + + try: + time = self.config["substructure"]["takt_time"] + + except KeyError: + time = 0 + + try: + lines = self.config["port"]["sub_assembly_lines"] + + except KeyError: + lines = 1 + + num = self.config["plant"]["num_turbines"] + to_assemble = [1] * num + + self.sub_assembly_lines = [] + for i in range(lines): + a = SubstructureAssemblyLine( + to_assemble, time, self.wet_storage, i + 1 + ) + + self.env.register(a) + a.start() + self.sub_assembly_lines.append(a) + + def initialize_turbine_assembly(self): + """ + Initializes turbine assembly lines. The number of independent lines + can be configured with the following parameters: + + - self.config["port"]["turb_assembly_lines"] + """ + + try: + storage = self.config["port"]["assembly_storage"] + + except KeyError: + storage = float("inf") + + self.assembly_storage = WetStorage(self.env, storage) + + try: + lines = self.config["port"]["turbine_assembly_cranes"] + + except KeyError: + lines = 1 + + turbine = self.config["turbine"] + self.turbine_assembly_lines = [] + for i in range(lines): + a = TurbineAssemblyLine( + self.wet_storage, self.assembly_storage, turbine, i + 1 + ) + + self.env.register(a) + a.start() + self.turbine_assembly_lines.append(a) + + def initialize_towing_groups(self, **kwargs): + """ + Initializes towing groups to bring completed assemblies to site and + stabilize the assembly during final installation. + """ + + distance = self.config["site"]["distance"] + self.installation_groups = [] + + vessel = self.config["towing_vessel"] + num_groups = self.config["towing_vessel_groups"].get("num_groups", 1) + towing = self.config["towing_vessel_groups"]["towing_vessels"] + stabilization = self.config["towing_vessel_groups"][ + "stabilization_vessels" + ] + + for i in range(num_groups): + g = TowingGroup(vessel, num=i + 1) + self.env.register(g) + g.initialize() + self.installation_groups.append(g) + + install_moored_substructures_from_storage( + g, + self.assembly_storage, + distance, + towing, + stabilization, + **kwargs, + ) + + @property + def detailed_output(self): + """""" + + return { + "operational_delays": { + **{ + k: self.operational_delay(str(k)) + for k in self.sub_assembly_lines + }, + **{ + k: self.operational_delay(str(k)) + for k in self.turbine_assembly_lines + }, + **{ + k: self.operational_delay(str(k)) + for k in self.installation_groups + }, + } + } + + def operational_delay(self, name): + """""" + + 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"]) + + return delay + + +@process +def install_gbf_substructures_from_storage( + group, feed, distance, towing_vessels, stabilization_vessels, **kwargs +): + """ + Process logic for the towing vessel group. + + Parameters + ---------- + group : TowingGroup + feed : simpy.Store + Completed assembly storage. + distance : int | float + Distance from port to site. + towing_vessels : int + Number of vessels to use for towing to site. + stabilization_vessels : int + Number of vessels to use for substructure stabilization during final + installation at site. + """ + + 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("Release from Quay-Side", 4, num_vessels=3) + yield group.group_task("Transit", 10, num_vessels=towing_vessels) + yield group.group_task( + "Installation", 10, num_vessels=stabilization_vessels + ) + yield group.group_task("Transit", 10, num_vessels=towing_vessels) diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 11cba10e..a0950dbc 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -6,9 +6,10 @@ __email__ = "jake.nunemaker@nrel.gov" -from marmot import process +import simpy +from marmot import le, process -from ORBIT.core import WetStorage +from ORBIT.core import Vessel, WetStorage from ORBIT.phases.install import InstallPhase from .common import TowingGroup, TurbineAssemblyLine, SubstructureAssemblyLine @@ -24,13 +25,17 @@ class MooredSubInstallation(InstallPhase): #: expected_config = { + "support_vessel": "str", "towing_vessel": "str", "towing_vessel_groups": { "towing_vessels": "int", "stabilization_vessels": "int", "num_groups": "int (optional)", }, - "substructure": {"takt_time": "int | float (optional, default: 0)"}, + "substructure": { + "takt_time": "int | float (optional, default: 0)", + "towing_speed": "int | float (optional, default: 6 km/h)", + }, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, "turbine": "dict", @@ -72,9 +77,14 @@ def setup_simulation(self, **kwargs): - Initializes towing groups """ + self.distance = self.config["site"]["distance"] + self.num_turbines = self.config["plant"]["num_turbines"] + self.initialize_substructure_production() self.initialize_turbine_assembly() + self.initialize_queue() self.initialize_towing_groups() + self.initialize_support_vessel() def initialize_substructure_production(self): """ @@ -106,8 +116,7 @@ def initialize_substructure_production(self): except KeyError: lines = 1 - num = self.config["plant"]["num_turbines"] - to_assemble = [1] * num + to_assemble = [1] * self.num_turbines self.sub_assembly_lines = [] for i in range(lines): @@ -158,15 +167,12 @@ def initialize_towing_groups(self, **kwargs): stabilize the assembly during final installation. """ - distance = self.config["site"]["distance"] self.installation_groups = [] vessel = self.config["towing_vessel"] num_groups = self.config["towing_vessel_groups"].get("num_groups", 1) towing = self.config["towing_vessel_groups"]["towing_vessels"] - stabilization = self.config["towing_vessel_groups"][ - "stabilization_vessels" - ] + towing_speed = self.config["substructure"].get("towing_speed", 6) for i in range(num_groups): g = TowingGroup(vessel, num=i + 1) @@ -174,15 +180,52 @@ def initialize_towing_groups(self, **kwargs): g.initialize() self.installation_groups.append(g) - install_moored_substructures_from_storage( + transfer_moored_substructures_from_storage( g, self.assembly_storage, - distance, + self.distance, + self.active_group, towing, - stabilization, + towing_speed, **kwargs, ) + def initialize_queue(self): + """ + Initializes the queue, modeled as a ``SimPy.Resource`` that towing + groups join at site. + """ + + self.active_group = simpy.Resource(self.env, capacity=1) + self.active_group.vessel = None + self.active_group.activate = self.env.event() + + def initialize_support_vessel(self, **kwargs): + """ + Initializes Multi-Purpose Support Vessel to perform installation + processes at site. + """ + + specs = self.config["support_vessel"] + vessel = Vessel("Multi-Purpose Support Vessel", specs) + + self.env.register(vessel) + vessel.initialize() + self.support_vessel = vessel + + stabilization = self.config["towing_vessel_groups"][ + "stabilization_vessels" + ] + + install_moored_substructures( + self.support_vessel, + self.active_group, + self.distance, + self.num_turbines, + stabilization, + **kwargs, + ) + @property def detailed_output(self): """""" @@ -214,8 +257,8 @@ def operational_delay(self, name): @process -def install_moored_substructures_from_storage( - group, feed, distance, towing_vessels, stabilization_vessels, **kwargs +def transfer_moored_substructures_from_storage( + group, feed, distance, queue, towing_vessels, towing_speed, **kwargs ): """ Process logic for the towing vessel group. @@ -230,11 +273,13 @@ def install_moored_substructures_from_storage( Distance from port to site. towing_vessels : int Number of vessels to use for towing to site. - stabilization_vessels : int - Number of vessels to use for substructure stabilization during final - installation at site. + towing_speed : int | float + Configured towing speed (km/h) """ + towing_time = distance / towing_speed + transit_time = distance / 20 + while True: start = group.env.now @@ -246,8 +291,100 @@ def install_moored_substructures_from_storage( "Delay: No Completed Assemblies Available", delay ) - yield group.group_task("Transit", 10, num_vessels=towing_vessels) yield group.group_task( - "Installation", 10, num_vessels=stabilization_vessels + "Tow Substructure", towing_time, num_vessels=towing_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("Transit", 10, num_vessels=towing_vessels) + + +@process +def install_moored_substructures( + vessel, queue, distance, substructures, stabilization_vessels, **kwargs +): + """ + Logic that a Multi-Purpose Support Vessel uses at site to complete the + installation of moored substructures. + + Parameters + ---------- + vessel : Vessel + queue : + distance : int | float + Distance between port and site (km). + substructures : int + Number of substructures to install before transiting back to port. + stabilization_vessels : int + Number of vessels to use for substructure stabilization during final + installation at site. + """ + + yield vessel.transit(distance) + + n = 0 + while n < substructures: + + if queue.vessel: + start = vessel.env.now + yield vessel.task( + "Position Substructure", + 2, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + yield vessel.task( + "Ballast Substructure", + 6, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + yield vessel.task( + "Connect Mooring Lines", + 22, + suspendable=True, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + yield vessel.task( + "Check Mooring Lines", + 12, + suspendable=True, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + + group_time = vessel.env.now - start + queue.vessel.submit_action_log( + "Positioning Support", + group_time, + location="site", + num_vessels=stabilization_vessels, + ) + yield queue.vessel.release.succeed() + + else: + start = vessel.env.now + yield queue.activate + delay_time = vessel.env.now - start + vessel.submit_action_log("Delay", delay_time, location="Site") + + yield vessel.transit(distance) diff --git a/library/vessels/example_support_vessel.yaml b/library/vessels/example_support_vessel.yaml new file mode 100644 index 00000000..89dbf161 --- /dev/null +++ b/library/vessels/example_support_vessel.yaml @@ -0,0 +1,9 @@ +transport_specs: + max_waveheight: 3 # m + max_windspeed: 20 # m/s + transit_speed: 10 # km/h +vessel_specs: + day_rate: 100000 # USD/day + overall_length: 150 # m + mobilization_days: 7 # days + mobilization_mult: 1 # Mobilization multiplier applied to 'day_rate' From be0751169356098ca54f917f3a6eca0c590eb679 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Mon, 6 Apr 2020 15:05:59 -0600 Subject: [PATCH 11/43] Added preliminary site operations for gravity based foundation installation. --- ORBIT/phases/install/__init__.py | 5 +- .../install/quayside_assembly_tow/__init__.py | 1 + .../quayside_assembly_tow/gravity_base.py | 185 ++++++++++++++++-- .../install/quayside_assembly_tow/moored.py | 3 + 4 files changed, 173 insertions(+), 21 deletions(-) diff --git a/ORBIT/phases/install/__init__.py b/ORBIT/phases/install/__init__.py index d503fec8..874968e0 100644 --- a/ORBIT/phases/install/__init__.py +++ b/ORBIT/phases/install/__init__.py @@ -10,5 +10,8 @@ from .cable_install import ArrayCableInstallation, ExportCableInstallation from .turbine_install import TurbineInstallation from .monopile_install import MonopileInstallation -from .quayside_assembly_tow import MooredSubInstallation +from .quayside_assembly_tow import ( + MooredSubInstallation, + GravityBasedInstallation, +) from .scour_protection_install import ScourProtectionInstallation diff --git a/ORBIT/phases/install/quayside_assembly_tow/__init__.py b/ORBIT/phases/install/quayside_assembly_tow/__init__.py index 4c8daac7..e97eed02 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/__init__.py +++ b/ORBIT/phases/install/quayside_assembly_tow/__init__.py @@ -7,3 +7,4 @@ from .moored import MooredSubInstallation +from .gravity_base import GravityBasedInstallation diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index 5e3e6615..efe6719e 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -6,9 +6,10 @@ __email__ = "jake.nunemaker@nrel.gov" -from marmot import process +import simpy +from marmot import le, process -from ORBIT.core import WetStorage +from ORBIT.core import Vessel, WetStorage from ORBIT.phases.install import InstallPhase from .common import TowingGroup, TurbineAssemblyLine, SubstructureAssemblyLine @@ -24,13 +25,17 @@ class GravityBasedInstallation(InstallPhase): #: expected_config = { + "support_vessel": "str", "towing_vessel": "str", "towing_vessel_groups": { "towing_vessels": "int", "stabilization_vessels": "int", "num_groups": "int (optional)", }, - "substructure": {"takt_time": "int | float (optional, default: 0)"}, + "substructure": { + "takt_time": "int | float (optional, default: 0)", + "towing_speed": "int | float (optional, default: 6 km/h)", + }, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, "turbine": "dict", @@ -72,9 +77,14 @@ def setup_simulation(self, **kwargs): - Initializes towing groups """ + self.distance = self.config["site"]["distance"] + self.num_turbines = self.config["plant"]["num_turbines"] + self.initialize_substructure_production() self.initialize_turbine_assembly() + self.initialize_queue() self.initialize_towing_groups() + self.initialize_support_vessel() def initialize_substructure_production(self): """ @@ -158,15 +168,12 @@ def initialize_towing_groups(self, **kwargs): stabilize the assembly during final installation. """ - distance = self.config["site"]["distance"] self.installation_groups = [] vessel = self.config["towing_vessel"] num_groups = self.config["towing_vessel_groups"].get("num_groups", 1) towing = self.config["towing_vessel_groups"]["towing_vessels"] - stabilization = self.config["towing_vessel_groups"][ - "stabilization_vessels" - ] + towing_speed = self.config["substructure"].get("towing_speed", 6) for i in range(num_groups): g = TowingGroup(vessel, num=i + 1) @@ -174,15 +181,52 @@ def initialize_towing_groups(self, **kwargs): g.initialize() self.installation_groups.append(g) - install_moored_substructures_from_storage( + transfer_gbf_substructures_from_storage( g, self.assembly_storage, - distance, + self.distance, + self.active_group, towing, - stabilization, + towing_speed, **kwargs, ) + def initialize_queue(self): + """ + Initializes the queue, modeled as a ``SimPy.Resource`` that towing + groups join at site. + """ + + self.active_group = simpy.Resource(self.env, capacity=1) + self.active_group.vessel = None + self.active_group.activate = self.env.event() + + def initialize_support_vessel(self, **kwargs): + """ + Initializes Multi-Purpose Support Vessel to perform installation + processes at site. + """ + + specs = self.config["support_vessel"] + vessel = Vessel("Multi-Purpose Support Vessel", specs) + + self.env.register(vessel) + vessel.initialize() + self.support_vessel = vessel + + stabilization = self.config["towing_vessel_groups"][ + "stabilization_vessels" + ] + + install_gravity_base_foundations( + self.support_vessel, + self.active_group, + self.distance, + self.num_turbines, + stabilization, + **kwargs, + ) + @property def detailed_output(self): """""" @@ -201,6 +245,9 @@ 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) + ), } } @@ -214,26 +261,29 @@ def operational_delay(self, name): @process -def install_gbf_substructures_from_storage( - group, feed, distance, towing_vessels, stabilization_vessels, **kwargs +def transfer_gbf_substructures_from_storage( + group, feed, distance, queue, towing_vessels, towing_speed, **kwargs ): """ Process logic for the towing vessel group. Parameters ---------- - group : TowingGroup + group : Vessel + Towing group. feed : simpy.Store Completed assembly storage. distance : int | float Distance from port to site. towing_vessels : int Number of vessels to use for towing to site. - stabilization_vessels : int - Number of vessels to use for substructure stabilization during final - installation at site. + towing_speed : int | float + Configured towing speed (km/h) """ + towing_time = distance / towing_speed + transit_time = distance / 20 + while True: start = group.env.now @@ -245,9 +295,104 @@ def install_gbf_substructures_from_storage( "Delay: No Completed Assemblies Available", delay ) - yield group.group_task("Release from Quay-Side", 4, num_vessels=3) - yield group.group_task("Transit", 10, num_vessels=towing_vessels) yield group.group_task( - "Installation", 10, num_vessels=stabilization_vessels + "Tow Substructure", towing_time, num_vessels=towing_vessels ) - yield group.group_task("Transit", 10, num_vessels=towing_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 + ) + + +@process +def install_gravity_base_foundations( + vessel, queue, distance, substructures, stabilization_vessels, **kwargs +): + """ + Logic that a Multi-Purpose Support Vessel uses at site to complete the + installation of gravity based foundations. + + Parameters + ---------- + vessel : Vessel + queue : + distance : int | float + Distance between port and site (km). + substructures : int + Number of substructures to install before transiting back to port. + stabilization_vessels : int + Number of vessels to use for substructure stabilization during final + installation at site. + """ + + yield vessel.transit(distance) + + n = 0 + while n < substructures: + + if queue.vessel: + start = vessel.env.now + yield vessel.task( + "Position Substructure", + 5, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + yield vessel.task( + "ROV Survey", + 1, + constraints={"windspeed": le(25), "waveheight": le(3)}, + ) + + # TODO: Model for ballast pump time + yield vessel.task( + "Pump Ballast", + 12, + # suspendable=True, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + + # TODO: Model for GBF grout time + yield vessel.task( + "Grout GBF", + 6, + suspendable=True, + constraints={"windspeed": le(15), "waveheight": le(2)}, + ) + + group_time = vessel.env.now - start + queue.vessel.submit_action_log( + "Positioning Support", + group_time, + location="site", + num_vessels=stabilization_vessels, + ) + yield queue.vessel.release.succeed() + + else: + start = vessel.env.now + yield queue.activate + delay_time = vessel.env.now - start + vessel.submit_action_log("Delay", 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 a0950dbc..49fc1a97 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -244,6 +244,9 @@ 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) + ), } } From db276b315a4e3bbc16662ad43b7542415176352c Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Mon, 6 Apr 2020 15:13:18 -0600 Subject: [PATCH 12/43] Moved support vessel mobilize to when the first towing group arrives at site. --- .../install/quayside_assembly_tow/gravity_base.py | 14 +++++++++----- .../phases/install/quayside_assembly_tow/moored.py | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index efe6719e..236806a6 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -178,7 +178,7 @@ def initialize_towing_groups(self, **kwargs): for i in range(num_groups): g = TowingGroup(vessel, num=i + 1) self.env.register(g) - g.initialize() + g.initialize(mobilize=False) self.installation_groups.append(g) transfer_gbf_substructures_from_storage( @@ -346,12 +346,14 @@ def install_gravity_base_foundations( installation at site. """ - yield vessel.transit(distance) - n = 0 while n < substructures: - if queue.vessel: + + if n == 0: + vessel.mobilize() + yield vessel.transit(distance) + start = vessel.env.now yield vessel.task( "Position Substructure", @@ -393,6 +395,8 @@ def install_gravity_base_foundations( start = vessel.env.now yield queue.activate delay_time = vessel.env.now - start - vessel.submit_action_log("Delay", delay_time, location="Site") + + if n != 0: + vessel.submit_action_log("Delay", 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 49fc1a97..09312781 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -210,7 +210,7 @@ def initialize_support_vessel(self, **kwargs): vessel = Vessel("Multi-Purpose Support Vessel", specs) self.env.register(vessel) - vessel.initialize() + vessel.initialize(mobilize=False) self.support_vessel = vessel stabilization = self.config["towing_vessel_groups"][ @@ -345,12 +345,14 @@ def install_moored_substructures( installation at site. """ - yield vessel.transit(distance) - n = 0 while n < substructures: - if queue.vessel: + + if n == 0: + vessel.mobilize() + yield vessel.transit(distance) + start = vessel.env.now yield vessel.task( "Position Substructure", @@ -388,6 +390,8 @@ def install_moored_substructures( start = vessel.env.now yield queue.activate delay_time = vessel.env.now - start - vessel.submit_action_log("Delay", delay_time, location="Site") + + if n != 0: + vessel.submit_action_log("Delay", delay_time, location="Site") yield vessel.transit(distance) From 97fff2e0a66891070edfef4568998867f82859d7 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 14 Apr 2020 22:35:31 -0600 Subject: [PATCH 13/43] Added constraints to towing group tow-out process. --- ORBIT/phases/install/quayside_assembly_tow/common.py | 7 +++++-- ORBIT/phases/install/quayside_assembly_tow/moored.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index f337ab54..9d7c38d3 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -8,6 +8,7 @@ from marmot import Agent, le, process +from marmot._exceptions import AgentNotRegistered class Substructure: @@ -234,7 +235,9 @@ def initialize(self): self.submit_debug_log(message="{self.name} initialized.") @process - def group_task(self, name, duration, num_vessels, **kwargs): + def group_task( + self, name, duration, num_vessels, constraints={}, **kwargs + ): """ Submits a group task with any number of towing vessels. @@ -250,7 +253,7 @@ def group_task(self, name, duration, num_vessels, **kwargs): """ kwargs = {**kwargs, "num_vessels": num_vessels} - yield self.task(name, duration, **kwargs) + yield self.task(name, duration, constraints=constraints, **kwargs) def operation_cost(self, hours, **kwargs): """ diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 09312781..9d8b65fb 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -295,7 +295,10 @@ def transfer_moored_substructures_from_storage( ) yield group.group_task( - "Tow Substructure", towing_time, num_vessels=towing_vessels + "Tow Substructure", + towing_time, + num_vessels=towing_vessels, + constraints={"windspeed": le(15), "waveheight": le(2)}, ) # At Site From 46a008c9a9d4afe9b59b866e67cf8af1e035c2e6 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 15 Apr 2020 17:16:26 -0600 Subject: [PATCH 14/43] Incorporated review comments. --- ORBIT/phases/design/mooring_system_design.py | 10 +++----- .../install/quayside_assembly_tow/common.py | 1 + .../quayside_assembly_tow/gravity_base.py | 2 +- .../install/quayside_assembly_tow/moored.py | 2 +- .../api_GravityBasedInstallation.rst | 8 +++++++ .../api_MooredSubInstallation.rst | 8 +++++++ .../doc_GravityBasedInstallation.rst | 24 +++++++++++++++++++ .../doc_MooredSubInstallation.rst | 24 +++++++++++++++++++ 8 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 docs/source/phases/install/quayside_towout/api_GravityBasedInstallation.rst create mode 100644 docs/source/phases/install/quayside_towout/api_MooredSubInstallation.rst create mode 100644 docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst create mode 100644 docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py index 8c42d283..9f6827bc 100644 --- a/ORBIT/phases/design/mooring_system_design.py +++ b/ORBIT/phases/design/mooring_system_design.py @@ -113,9 +113,7 @@ def calculate_total_cost(self): @property def design_result(self): - """ - TODO: - """ + """Returns the results of the design phase.""" return { "num_lines": self.num_lines, @@ -128,11 +126,9 @@ def design_result(self): def total_phase_cost(self): """Returns total phase cost in $USD.""" - _design = self.config.get("monopile_design", {}) + _design = self.config.get("mooring_system_design", {}) design_cost = _design.get("design_cost", 0.0) - material_cost = sum([v for _, v in self.material_cost.items()]) - - return design_cost + material_cost + return self.calculate_total_cost() + design_cost @property def total_phase_time(self): diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index 9d7c38d3..bc433e94 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -228,6 +228,7 @@ def __init__(self, vessel_specs, num=1): super().__init__(f"Towing Group {num}") self._specs = vessel_specs self.day_rate = self._specs["vessel_specs"]["day_rate"] + self.transit_speed = self._specs["transport_specs"]["transit_speed"] def initialize(self): """Initializes the towing group.""" diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index 236806a6..86fc0b35 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -282,7 +282,7 @@ def transfer_gbf_substructures_from_storage( """ towing_time = distance / towing_speed - transit_time = distance / 20 + transit_time = distance / group.transit_speed while True: diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 9d8b65fb..7a77f810 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -281,7 +281,7 @@ def transfer_moored_substructures_from_storage( """ towing_time = distance / towing_speed - transit_time = distance / 20 + transit_time = distance / group.transit_speed while True: diff --git a/docs/source/phases/install/quayside_towout/api_GravityBasedInstallation.rst b/docs/source/phases/install/quayside_towout/api_GravityBasedInstallation.rst new file mode 100644 index 00000000..283eda91 --- /dev/null +++ b/docs/source/phases/install/quayside_towout/api_GravityBasedInstallation.rst @@ -0,0 +1,8 @@ +Gravity-Based Foundation Installation API +========================================= + +For detailed methodology, please see +:doc:`Gravity-Based Foundation Installation Methodology `. + +.. autoclass:: ORBIT.phases.install.GravityBasedInstallation + :members: diff --git a/docs/source/phases/install/quayside_towout/api_MooredSubInstallation.rst b/docs/source/phases/install/quayside_towout/api_MooredSubInstallation.rst new file mode 100644 index 00000000..7c7a5f0f --- /dev/null +++ b/docs/source/phases/install/quayside_towout/api_MooredSubInstallation.rst @@ -0,0 +1,8 @@ +Moored Substructure Installation API +==================================== + +For detailed methodology, please see +:doc:`Moored Substructure Installation Methodology `. + +.. autoclass:: ORBIT.phases.install.MooredSubInstallation + :members: diff --git a/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst b/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst new file mode 100644 index 00000000..068c7437 --- /dev/null +++ b/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst @@ -0,0 +1,24 @@ +Gravity-Based Foundation Installation Methodology +================================================= + +For details of the code implementation, please see +:doc:`Moored Substructure Installation API `. + +Overview +-------- + + +Configuration +------------- + + +Processes +--------- + + +Configuration Examples +---------------------- + + +Process Diagrams +---------------- diff --git a/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst b/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst new file mode 100644 index 00000000..f9898dc6 --- /dev/null +++ b/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst @@ -0,0 +1,24 @@ +Moored Substructure Installation Methodology +============================================ + +For details of the code implementation, please see +:doc:`Moored Substructure Installation API `. + +Overview +-------- + + +Configuration +------------- + + +Processes +--------- + + +Configuration Examples +---------------------- + + +Process Diagrams +---------------- From aa7b786d474359af46c5f2741e21dcf81e3a0663 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 15 Apr 2020 17:23:43 -0600 Subject: [PATCH 15/43] Incorporated industry feedback. --- ORBIT/phases/install/quayside_assembly_tow/common.py | 12 ++++++++++++ ORBIT/phases/install/quayside_assembly_tow/moored.py | 9 ++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index bc433e94..8ee01677 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -129,6 +129,7 @@ def assemble_turbine(self): instead of calculating the lift times dynamically. """ + yield self.move_substructure() yield self.prepare_for_assembly() sections = self.turbine["tower"].get("sections", 1) @@ -155,6 +156,17 @@ def assemble_turbine(self): message="Assembly delievered to installation groups." ) + @process + def move_substructure(self): + """ + Task representing time associated with moving the completed + substructure assembly to the turbine assembly line. + + TODO: Move to dynamic process involving tow groups. + """ + + yield self.task("Move Substructure", 8) + @process def prepare_for_assembly(self): """ diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 7a77f810..92bfa130 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -294,6 +294,13 @@ def transfer_moored_substructures_from_storage( "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)}, + ) + yield group.group_task( "Tow Substructure", towing_time, @@ -363,7 +370,7 @@ def install_moored_substructures( constraints={"windspeed": le(15), "waveheight": le(2)}, ) yield vessel.task( - "Ballast Substructure", + "Ballast to Operational Draft", 6, constraints={"windspeed": le(15), "waveheight": le(2)}, ) From d139809d67e121dbf81c0e105e0e614ceaef97ef Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 29 Apr 2020 15:31:15 -0600 Subject: [PATCH 16/43] Small fixes in preliminary MooringSystemDesign. Added module to ProjectManager. Added basic tests. --- ORBIT/manager.py | 2 + ORBIT/phases/design/mooring_system_design.py | 35 ++++++-- .../design/test_mooring_system_design.py | 85 +++++++++++++++++++ 3 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 tests/phases/design/test_mooring_system_design.py diff --git a/ORBIT/manager.py b/ORBIT/manager.py index 357fe15c..e8ed448d 100644 --- a/ORBIT/manager.py +++ b/ORBIT/manager.py @@ -22,6 +22,7 @@ ArraySystemDesign, ExportSystemDesign, ProjectDevelopment, + MooringSystemDesign, ScourProtectionDesign, OffshoreSubstationDesign, ) @@ -53,6 +54,7 @@ class ProjectManager: ExportSystemDesign, ScourProtectionDesign, OffshoreSubstationDesign, + MooringSystemDesign, ] _install_phases = [ diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py index 9f6827bc..f3bd4cd4 100644 --- a/ORBIT/phases/design/mooring_system_design.py +++ b/ORBIT/phases/design/mooring_system_design.py @@ -17,10 +17,10 @@ class MooringSystemDesign(DesignPhase): "turbine": {"turbine_rating": "int | float"}, "plant": {"num_turbines": "int"}, "mooring_system_design": { - "num_lines": "int | float (optional)", - "anchor_type": "str (optional)", + "num_lines": "int | float (optional, default: 4)", + "anchor_type": "str (optional, default: 'suction')", "mooring_line_cost_rate": "int | float (optional)", - "drag_embedment_fixed_length": "int | float (optional)", + "drag_embedment_fixed_length": "int | float (optional, default: .5km)", }, } @@ -38,8 +38,11 @@ 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._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.extract_defaults() self._outputs = {} @@ -87,8 +90,13 @@ def calculate_line_length(self): Returns the mooring line length. """ + if self.anchor_type == "Drag Embedment": + fixed = self._design.get("drag_embedment_fixed_length", 0.5) + + else: + fixed = 0 + depth = self.config["site"]["depth"] - fixed = self._design.get("drag_embedment_fixed_length", 0.5) self.line_length = ( 0.0002 * (depth ** 2) + 1.264 * depth + 47.776 + fixed ) @@ -115,11 +123,15 @@ def calculate_total_cost(self): def design_result(self): """Returns the results of the design phase.""" + # TODO: Line mass/km + # TODO: Anchor mass + # TODO: Map with upcoming expected inputs of MooringSystemInstallation. + return { "num_lines": self.num_lines, + "line_diam": self.line_diam, "line_length": self.line_length, - "anchor_cost": self.anchor_cost, - "total_cost": self.calculate_total_cost(), + "anchor_type": self.anchor_type, } @property @@ -134,7 +146,7 @@ def total_phase_cost(self): def total_phase_time(self): """Returns total phase time in hours.""" - _design = self.config.get("monopile_design", {}) + _design = self.config.get("mooring_system_design", {}) phase_time = _design.get("design_time", 0.0) return phase_time @@ -142,4 +154,11 @@ def total_phase_time(self): def detailed_output(self): """Returns detailed phase information.""" - return {} + return { + "num_lines": self.num_lines, + "line_diam": self.line_diam, + "line_length": self.line_length, + "anchor_type": self.anchor_type, + "anchor_cost": self.anchor_cost, + "system_cost": self.calculate_total_cost(), + } diff --git a/tests/phases/design/test_mooring_system_design.py b/tests/phases/design/test_mooring_system_design.py new file mode 100644 index 00000000..159e6e8b --- /dev/null +++ b/tests/phases/design/test_mooring_system_design.py @@ -0,0 +1,85 @@ +"""Tests for the `MooringSystemDesign` class.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +from copy import deepcopy + +import pytest + +from ORBIT.phases.design import MooringSystemDesign + +base = { + "site": {"depth": 200}, + "turbine": {"turbine_rating": 6}, + "plant": {"num_turbines": 50}, +} + + +@pytest.mark.parametrize("depth", range(10, 1000, 100)) +def test_depth_sweep(depth): + + config = deepcopy(base) + config["site"]["depth"] = depth + + m = MooringSystemDesign(config) + m.run() + + assert m.design_result + assert m.total_phase_cost + + +@pytest.mark.parametrize("rating", range(3, 15, 1)) +def test_rating_sweeip(rating): + + config = deepcopy(base) + config["turbine"]["turbine_rating"] = rating + + m = MooringSystemDesign(config) + m.run() + + assert m.design_result + assert m.total_phase_cost + + +def test_drag_embedment_fixed_length(): + + m = MooringSystemDesign(base) + m.run() + + baseline = m.line_length + + default = deepcopy(base) + default["mooring_system_design"] = {"anchor_type": "Drag Embedment"} + + m = MooringSystemDesign(default) + m.run() + + with_default = m.line_length + assert with_default > baseline + + custom = deepcopy(base) + custom["mooring_system_design"] = { + "anchor_type": "Drag Embedment", + "drag_embedment_fixed_length": 10, + } + + m = MooringSystemDesign(custom) + m.run() + + assert m.line_length > with_default + assert m.line_length > baseline + + +def test_custom_num_lines(): + + config = deepcopy(base) + config["mooring_system_design"] = {"num_lines": 5} + + m = MooringSystemDesign(config) + m.run() + + assert m.design_result["num_lines"] == 5 From 880948adc61f02aee7d44e039cd28899da8cd3e9 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 29 Apr 2020 15:56:15 -0600 Subject: [PATCH 17/43] Cleanup in placeholder files. --- .../quayside_assembly_tow/test_gravity_based.py | 6 ++++++ .../install/quayside_assembly_tow/test_moored.py | 11 ----------- 2 files changed, 6 insertions(+), 11 deletions(-) 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 e69de29b..d732f3f3 100644 --- a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py +++ b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py @@ -0,0 +1,6 @@ +"""Tests for the `GravityBasedInstallation` class and related infrastructure.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" diff --git a/tests/phases/install/quayside_assembly_tow/test_moored.py b/tests/phases/install/quayside_assembly_tow/test_moored.py index f24f6dfe..e6aec501 100644 --- a/tests/phases/install/quayside_assembly_tow/test_moored.py +++ b/tests/phases/install/quayside_assembly_tow/test_moored.py @@ -4,14 +4,3 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" - - -from copy import deepcopy - -import pytest - -from tests.data import test_weather -from ORBIT.library import initialize_library, extract_library_specs -from ORBIT.phases.install import MooredSubInstallation - -initialize_library(pytest.library) From 8fb9b7f57b7b185d6c5f46e9791f89f350eb8eb7 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Thu, 30 Apr 2020 14:39:15 -0600 Subject: [PATCH 18/43] Added preliminary file/class structure. --- .../phases/design/semi_submersible_design.py | 34 +++++++++++++++++++ ORBIT/phases/design/spar_design.py | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 ORBIT/phases/design/semi_submersible_design.py create mode 100644 ORBIT/phases/design/spar_design.py diff --git a/ORBIT/phases/design/semi_submersible_design.py b/ORBIT/phases/design/semi_submersible_design.py new file mode 100644 index 00000000..0b9bee3f --- /dev/null +++ b/ORBIT/phases/design/semi_submersible_design.py @@ -0,0 +1,34 @@ +"""Provides the `SemiSubmersibleDesign` class (from OffshoreBOS).""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +from ORBIT.phases.design import DesignPhase + + +class SemiSubmersibleDesign(DesignPhase): + """Semi-Submersible Substructure Design""" + + expected_config = {} + + def __init__(self, config, **kwargs): + """ + Creates an instance of SemiSubmersibleDesign. + + Parameters + ---------- + config : dict + """ + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self.extract_defaults() + self._outputs = {} + + def run(self): + """Main run function.""" + + pass diff --git a/ORBIT/phases/design/spar_design.py b/ORBIT/phases/design/spar_design.py new file mode 100644 index 00000000..50fc12af --- /dev/null +++ b/ORBIT/phases/design/spar_design.py @@ -0,0 +1,34 @@ +"""Provides the `SparDesign` class (from OffshoreBOS).""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +from ORBIT.phases.design import DesignPhase + + +class SparDesign(DesignPhase): + """Spar Substructure Design""" + + expected_config = {} + + def __init__(self, config, **kwargs): + """ + Creates an instance of SparDesign. + + Parameters + ---------- + config : dict + """ + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self.extract_defaults() + self._outputs = {} + + def run(self): + """Main run function.""" + + pass From dbeae6868d1d3900545ab1278e144d8cacde6e12 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 5 May 2020 12:59:33 -0600 Subject: [PATCH 19/43] Added MooredSubInstallation to ProjectManager. --- ORBIT/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ORBIT/manager.py b/ORBIT/manager.py index 9d0c6a6e..1b461070 100644 --- a/ORBIT/manager.py +++ b/ORBIT/manager.py @@ -33,6 +33,7 @@ ExportCableInstallation, ScourProtectionInstallation, OffshoreSubstationInstallation, + MooredSubInstallation ) from ORBIT.core.exceptions import ( PhaseNotFound, @@ -64,6 +65,7 @@ class ProjectManager: ArrayCableInstallation, ExportCableInstallation, ScourProtectionInstallation, + MooredSubInstallation ] def __init__(self, config, library_path=None, weather=None): From 10db41b87f41c8822e5daecb30251704a8a3a9d3 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 5 May 2020 14:05:05 -0600 Subject: [PATCH 20/43] Added preliminary scenario files to library. --- library/cables/XLPE_1000m_220kV.yaml | 9 +++++++ library/cables/XLPE_185mm_66kV.yaml | 9 +++++++ library/cables/XLPE_630mm_66kV.yaml | 9 +++++++ library/turbines/12MW_generic.yaml | 20 ++++++++++++++ library/vessels/floating_barge.yaml | 25 ++++++++++++++++++ .../vessels/floating_heavy_lift_vessel.yaml | 26 +++++++++++++++++++ 6 files changed, 98 insertions(+) create mode 100755 library/cables/XLPE_1000m_220kV.yaml create mode 100755 library/cables/XLPE_185mm_66kV.yaml create mode 100755 library/cables/XLPE_630mm_66kV.yaml create mode 100755 library/turbines/12MW_generic.yaml create mode 100644 library/vessels/floating_barge.yaml create mode 100644 library/vessels/floating_heavy_lift_vessel.yaml diff --git a/library/cables/XLPE_1000m_220kV.yaml b/library/cables/XLPE_1000m_220kV.yaml new file mode 100755 index 00000000..68c19421 --- /dev/null +++ b/library/cables/XLPE_1000m_220kV.yaml @@ -0,0 +1,9 @@ +ac_resistance: 0.03 # +capacitance: 300 # +conductor_size: 1000 # +cost_per_km: 850000 # +current_capacity: 900 # Guess +inductance: 0.35 # +linear_density: 90 # From BVG Guide to OSW +name: XLPE_220kV_1 +rated_voltage: 220 diff --git a/library/cables/XLPE_185mm_66kV.yaml b/library/cables/XLPE_185mm_66kV.yaml new file mode 100755 index 00000000..be26ab38 --- /dev/null +++ b/library/cables/XLPE_185mm_66kV.yaml @@ -0,0 +1,9 @@ +ac_resistance: 0.128 # +capacitance: 163 # +conductor_size: 185 # +cost_per_km: 200000 # +current_capacity: 445 # At 2m burial depth +inductance: 0.443 # +linear_density: 26.1 # +name: XLPE_66kV_1 +rated_voltage: 66 diff --git a/library/cables/XLPE_630mm_66kV.yaml b/library/cables/XLPE_630mm_66kV.yaml new file mode 100755 index 00000000..aa85a114 --- /dev/null +++ b/library/cables/XLPE_630mm_66kV.yaml @@ -0,0 +1,9 @@ +ac_resistance: 0.04 # +capacitance: 300 # TODO +conductor_size: 630 # TODO +cost_per_km: 400000 # TODO +current_capacity: 775 # TODO +inductance: 0.35 # TODO +linear_density: 42.5 # TODO +name: XLPE_66kV_2 +rated_voltage: 66 diff --git a/library/turbines/12MW_generic.yaml b/library/turbines/12MW_generic.yaml new file mode 100755 index 00000000..ee69bb4b --- /dev/null +++ b/library/turbines/12MW_generic.yaml @@ -0,0 +1,20 @@ +blade: + deck_space: 385 # m^2 ##### Reset all deck_space = 1 to avoid vessel constraints + length: 107 # m + type: Blade + mass: 54 # t +hub_height: 132 # m +nacelle: + deck_space: 203 # m^2 + type: Nacelle + mass: 604 # t +name: 12MW Generic Turbine +rated_windspeed: 11 # m/s +rotor_diameter: 215 # m +tower: + deck_space: 50.24 # m^2 + sections: 2 # n + type: Tower + length: 132 + mass: 399 # t +turbine_rating: 12 # MW diff --git a/library/vessels/floating_barge.yaml b/library/vessels/floating_barge.yaml new file mode 100644 index 00000000..d1381a4b --- /dev/null +++ b/library/vessels/floating_barge.yaml @@ -0,0 +1,25 @@ +crane_specs: + max_lift: 500 # t + radius: 15 # m +jacksys_specs: # TEMPORARY UNTIL FLOATING FUNCTIONALY ADDED + air_gap: 10 # m, distance from MSL to jacked up height + leg_length: 85 # m + leg_pen: 5 # m + max_depth: 800 # m + max_extension: 800 # m + speed_above_depth: 100 # m/min + speed_below_depth: 100 # m/min +storage_specs: + max_cargo: 8000 # t + max_deck_load: 8 # t/m^2 + max_deck_space: 1000 # m^2 +transport_specs: + max_waveheight: 2.5 # m + max_windspeed: 20 # m/s + transit_speed: 6 # km/h +vessel_specs: + beam_length: 35 # m + day_rate: 120000 # USD/day + max_draft: 5 # m + min_draft: 4 # m + overall_length: 60 # m diff --git a/library/vessels/floating_heavy_lift_vessel.yaml b/library/vessels/floating_heavy_lift_vessel.yaml new file mode 100644 index 00000000..4c285fcb --- /dev/null +++ b/library/vessels/floating_heavy_lift_vessel.yaml @@ -0,0 +1,26 @@ +crane_specs: + boom_length: 100 # m + max_hook_height: 72 # m + max_lift: 5500 # t + max_windspeed: 15 # m/s + radius: 30 # m +jacksys_specs: # TEMPORARY UNTIL FLOATING FUNCTIONALY ADDED + air_gap: 10 # m, distance from MSL to jacked up height + leg_length: 110 # m + leg_pen: 5 # m + max_depth: 800 # m + max_extension: 800 # m + speed_above_depth: 100 # m/min + speed_below_depth: 100 # m/min +storage_specs: + max_cargo: 8000 # t + max_deck_load: 15 # t/m^2 + max_deck_space: 4000 # m^2 +transport_specs: + max_waveheight: 2.5 # m + max_windspeed: 20 # m/s + transit_speed: 7 # km/h +vessel_specs: + day_rate: 500000 # USD/day + max_draft: 4.5 # m + overall_length: 102.75 # m From 009f8f0526e449735e804b27fb29eba8c523eb69 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 5 May 2020 14:29:00 -0600 Subject: [PATCH 21/43] Updated floating vessels. Updated assembly line classes. --- .../install/quayside_assembly_tow/common.py | 70 +++++++++++++++++++ library/vessels/floating_barge.yaml | 8 +-- .../vessels/floating_heavy_lift_vessel.yaml | 8 +-- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/ORBIT/phases/install/quayside_assembly_tow/common.py b/ORBIT/phases/install/quayside_assembly_tow/common.py index 8ee01677..252965b7 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/common.py +++ b/ORBIT/phases/install/quayside_assembly_tow/common.py @@ -45,6 +45,41 @@ def __init__(self, assigned, time, target, num): self.time = time self.target = target + def submit_action_log(self, action, duration, **kwargs): + """ + Submits a log representing a completed `action` performed over time + `duration`. + + This method overwrites the default `submit_action_log` in + `marmot.Agent`, adding operation cost to every submitted log within + ORBIT. + + Parameters + ---------- + action : str + Performed action. + duration : int | float + Duration of action. + + Raises + ------ + AgentNotRegistered + """ + + if self.env is None: + raise AgentNotRegistered(self) + + else: + payload = { + **kwargs, + "agent": str(self), + "action": action, + "duration": float(duration), + "cost": 0, + } + + self.env._submit_log(payload, level="ACTION") + @process def assemble_substructure(self): """ @@ -101,6 +136,41 @@ def __init__(self, feed, target, turbine, num): self.target = target self.turbine = turbine + def submit_action_log(self, action, duration, **kwargs): + """ + Submits a log representing a completed `action` performed over time + `duration`. + + This method overwrites the default `submit_action_log` in + `marmot.Agent`, adding operation cost to every submitted log within + ORBIT. + + Parameters + ---------- + action : str + Performed action. + duration : int | float + Duration of action. + + Raises + ------ + AgentNotRegistered + """ + + if self.env is None: + raise AgentNotRegistered(self) + + else: + payload = { + **kwargs, + "agent": str(self), + "action": action, + "duration": float(duration), + "cost": 0, + } + + self.env._submit_log(payload, level="ACTION") + @process def start(self): """ diff --git a/library/vessels/floating_barge.yaml b/library/vessels/floating_barge.yaml index d1381a4b..0dd7212b 100644 --- a/library/vessels/floating_barge.yaml +++ b/library/vessels/floating_barge.yaml @@ -5,10 +5,10 @@ jacksys_specs: # TEMPORARY UNTIL FLOATING FUNCTIONALY ADDED air_gap: 10 # m, distance from MSL to jacked up height leg_length: 85 # m leg_pen: 5 # m - max_depth: 800 # m - max_extension: 800 # m - speed_above_depth: 100 # m/min - speed_below_depth: 100 # m/min + max_depth: 1000 # m + max_extension: 1000 # m + speed_above_depth: 10000 # m/min + speed_below_depth: 10000 # m/min storage_specs: max_cargo: 8000 # t max_deck_load: 8 # t/m^2 diff --git a/library/vessels/floating_heavy_lift_vessel.yaml b/library/vessels/floating_heavy_lift_vessel.yaml index 4c285fcb..fea7e5da 100644 --- a/library/vessels/floating_heavy_lift_vessel.yaml +++ b/library/vessels/floating_heavy_lift_vessel.yaml @@ -8,10 +8,10 @@ jacksys_specs: # TEMPORARY UNTIL FLOATING FUNCTIONALY ADDED air_gap: 10 # m, distance from MSL to jacked up height leg_length: 110 # m leg_pen: 5 # m - max_depth: 800 # m - max_extension: 800 # m - speed_above_depth: 100 # m/min - speed_below_depth: 100 # m/min + max_depth: 1000 # m + max_extension: 1000 # m + speed_above_depth: 10000 # m/min + speed_below_depth: 10000 # m/min storage_specs: max_cargo: 8000 # t max_deck_load: 15 # t/m^2 From 71b75bcb023fa2928f7a47ad2590644a9cde5b7d Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 5 May 2020 14:46:32 -0600 Subject: [PATCH 22/43] Bringing cable names in line with filenames. --- library/cables/XLPE_1000m_220kV.yaml | 2 +- library/cables/XLPE_185mm_66kV.yaml | 2 +- library/cables/XLPE_630mm_66kV.yaml | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/library/cables/XLPE_1000m_220kV.yaml b/library/cables/XLPE_1000m_220kV.yaml index 68c19421..62fc48e3 100755 --- a/library/cables/XLPE_1000m_220kV.yaml +++ b/library/cables/XLPE_1000m_220kV.yaml @@ -5,5 +5,5 @@ cost_per_km: 850000 # current_capacity: 900 # Guess inductance: 0.35 # linear_density: 90 # From BVG Guide to OSW -name: XLPE_220kV_1 +name: XLPE_1000m_220kV rated_voltage: 220 diff --git a/library/cables/XLPE_185mm_66kV.yaml b/library/cables/XLPE_185mm_66kV.yaml index be26ab38..f2371fbd 100755 --- a/library/cables/XLPE_185mm_66kV.yaml +++ b/library/cables/XLPE_185mm_66kV.yaml @@ -5,5 +5,5 @@ cost_per_km: 200000 # current_capacity: 445 # At 2m burial depth inductance: 0.443 # linear_density: 26.1 # -name: XLPE_66kV_1 +name: XLPE_185mm_66kV rated_voltage: 66 diff --git a/library/cables/XLPE_630mm_66kV.yaml b/library/cables/XLPE_630mm_66kV.yaml index aa85a114..fa5dcc22 100755 --- a/library/cables/XLPE_630mm_66kV.yaml +++ b/library/cables/XLPE_630mm_66kV.yaml @@ -1,9 +1,9 @@ -ac_resistance: 0.04 # -capacitance: 300 # TODO -conductor_size: 630 # TODO -cost_per_km: 400000 # TODO -current_capacity: 775 # TODO -inductance: 0.35 # TODO -linear_density: 42.5 # TODO -name: XLPE_66kV_2 +ac_resistance: 0.04 +capacitance: 300 +conductor_size: 630 +cost_per_km: 400000 +current_capacity: 775 +inductance: 0.35 +linear_density: 42.5 +name: XLPE_630mm_66kV rated_voltage: 66 From 4ada13823cd7d6a65877820feb33cb255d3ffa6a Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Thu, 7 May 2020 12:48:52 -0600 Subject: [PATCH 23/43] Updated towing vessel day rate. --- library/vessels/example_towing_vessel.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/vessels/example_towing_vessel.yaml b/library/vessels/example_towing_vessel.yaml index 39693ca3..b1de857d 100644 --- a/library/vessels/example_towing_vessel.yaml +++ b/library/vessels/example_towing_vessel.yaml @@ -4,7 +4,7 @@ transport_specs: transit_speed: 6 # km/h vessel_specs: beam_length: 35 # m - day_rate: 40000 # USD/day + day_rate: 30000 # USD/day max_draft: 5 # m min_draft: 4 # m overall_length: 60 # m From 92b3ef25610b4697671c27d79c3aff5de3828986 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Thu, 7 May 2020 15:51:41 -0600 Subject: [PATCH 24/43] Added initial semi-submersible design module. --- ORBIT/phases/design/__init__.py | 1 + .../phases/design/semi_submersible_design.py | 192 +++++++++++++++++- 2 files changed, 191 insertions(+), 2 deletions(-) diff --git a/ORBIT/phases/design/__init__.py b/ORBIT/phases/design/__init__.py index 2894b6ad..20207f11 100644 --- a/ORBIT/phases/design/__init__.py +++ b/ORBIT/phases/design/__init__.py @@ -14,3 +14,4 @@ from .export_system_design import ExportSystemDesign from .mooring_system_design import MooringSystemDesign from .scour_protection_design import ScourProtectionDesign +from .semi_submersible_design import SemiSubmersibleDesign diff --git a/ORBIT/phases/design/semi_submersible_design.py b/ORBIT/phases/design/semi_submersible_design.py index 0b9bee3f..edfc3f28 100644 --- a/ORBIT/phases/design/semi_submersible_design.py +++ b/ORBIT/phases/design/semi_submersible_design.py @@ -12,7 +12,20 @@ class SemiSubmersibleDesign(DesignPhase): """Semi-Submersible Substructure Design""" - expected_config = {} + expected_config = { + "site": {"depth": "m"}, + "plant": {"num_turbines": "int"}, + "turbine": {"turbine_rating": "MW"}, + "semisubmersible_design": { + "column_CR": "$/t (optional, default: 3120)", + "truss_CR": "$/t (optional, default: 6250)", + "heave_plate_CR": "$/t (optional, default: 6250)", + "secondary_steel_CR": "$/t (optional, default: 7250)", + "towing_speed": "km/h (optional, default: 6)", + "design_time": "h, (optional, default: 0)", + "design_cost": "h, (optional, default: 0)", + }, + } def __init__(self, config, **kwargs): """ @@ -26,9 +39,184 @@ def __init__(self, config, **kwargs): config = self.initialize_library(config, **kwargs) self.config = self.validate_config(config) self.extract_defaults() + self._design = self.config.get("semisubmersible_design", {}) + self._outputs = {} + @property + def stiffened_column_mass(self): + """ + Calculates the mass of the stiffened column for a single + semi-submersible in tonnes. From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + mass = -0.9581 * rating ** 2 + 40.89 * rating + 802.09 + + return mass + + @property + def stiffened_column_cost(self): + """ + Calculates the cost of the stiffened column for a single + semi-submersible. From original OffshoreBOS model. + """ + + cr = self._design.get("column_CR", 3120) + return self.stiffened_column_mass * cr + + @property + def truss_mass(self): + """ + Calculates the truss mass for a single semi-submersible in tonnes. From + original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + mass = 2.7894 * rating ** 2 + 15.591 * rating + 266.03 + + return mass + + @property + def truss_cost(self): + """ + Calculates the cost of the truss for a signle semi-submerisble. From + original OffshoreBOS model. + """ + + cr = self._design.get("truss_CR", 6250) + return self.truss_mass * cr + + @property + def heave_plate_mass(self): + """ + Calculates the heave plate mass for a single semi-submersible in tonnes. + From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + mass = -0.4397 * rating ** 2 + 21.545 * rating + 177.42 + + return mass + + @property + def heave_plate_cost(self): + """ + Calculates the heave plate cost for a single semi-submersible. From + original OffshoreBOS model. + """ + + cr = self._design.get("heave_plate_CR", 6250) + return self.heave_plate_mass * cr + + @property + def secondary_steel_mass(self): + """ + Calculates the mass of the required secondary steel for a single + semi-submersible. From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + mass = -0.153 * rating ** 2 + 6.54 * rating + 128.34 + + return mass + + @property + def secondary_steel_cost(self): + """ + Calculates the cost of the required secondary steel for a single + semi-submersible. For original OffshoreBOS model. + """ + + cr = self._design.get("secondary_steel_CR", 7250) + return self.secondary_steel_mass * cr + def run(self): """Main run function.""" - pass + substructure = { + "mass": self.substructure_mass, + "cost": self.substructure_cost, + "towing_speed": self._design.get("towing_speed", 6), + } + + self._outputs["substructure"] = substructure + + @property + def substructure_mass(self): + """Returns single substructure mass.""" + + return ( + self.stiffened_column_mass + + self.truss_mass + + self.heave_plate_mass + + self.secondary_steel_mass + ) + + @property + def substructure_cost(self): + """Returns single substructure cost.""" + + return ( + self.stiffened_column_cost + + self.truss_cost + + self.heave_plate_cost + + self.secondary_steel_cost + ) + + @property + def total_substructure_mass(self): + """Returns mass of all substructures.""" + + num = self.config["plant"]["num_turbines"] + return num * self.substructure_mass + + @property + def total_substructure_cost(self): + """Retruns cost of all substructures.""" + + num = self.config["plant"]["num_turbines"] + return num * self.substructure_cost + + @property + def design_result(self): + """Returns the result of `self.run()`""" + + if not self._outputs: + raise Exception("Has `SemiSubmersibleDesign` been ran yet?") + + return self._outputs + + @property + def total_phase_cost(self): + """Returns total phase cost in $USD.""" + + _design = self.config.get("semisubmersible_design", {}) + design_cost = _design.get("design_cost", 0.0) + + return design_cost + self.total_substructure_cost + + @property + def total_phase_time(self): + """Returns total phase time in hours.""" + + _design = self.config.get("semisubmersible_design", {}) + phase_time = _design.get("design_time", 0.0) + return phase_time + + @property + def detailed_output(self): + """Returns detailed phase information.""" + + _outputs = { + "stiffened_column_mass": self.stiffened_column_mass, + "stiffened_column_cost": self.stiffened_column_cost, + "truss_mass": self.truss_mass, + "truss_cost": self.truss_cost, + "heave_plate_mass": self.heave_plate_mass, + "heave_plate_cost": self.heave_plate_cost, + "secondary_steel_mass": self.secondary_steel_mass, + "secondary_steel_cost": self.secondary_steel_cost, + } + + return _outputs From 5615c0685b6a20642c559a32f22fe967ee954054 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 12 May 2020 11:06:27 -0600 Subject: [PATCH 25/43] Implemented spar design relationships from OffshoreBOS in new class ORBIT.phases.design.SparDesign. --- ORBIT/phases/design/__init__.py | 1 + .../phases/design/semi_submersible_design.py | 28 +-- ORBIT/phases/design/spar_design.py | 198 +++++++++++++++++- 3 files changed, 210 insertions(+), 17 deletions(-) diff --git a/ORBIT/phases/design/__init__.py b/ORBIT/phases/design/__init__.py index 20207f11..c7d85801 100644 --- a/ORBIT/phases/design/__init__.py +++ b/ORBIT/phases/design/__init__.py @@ -8,6 +8,7 @@ from .design_phase import DesignPhase # isort:skip from .oss_design import OffshoreSubstationDesign +from .spar_design import SparDesign from .monopile_design import MonopileDesign from .array_system_design import ArraySystemDesign, CustomArraySystemDesign from .project_development import ProjectDevelopment diff --git a/ORBIT/phases/design/semi_submersible_design.py b/ORBIT/phases/design/semi_submersible_design.py index edfc3f28..0ae87d44 100644 --- a/ORBIT/phases/design/semi_submersible_design.py +++ b/ORBIT/phases/design/semi_submersible_design.py @@ -17,7 +17,7 @@ class SemiSubmersibleDesign(DesignPhase): "plant": {"num_turbines": "int"}, "turbine": {"turbine_rating": "MW"}, "semisubmersible_design": { - "column_CR": "$/t (optional, default: 3120)", + "stiffened_column_CR": "$/t (optional, default: 3120)", "truss_CR": "$/t (optional, default: 6250)", "heave_plate_CR": "$/t (optional, default: 6250)", "secondary_steel_CR": "$/t (optional, default: 7250)", @@ -29,7 +29,7 @@ class SemiSubmersibleDesign(DesignPhase): def __init__(self, config, **kwargs): """ - Creates an instance of SemiSubmersibleDesign. + Creates an instance of `SemiSubmersibleDesign`. Parameters ---------- @@ -43,6 +43,17 @@ def __init__(self, config, **kwargs): self._outputs = {} + def run(self): + """Main run function.""" + + substructure = { + "mass": self.substructure_mass, + "cost": self.substructure_cost, + "towing_speed": self._design.get("towing_speed", 6), + } + + self._outputs["semisubmersible"] = substructure + @property def stiffened_column_mass(self): """ @@ -62,7 +73,7 @@ def stiffened_column_cost(self): semi-submersible. From original OffshoreBOS model. """ - cr = self._design.get("column_CR", 3120) + cr = self._design.get("stiffened_column_CR", 3120) return self.stiffened_column_mass * cr @property @@ -131,17 +142,6 @@ def secondary_steel_cost(self): cr = self._design.get("secondary_steel_CR", 7250) return self.secondary_steel_mass * cr - def run(self): - """Main run function.""" - - substructure = { - "mass": self.substructure_mass, - "cost": self.substructure_cost, - "towing_speed": self._design.get("towing_speed", 6), - } - - self._outputs["substructure"] = substructure - @property def substructure_mass(self): """Returns single substructure mass.""" diff --git a/ORBIT/phases/design/spar_design.py b/ORBIT/phases/design/spar_design.py index 50fc12af..2e8ac0f1 100644 --- a/ORBIT/phases/design/spar_design.py +++ b/ORBIT/phases/design/spar_design.py @@ -6,17 +6,32 @@ __email__ = "jake.nunemaker@nrel.gov" +from numpy import exp, log + from ORBIT.phases.design import DesignPhase class SparDesign(DesignPhase): """Spar Substructure Design""" - expected_config = {} + expected_config = { + "site": {"depth": "m"}, + "plant": {"num_turbines": "int"}, + "turbine": {"turbine_rating": "MW"}, + "spar_design": { + "stiffened_column_CR": "$/t (optional, default: 3120)", + "tapered_column_CR": "$/t (optional, default: 4220)", + "ballast_material_CR": "$/t (optional, default: 100)", + "secondary_steel_CR": "$/t (optional, default: 7250)", + "towing_speed": "km/h (optional, default: 6)", + "design_time": "h, (optional, default: 0)", + "design_cost": "h, (optional, default: 0)", + }, + } def __init__(self, config, **kwargs): """ - Creates an instance of SparDesign. + Creates an instance of `SparDesign`. Parameters ---------- @@ -26,9 +41,186 @@ def __init__(self, config, **kwargs): config = self.initialize_library(config, **kwargs) self.config = self.validate_config(config) self.extract_defaults() + self._design = self.config.get("spar_design", {}) + self._outputs = {} def run(self): """Main run function.""" - pass + substructure = { + "mass": self.unballasted_mass, + "ballasted_mass": self.ballasted_mass, + "cost": self.substructure_cost, + "towing_speed": self._design.get("towing_speed", 6), + } + + self._outputs["spar"] = substructure + + @property + def stiffened_column_mass(self): + """ + Calculates the mass of the stiffened column for a single spar in tonnes. From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + depth = self.config["site"]["depth"] + + mass = 535.93 + 17.664 * rating ** 2 + 0.02328 * depth * log(depth) + + return mass + + @property + def tapered_column_mass(self): + """ + Calculates the mass of the atpered column for a single spar in tonnes. From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + + mass = 125.81 * log(rating) + 58.712 + + return mass + + @property + def stiffened_column_cost(self): + """ + Calculates the cost of the stiffened column for a single spar. From original OffshoreBOS model. + """ + + cr = self._design.get("stiffened_column_CR", 3120) + return self.stiffened_column_mass * cr + + @property + def tapered_column_cost(self): + """ + Calculates the cost of the tapered column for a single spar. From original OffshoreBOS model. + """ + + cr = self._design.get("tapered_column_CR", 4220) + return self.tapered_column_mass * cr + + @property + def ballast_mass(self): + """ + Calculates the ballast mass of a single spar. From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + mass = -16.536 * rating ** 2 + 1261.8 * rating - 1554.6 + + return mass + + @property + def ballast_cost(self): + """ + Calculates the cost of ballast material for a single spar. From original OffshoreBOS model. + """ + + cr = self._design.get("ballast_material_CR", 100) + return self.ballast_mass * cr + + @property + def secondary_steel_mass(self): + """ + Calculates the mass of the required secondary steel for a single + spar. From original OffshoreBOS model. + """ + + rating = self.config["turbine"]["turbine_rating"] + depth = self.config["site"]["depth"] + + mass = exp( + 3.58 + + 0.196 * (rating ** 0.5) * log(rating) + + 0.00001 * depth * log(depth) + ) + + return mass + + @property + def secondary_steel_cost(self): + """ + Calculates the cost of the required secondary steel for a single + spar. For original OffshoreBOS model. + """ + + cr = self._design.get("secondary_steel_CR", 7250) + return self.secondary_steel_mass * cr + + @property + def unballasted_mass(self): + """Returns the unballasted mass of the spar substructure.""" + + return ( + self.stiffened_column_mass + + self.tapered_column_mass + + self.secondary_steel_mass + ) + + @property + def ballasted_mass(self): + """Returns the ballasted mass of the spar substructure.""" + + return self.unballasted_mass + self.ballast_mass + + @property + def substructure_cost(self): + """Returns the total cost (including ballast) of the spar substructure.""" + + return ( + self.stiffened_column_cost + + self.tapered_column_cost + + self.secondary_steel_cost + + self.ballast_cost + ) + + @property + def detailed_output(self): + """Returns detailed phase information.""" + + _outputs = { + "stiffened_column_mass": self.stiffened_column_mass, + "stiffened_column_cost": self.stiffened_column_cost, + "tapered_column_mass": self.tapered_column_mass, + "tapered_column_cost": self.tapered_column_cost, + "ballast_mass": self.ballast_mass, + "ballast_cost": self.ballast_cost, + "secondary_steel_mass": self.secondary_steel_mass, + "secondary_steel_cost": self.secondary_steel_cost, + } + + return _outputs + + @property + def total_substructure_cost(self): + """Retruns cost of all substructures.""" + + num = self.config["plant"]["num_turbines"] + return num * self.substructure_cost + + @property + def total_phase_cost(self): + """Returns total phase cost in $USD.""" + + _design = self.config.get("spar_design", {}) + design_cost = _design.get("design_cost", 0.0) + + return design_cost + self.total_substructure_cost + + @property + def total_phase_time(self): + """Returns total phase time in hours.""" + + _design = self.config.get("spar_design", {}) + phase_time = _design.get("design_time", 0.0) + return phase_time + + @property + def design_result(self): + """Returns the result of `self.run()`""" + + if not self._outputs: + raise Exception("Has `SparDesign` been ran yet?") + + return self._outputs From a0e4e4bc612f1d5d2bba985e599d77703d597126 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 12 May 2020 11:35:14 -0600 Subject: [PATCH 26/43] Added parameter sweeps and kwarg tests for SparDesign and SemiSubmersibleDesign. --- .../design/test_semisubmersible_design.py | 66 +++++++++++++++++++ tests/phases/design/test_spar_design.py | 66 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 tests/phases/design/test_semisubmersible_design.py create mode 100644 tests/phases/design/test_spar_design.py diff --git a/tests/phases/design/test_semisubmersible_design.py b/tests/phases/design/test_semisubmersible_design.py new file mode 100644 index 00000000..de4315e2 --- /dev/null +++ b/tests/phases/design/test_semisubmersible_design.py @@ -0,0 +1,66 @@ +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "Jake.Nunemaker@nrel.gov" + + +from copy import deepcopy +from itertools import product + +import pytest + +from ORBIT.phases.design import SemiSubmersibleDesign + +base = { + "site": {"depth": 500}, + "plant": {"num_turbines": 50}, + "turbine": {"turbine_rating": 12}, + "semisubmersible_design": {}, +} + + +@pytest.mark.parametrize( + "depth,turbine_rating", product(range(100, 1201, 200), range(3, 15, 1)) +) +def test_parameter_sweep(depth, turbine_rating): + + config = { + "site": {"depth": depth}, + "plant": {"num_turbines": 50}, + "turbine": {"turbine_rating": turbine_rating}, + "substation_design": {}, + } + + s = SemiSubmersibleDesign(config) + s.run() + + assert s.detailed_output["stiffened_column_mass"] > 0 + assert s.detailed_output["truss_mass"] > 0 + assert s.detailed_output["heave_plate_mass"] > 0 + assert s.detailed_output["secondary_steel_mass"] > 0 + + +def test_design_kwargs(): + + test_kwargs = { + "stiffened_column_CR": 3000, + "truss_CR": 6000, + "heave_plate_CR": 6000, + "secondary_steel_CR": 7000, + } + + s = SemiSubmersibleDesign(base) + s.run() + base_cost = s.total_phase_cost + + for k, v in test_kwargs.items(): + + config = deepcopy(base) + config["semisubmersible_design"] = {} + config["semisubmersible_design"][k] = v + + s = SemiSubmersibleDesign(config) + s.run() + cost = s.total_phase_cost + + assert cost != base_cost diff --git a/tests/phases/design/test_spar_design.py b/tests/phases/design/test_spar_design.py new file mode 100644 index 00000000..f21bd2a2 --- /dev/null +++ b/tests/phases/design/test_spar_design.py @@ -0,0 +1,66 @@ +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "Jake.Nunemaker@nrel.gov" + + +from copy import deepcopy +from itertools import product + +import pytest + +from ORBIT.phases.design import SparDesign + +base = { + "site": {"depth": 500}, + "plant": {"num_turbines": 50}, + "turbine": {"turbine_rating": 12}, + "spar_design": {}, +} + + +@pytest.mark.parametrize( + "depth,turbine_rating", product(range(100, 1201, 200), range(3, 15, 1)) +) +def test_parameter_sweep(depth, turbine_rating): + + config = { + "site": {"depth": depth}, + "plant": {"num_turbines": 50}, + "turbine": {"turbine_rating": turbine_rating}, + "substation_design": {}, + } + + s = SparDesign(config) + s.run() + + assert s.detailed_output["stiffened_column_mass"] > 0 + assert s.detailed_output["tapered_column_mass"] > 0 + assert s.detailed_output["ballast_mass"] > 0 + assert s.detailed_output["secondary_steel_mass"] > 0 + + +def test_design_kwargs(): + + test_kwargs = { + "stiffened_column_CR": 3000, + "tapered_column_CR": 4000, + "ballast_material_CR": 200, + "secondary_steel_CR": 7000, + } + + s = SparDesign(base) + s.run() + base_cost = s.total_phase_cost + + for k, v in test_kwargs.items(): + + config = deepcopy(base) + config["spar_design"] = {} + config["spar_design"][k] = v + + s = SparDesign(config) + s.run() + cost = s.total_phase_cost + + assert cost != base_cost From 8ebd971b9f713e951b5aa192af0fc63d50fd76ab Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 12 May 2020 14:49:27 -0600 Subject: [PATCH 27/43] Added documentation files and links for semi-submersible and spar design modules. --- docs/source/api_DesignPhase.rst | 2 ++ docs/source/doc_DesignPhase.rst | 2 ++ .../phases/design/api_SemiSubmersibleDesign.rst | 8 ++++++++ docs/source/phases/design/api_SparDesign.rst | 8 ++++++++ .../phases/design/doc_SemiSubmersibleDesign.rst | 17 +++++++++++++++++ docs/source/phases/design/doc_SparDesign.rst | 17 +++++++++++++++++ 6 files changed, 54 insertions(+) create mode 100644 docs/source/phases/design/api_SemiSubmersibleDesign.rst create mode 100644 docs/source/phases/design/api_SparDesign.rst create mode 100644 docs/source/phases/design/doc_SemiSubmersibleDesign.rst create mode 100644 docs/source/phases/design/doc_SparDesign.rst diff --git a/docs/source/api_DesignPhase.rst b/docs/source/api_DesignPhase.rst index 5dedfd1c..3b0741ce 100644 --- a/docs/source/api_DesignPhase.rst +++ b/docs/source/api_DesignPhase.rst @@ -16,5 +16,7 @@ trends but are not intended to be used for actual designs. phases/design/api_ArraySystemDesign phases/design/api_ExportSystemDesign phases/design/api_OffshoreSubstationDesign + phases/design/api_SemiSubmersibleDesign + phases/design/api_SparDesign .. phases/design/api_JacketDesign diff --git a/docs/source/doc_DesignPhase.rst b/docs/source/doc_DesignPhase.rst index 3a756701..f5468518 100644 --- a/docs/source/doc_DesignPhase.rst +++ b/docs/source/doc_DesignPhase.rst @@ -14,5 +14,7 @@ the model. phases/design/doc_ArraySystemDesign phases/design/doc_ExportSystemDesign phases/design/doc_OffshoreSubstationDesign + phases/design/doc_SemiSubmersibleDesign + phases/design/doc_SparDesign .. phases/design/doc_JacketDesign diff --git a/docs/source/phases/design/api_SemiSubmersibleDesign.rst b/docs/source/phases/design/api_SemiSubmersibleDesign.rst new file mode 100644 index 00000000..ed3ba2b6 --- /dev/null +++ b/docs/source/phases/design/api_SemiSubmersibleDesign.rst @@ -0,0 +1,8 @@ +Semi-Submersible Design API +=========================== + +For detailed methodology, please see +:doc:`Semi-Submersible Design `. + +.. autoclass:: ORBIT.phases.design.SemiSubmersibleDesign + :members: diff --git a/docs/source/phases/design/api_SparDesign.rst b/docs/source/phases/design/api_SparDesign.rst new file mode 100644 index 00000000..21e1593d --- /dev/null +++ b/docs/source/phases/design/api_SparDesign.rst @@ -0,0 +1,8 @@ +Spar Design API +=============== + +For detailed methodology, please see +:doc:`Spar Design `. + +.. autoclass:: ORBIT.phases.design.SparDesign + :members: diff --git a/docs/source/phases/design/doc_SemiSubmersibleDesign.rst b/docs/source/phases/design/doc_SemiSubmersibleDesign.rst new file mode 100644 index 00000000..5e181fdf --- /dev/null +++ b/docs/source/phases/design/doc_SemiSubmersibleDesign.rst @@ -0,0 +1,17 @@ +Semi-Submersible Design Methodology +=================================== + +For details of the code implementation, please see +:doc:`Semi-Submersible Design API `. + +Overview +-------- + +The semi-submersible design module in ORBIT is based on previous modeling +efforts undertaken by NREL, [#maness2017]_. + +References +---------- + +.. [#maness2017] Michael Maness, Benjamin Maples, Aaron Smith, + NREL Offshore Balance-of-System Model, 2017 diff --git a/docs/source/phases/design/doc_SparDesign.rst b/docs/source/phases/design/doc_SparDesign.rst new file mode 100644 index 00000000..6200b85f --- /dev/null +++ b/docs/source/phases/design/doc_SparDesign.rst @@ -0,0 +1,17 @@ +Spar Design Methodology +======================= + +For details of the code implementation, please see +:doc:`Spar Design API `. + +Overview +-------- + +The spar design module in ORBIT is based on previous modeling efforts +undertaken by NREL, [#maness2017]_. + +References +---------- + +.. [#maness2017] Michael Maness, Benjamin Maples, Aaron Smith, + NREL Offshore Balance-of-System Model, 2017 From 9f4027eb966d725e465edd87d51d7de96b6aea79 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 27 May 2020 11:44:25 -0600 Subject: [PATCH 28/43] Revised 'port.num_cranes' input to be optional. --- ORBIT/phases/install/install_phase.py | 8 +++++--- ORBIT/phases/install/monopile_install/standard.py | 2 +- ORBIT/phases/install/oss_install/standard.py | 2 +- ORBIT/phases/install/turbine_install/standard.py | 2 +- library/project/config/example_array_cable_install.yaml | 1 - .../config/example_custom_array_project_manager.yaml | 1 - tests/data/library/project/config/complete_project.yaml | 2 -- tests/data/library/project/config/library.yaml | 1 - tests/data/library/project/config/project_manager.yaml | 1 - .../library/project/config/scour_protection_install.yaml | 1 - 10 files changed, 8 insertions(+), 13 deletions(-) diff --git a/ORBIT/phases/install/install_phase.py b/ORBIT/phases/install/install_phase.py index 0be267a2..3f200dcd 100644 --- a/ORBIT/phases/install/install_phase.py +++ b/ORBIT/phases/install/install_phase.py @@ -61,13 +61,14 @@ def initialize_port(self): Initializes a Port object with N number of cranes. """ + self.port = Port(self.env) + try: cranes = self.config["port"]["num_cranes"] - self.port = Port(self.env) self.port.crane = simpy.Resource(self.env, cranes) except KeyError: - self.port = Port(self.env) + self.port.crane = simpy.Resource(self.env, 1) def run(self, until=None): """ @@ -101,7 +102,8 @@ def port_costs(self): else: key = "port_cost_per_month" - rate = self.config["port"].get("monthly_rate", self.defaults[key]) + port_config = self.config.get("port", {}) + rate = port_config.get("monthly_rate", self.defaults[key]) months = self.total_phase_time / (8760 / 12) return months * rate diff --git a/ORBIT/phases/install/monopile_install/standard.py b/ORBIT/phases/install/monopile_install/standard.py index a8fcfae9..01e438f8 100644 --- a/ORBIT/phases/install/monopile_install/standard.py +++ b/ORBIT/phases/install/monopile_install/standard.py @@ -47,7 +47,7 @@ class MonopileInstallation(InstallPhase): "plant": {"num_turbines": "int"}, "turbine": {"hub_height": "m"}, "port": { - "num_cranes": "int", + "num_cranes": "int (optional, default: 1)", "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, diff --git a/ORBIT/phases/install/oss_install/standard.py b/ORBIT/phases/install/oss_install/standard.py index 6837d2e3..07907229 100644 --- a/ORBIT/phases/install/oss_install/standard.py +++ b/ORBIT/phases/install/oss_install/standard.py @@ -37,7 +37,7 @@ class OffshoreSubstationInstallation(InstallPhase): "feeder": "dict | str", "site": {"distance": "km", "depth": "m"}, "port": { - "num_cranes": "int", + "num_cranes": "int (optional, default: 1)", "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, diff --git a/ORBIT/phases/install/turbine_install/standard.py b/ORBIT/phases/install/turbine_install/standard.py index 8b1e681f..d4683cbb 100644 --- a/ORBIT/phases/install/turbine_install/standard.py +++ b/ORBIT/phases/install/turbine_install/standard.py @@ -51,7 +51,7 @@ class TurbineInstallation(InstallPhase): "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, "port": { - "num_cranes": "int", + "num_cranes": "int (optional, default: 1)", "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, diff --git a/library/project/config/example_array_cable_install.yaml b/library/project/config/example_array_cable_install.yaml index 2c1408af..9230d3fd 100644 --- a/library/project/config/example_array_cable_install.yaml +++ b/library/project/config/example_array_cable_install.yaml @@ -6,6 +6,5 @@ site: distance: 50 port: monthly_rate: 10000 - num_cranes: 1 array_cable_install_vessel: example_cable_lay_vessel array_cable_bury_vessel: example_cable_lay_vessel diff --git a/library/project/config/example_custom_array_project_manager.yaml b/library/project/config/example_custom_array_project_manager.yaml index e3069b4c..3ca16364 100644 --- a/library/project/config/example_custom_array_project_manager.yaml +++ b/library/project/config/example_custom_array_project_manager.yaml @@ -7,7 +7,6 @@ plant: num_turbines: 67 port: monthly_rate: 10000 - num_cranes: 1 site: depth: 20 distance: 50 diff --git a/tests/data/library/project/config/complete_project.yaml b/tests/data/library/project/config/complete_project.yaml index 2c6898f9..5a29b68a 100644 --- a/tests/data/library/project/config/complete_project.yaml +++ b/tests/data/library/project/config/complete_project.yaml @@ -40,8 +40,6 @@ plant: row_spacing: 7 substation_distance: 1 turbine_spacing: 7 -port: - num_cranes: 1 scour_protection_design: cost_per_tonne: 40 scour_protection_depth: 1 diff --git a/tests/data/library/project/config/library.yaml b/tests/data/library/project/config/library.yaml index 77996402..e844eac2 100644 --- a/tests/data/library/project/config/library.yaml +++ b/tests/data/library/project/config/library.yaml @@ -15,7 +15,6 @@ plant: num_turbines: 10 port: monthly_rate: 10000 - num_cranes: 1 site: depth: 15 distance: 100 diff --git a/tests/data/library/project/config/project_manager.yaml b/tests/data/library/project/config/project_manager.yaml index ce5b22e4..47187b96 100644 --- a/tests/data/library/project/config/project_manager.yaml +++ b/tests/data/library/project/config/project_manager.yaml @@ -15,7 +15,6 @@ plant: turbine_spacing: 7 port: monthly_rate: 10000 - num_cranes: 1 site: depth: 15 distance: 50 diff --git a/tests/data/library/project/config/scour_protection_install.yaml b/tests/data/library/project/config/scour_protection_install.yaml index 120a96d6..fd23223c 100644 --- a/tests/data/library/project/config/scour_protection_install.yaml +++ b/tests/data/library/project/config/scour_protection_install.yaml @@ -3,7 +3,6 @@ plant: turbine_spacing: 5 port: monthly_rate: 100000 - num_cranes: 1 scour_protection: tons_per_substructure: 2000 spi_vessel: test_scour_protection_vessel From 6c383a197f59eff2301c22f648b82948d20aa368 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 27 May 2020 13:36:17 -0600 Subject: [PATCH 29/43] Line and anchor mass added to MooringSystemDesign. Structure for MooringSystemInstallation started. --- ORBIT/library.py | 1 + ORBIT/phases/design/mooring_system_design.py | 51 +++++++-- .../install/mooring_install/__init__.py | 9 ++ .../phases/install/mooring_install/mooring.py | 103 ++++++++++++++++++ 4 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 ORBIT/phases/install/mooring_install/__init__.py create mode 100644 ORBIT/phases/install/mooring_install/mooring.py diff --git a/ORBIT/library.py b/ORBIT/library.py index 00011747..599c5a0f 100755 --- a/ORBIT/library.py +++ b/ORBIT/library.py @@ -273,6 +273,7 @@ def export_library_specs(key, filename, data, file_ext="yaml"): "spi_vessel": "vessels", "trench_dig_vessel": "vessels", "feeder": "vessels", + "mooring_install_vessel": "vessels", "wtiv": "vessels", "towing_vessel": "vessels", "support_vessel": "vessels", diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py index f3bd4cd4..b9f002f5 100644 --- a/ORBIT/phases/design/mooring_system_design.py +++ b/ORBIT/phases/design/mooring_system_design.py @@ -6,6 +6,8 @@ __email__ = "jake.nunemaker@nrel.gov" +from math import sqrt + from ORBIT.phases.design import DesignPhase @@ -24,7 +26,16 @@ class MooringSystemDesign(DesignPhase): }, } - output_config = {"mooring": {"anchor_type": "str", "lines": "int"}} + output_config = { + "mooring_system": { + "num_lines": "int", + "line_diam": "m, float", + "line_mass": "t", + "line_length": "m", + "anchor_mass": "t", + "anchor_type": "str", + } + } def __init__(self, config, **kwargs): """ @@ -53,8 +64,10 @@ def run(self): self.determine_mooring_line() self.calculate_breaking_load() - self.calculate_line_length() - self.calculate_anchor_cost() + self.calculate_line_length_mass() + self.calculate_anchor_mass_cost() + + self._outputs["mooring_system"] = {**self.design_result} def determine_mooring_line(self): """ @@ -66,14 +79,17 @@ def determine_mooring_line(self): if fit <= 0.09: self.line_diam = 0.09 + self.line_mass_per_m = 0.161 self.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 else: self.line_diam = 0.15 + self.line_mass_per_m = 0.450 self.line_cost_rate = 1088.0 def calculate_breaking_load(self): @@ -85,9 +101,9 @@ def calculate_breaking_load(self): 419449 * (self.line_diam ** 2) + 93415 * self.line_diam - 3577.9 ) - def calculate_line_length(self): + def calculate_line_length_mass(self): """ - Returns the mooring line length. + Returns the mooring line length and mass. """ if self.anchor_type == "Drag Embedment": @@ -101,12 +117,23 @@ def calculate_line_length(self): 0.0002 * (depth ** 2) + 1.264 * depth + 47.776 + fixed ) - def calculate_anchor_cost(self): + self.line_mass = self.line_length * self.line_mass_per_m + + def calculate_anchor_mass_cost(self): """ - Returns the cost of drag embedment anchors. + 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. """ - self.anchor_cost = self.breaking_load / 9.81 / 20.0 * 2000.0 + 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 def calculate_total_cost(self): """ @@ -123,14 +150,12 @@ def calculate_total_cost(self): def design_result(self): """Returns the results of the design phase.""" - # TODO: Line mass/km - # TODO: Anchor mass - # TODO: Map with upcoming expected inputs of MooringSystemInstallation. - return { "num_lines": self.num_lines, "line_diam": self.line_diam, + "line_mass": self.line_mass, "line_length": self.line_length, + "anchor_mass": self.anchor_mass, "anchor_type": self.anchor_type, } @@ -157,8 +182,10 @@ def detailed_output(self): return { "num_lines": self.num_lines, "line_diam": self.line_diam, + "line_mass": self.line_mass, "line_length": self.line_length, "anchor_type": self.anchor_type, + "anchor_mass": self.anchor_mass, "anchor_cost": self.anchor_cost, "system_cost": self.calculate_total_cost(), } diff --git a/ORBIT/phases/install/mooring_install/__init__.py b/ORBIT/phases/install/mooring_install/__init__.py new file mode 100644 index 00000000..6e6af116 --- /dev/null +++ b/ORBIT/phases/install/mooring_install/__init__.py @@ -0,0 +1,9 @@ +"""Mooring Installation Modules.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +from .mooring import MooringSystemInstallation diff --git a/ORBIT/phases/install/mooring_install/mooring.py b/ORBIT/phases/install/mooring_install/mooring.py new file mode 100644 index 00000000..bbba35d8 --- /dev/null +++ b/ORBIT/phases/install/mooring_install/mooring.py @@ -0,0 +1,103 @@ +"""Installation strategies for mooring systems.""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "jake.nunemaker@nrel.gov" + + +import simpy +from marmot import le, process + +from ORBIT.core import Cargo, Vessel +from ORBIT.core._defaults import process_times as pt +from ORBIT.phases.install import InstallPhase + + +class MooringSystemInstallation(InstallPhase): + """Module to model the installation of mooring systems at sea.""" + + phase = "Mooring System Installation" + + #: + expected_config = { + "mooring_install_vessel": "dict | str", + "site": {"depth": "m", "distance": "km"}, + "plant": {"num_turbines": "int"}, + "mooring_system": { + "num_lines": "int", + "line_diam": "m, float", + "line_mass": "t", + "line_length": "m", + "anchor_mass": "t", + "anchor_type": "str", + }, + } + + def __init__(self, config, weather=None, **kwargs): + """ + Creates an instance of `MooringSystemInstallation`. + + Parameters + ---------- + config : dict + Simulation specific configuration. + weather : np.array + Weather data at site. + """ + + super().__init__(weather, **kwargs) + + config = self.initialize_library(config, **kwargs) + self.config = self.validate_config(config) + self.extract_defaults() + + self.setup_simulation(**kwargs) + + def setup_simulation(self, **kwargs): + """""" + + self.initialize_port() + self.initialize_installation_vessel() + self.initialize_components() + + def initialize_installation_vessel(self): + """Initializes the mooring system installation vessel.""" + + vessel_specs = self.config.get("mooring_install_vessel", None) + name = vessel_specs.get("name", "Mooring System Installation Vessel") + + vessel = Vessel(name, vessel_specs) + self.env.register(vessel) + + vessel.initialize() + vessel.at_port = True + vessel.at_site = False + self.vessel = vessel + + def initialize_components(self): + """Initializes the Cargo components at port.""" + pass + + +@process +def install_mooring_system(vessel, port, distance, **kwargs): + """ + Logic for the Mooring System Installation Vessel. + + Parameters + ---------- + """ + + pass + + +class MooringLine(Cargo): + pass + + +class MooringAnchor(Cargo): + pass + + +f From faf937d90789c583fcf9a0e305e954ad65af00d3 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Thu, 28 May 2020 16:17:33 -0600 Subject: [PATCH 30/43] Initial version of MooringSystemInstallation. --- ORBIT/core/_defaults.py | 5 + ORBIT/core/logic/vessel_logic.py | 14 +- ORBIT/core/vessel.py | 13 +- ORBIT/manager.py | 8 +- .../phases/design/semi_submersible_design.py | 2 + ORBIT/phases/design/spar_design.py | 2 + ORBIT/phases/install/__init__.py | 1 + .../phases/install/mooring_install/mooring.py | 257 +++++++++++++++++- library/vessels/example_support_vessel.yaml | 4 + 9 files changed, 279 insertions(+), 27 deletions(-) diff --git a/ORBIT/core/_defaults.py b/ORBIT/core/_defaults.py index 3b0470ea..40ddd6e8 100755 --- a/ORBIT/core/_defaults.py +++ b/ORBIT/core/_defaults.py @@ -53,6 +53,11 @@ "blade_fasten_time": 1.5, # hr "blade_release_time": 1, # hr "blade_attach_time": 3.5, # hr + # Mooring System + "mooring_system_load_time": 5, # hr + "mooring_site_survey_time": 4, # hr + "suction_pile_install_time": 11, # hr + "drag_embed_install_time": 5, # hr # Misc. "site_position_time": 2, # hr "rov_survey_time": 1, # hr diff --git a/ORBIT/core/logic/vessel_logic.py b/ORBIT/core/logic/vessel_logic.py index 8021d966..1e7c8c1d 100644 --- a/ORBIT/core/logic/vessel_logic.py +++ b/ORBIT/core/logic/vessel_logic.py @@ -228,12 +228,14 @@ def get_list_of_items_from_port(vessel, port, items, **kwargs): for item in buffer: action, time = item.fasten(**kwargs) vessel.storage.put_item(item) - yield vessel.task( - action, - time, - constraints=vessel.transit_limits, - **kwargs, - ) + + if time > 0: + yield vessel.task( + action, + time, + constraints=vessel.transit_limits, + **kwargs, + ) else: raise ItemNotFound(items) diff --git a/ORBIT/core/vessel.py b/ORBIT/core/vessel.py index 5e2651dc..74c6fdf6 100644 --- a/ORBIT/core/vessel.py +++ b/ORBIT/core/vessel.py @@ -269,12 +269,13 @@ def get_item_from_storage( raise e action, time = item.release(**kwargs) - yield self.task( - action, - time, - constraints=self.transit_limits, - cost=self.operation_cost(time), - ) + if time > 0: + yield self.task( + action, + time, + constraints=self.transit_limits, + cost=self.operation_cost(time), + ) if release and vessel.storage.any_remaining(_type) is False: vessel.release.succeed() diff --git a/ORBIT/manager.py b/ORBIT/manager.py index 1a479f13..8748ef2e 100644 --- a/ORBIT/manager.py +++ b/ORBIT/manager.py @@ -19,23 +19,25 @@ from ORBIT.phases import DesignPhase, InstallPhase from ORBIT.library import initialize_library, extract_library_data from ORBIT.phases.design import ( + SparDesign, MonopileDesign, ArraySystemDesign, ExportSystemDesign, ProjectDevelopment, MooringSystemDesign, ScourProtectionDesign, + SemiSubmersibleDesign, CustomArraySystemDesign, OffshoreSubstationDesign, ) from ORBIT.phases.install import ( TurbineInstallation, MonopileInstallation, + MooredSubInstallation, ArrayCableInstallation, ExportCableInstallation, ScourProtectionInstallation, OffshoreSubstationInstallation, - MooredSubInstallation ) from ORBIT.core.exceptions import ( PhaseNotFound, @@ -59,6 +61,8 @@ class ProjectManager: ScourProtectionDesign, OffshoreSubstationDesign, MooringSystemDesign, + SemiSubmersibleDesign, + SparDesign, ] _install_phases = [ @@ -68,7 +72,7 @@ class ProjectManager: ArrayCableInstallation, ExportCableInstallation, ScourProtectionInstallation, - MooredSubInstallation + MooredSubInstallation, ] def __init__(self, config, library_path=None, weather=None): diff --git a/ORBIT/phases/design/semi_submersible_design.py b/ORBIT/phases/design/semi_submersible_design.py index 0ae87d44..74392e8e 100644 --- a/ORBIT/phases/design/semi_submersible_design.py +++ b/ORBIT/phases/design/semi_submersible_design.py @@ -27,6 +27,8 @@ class SemiSubmersibleDesign(DesignPhase): }, } + output_config = {} + def __init__(self, config, **kwargs): """ Creates an instance of `SemiSubmersibleDesign`. diff --git a/ORBIT/phases/design/spar_design.py b/ORBIT/phases/design/spar_design.py index 2e8ac0f1..a5531161 100644 --- a/ORBIT/phases/design/spar_design.py +++ b/ORBIT/phases/design/spar_design.py @@ -29,6 +29,8 @@ class SparDesign(DesignPhase): }, } + output_config = {} + def __init__(self, config, **kwargs): """ Creates an instance of `SparDesign`. diff --git a/ORBIT/phases/install/__init__.py b/ORBIT/phases/install/__init__.py index 874968e0..e584656c 100644 --- a/ORBIT/phases/install/__init__.py +++ b/ORBIT/phases/install/__init__.py @@ -8,6 +8,7 @@ from .install_phase import InstallPhase # isort:skip from .oss_install import OffshoreSubstationInstallation from .cable_install import ArrayCableInstallation, ExportCableInstallation +from .mooring_install import MooringSystemInstallation from .turbine_install import TurbineInstallation from .monopile_install import MonopileInstallation from .quayside_assembly_tow import ( diff --git a/ORBIT/phases/install/mooring_install/mooring.py b/ORBIT/phases/install/mooring_install/mooring.py index bbba35d8..1d058fed 100644 --- a/ORBIT/phases/install/mooring_install/mooring.py +++ b/ORBIT/phases/install/mooring_install/mooring.py @@ -6,12 +6,13 @@ __email__ = "jake.nunemaker@nrel.gov" -import simpy -from marmot import le, process +from marmot import process from ORBIT.core import Cargo, Vessel +from ORBIT.core.logic import position_onsite, get_list_of_items_from_port from ORBIT.core._defaults import process_times as pt from ORBIT.phases.install import InstallPhase +from ORBIT.core.exceptions import ItemNotFound class MooringSystemInstallation(InstallPhase): @@ -26,11 +27,9 @@ class MooringSystemInstallation(InstallPhase): "plant": {"num_turbines": "int"}, "mooring_system": { "num_lines": "int", - "line_diam": "m, float", "line_mass": "t", - "line_length": "m", "anchor_mass": "t", - "anchor_type": "str", + "anchor_type": "str (optional, default: 'Suction Pile')", }, } @@ -61,6 +60,18 @@ def setup_simulation(self, **kwargs): self.initialize_installation_vessel() self.initialize_components() + depth = self.config["site"]["depth"] + distance = self.config["site"]["distance"] + + install_mooring_systems( + self.vessel, + self.port, + distance, + depth, + self.number_systems, + **kwargs, + ) + def initialize_installation_vessel(self): """Initializes the mooring system installation vessel.""" @@ -77,27 +88,247 @@ def initialize_installation_vessel(self): def initialize_components(self): """Initializes the Cargo components at port.""" - pass + + system = MooringSystem(**self.config["mooring_system"]) + self.number_systems = self.config["plant"]["num_turbines"] + + for _ in range(self.number_systems): + self.port.put(system) + + @property + def detailed_output(self): + """Detailed outputs of the scour protection installation.""" + + outputs = {self.phase: {**self.agent_efficiencies}} + + return outputs @process -def install_mooring_system(vessel, port, distance, **kwargs): +def install_mooring_systems(vessel, port, distance, depth, systems, **kwargs): """ Logic for the Mooring System Installation Vessel. Parameters ---------- + vessel : Vessel + Mooring System Installation Vessel + port : Port + distance : int | float + Distance between port and site (km). + systems : int + Total systems to install. + """ + + n = 0 + while n < systems: + if vessel.at_port: + try: + # Get mooring systems from port. + yield get_list_of_items_from_port( + vessel, port, ["MooringSystem"], **kwargs + ) + + except ItemNotFound: + # If no items are at port and vessel.storage.items is empty, + # the job is done + if not vessel.storage.items: + vessel.submit_debug_log( + message="Item not found. Shutting down." + ) + break + + # Transit to site + vessel.update_trip_data() + vessel.at_port = False + yield vessel.transit(distance) + vessel.at_site = True + + if vessel.at_site: + + if vessel.storage.items: + + system = yield vessel.get_item_from_storage( + "MooringSystem", **kwargs + ) + for _ in range(system.num_lines): + yield position_onsite(vessel, **kwargs) + yield perform_mooring_site_survey(vessel, **kwargs) + yield install_mooring_anchor( + vessel, depth, system.anchor_type, **kwargs + ) + yield install_mooring_line(vessel, depth, **kwargs) + + n += 1 + + else: + # Transit to port + vessel.at_site = False + yield vessel.transit(distance) + vessel.at_port = True + + vessel.submit_debug_log(message="Mooring systems installation complete!") + + +@process +def perform_mooring_site_survey(vessel, **kwargs): + """ + Calculates time required to perform a mooring system survey. + + Parameters + ---------- + vessel : Vessel + Vessel to perform action. + + Yields + ------ + vessel.task representing time to "Perform Mooring Site Survey". + """ + + key = "mooring_site_survey_time" + survey_time = kwargs.get(key, pt[key]) + + yield vessel.task( + "Perform Mooring Site Survey", + survey_time, + constraints=vessel.transit_limits, + **kwargs, + ) + + +@process +def install_mooring_anchor(vessel, depth, _type, **kwargs): + """ + Calculates time required to install a mooring system anchor. + + Parameters + ---------- + vessel : Vessel + Vessel to perform action. + depth : int | float + Depth at site (m). + _type : str + Anchor type. 'Suction Pile' or 'Drag Embedment'. + + Yields + ------ + vessel.task representing time to install mooring anchor. """ - pass + if _type == "Suction Pile": + key = "suction_pile_install_time" + task = "Install Suction Pile Anchor" + fixed = kwargs.get(key, pt[key]) + + elif _type == "Drag Embedment": + key = "drag_embed_install_time" + task = "Install Drag Embedment Anchor" + fixed = kwargs.get(key, pt[key]) + + else: + raise ValueError( + f"Mooring System Anchor Type: {_type} not recognized." + ) + + install_time = fixed + 0.005 * depth + yield vessel.task( + task, install_time, constraints=vessel.transit_limits, **kwargs + ) + + +@process +def install_mooring_line(vessel, depth, **kwargs): + """ + Calculates time required to install a mooring system line. + + Parameters + ---------- + vessel : Vessel + Vessel to perform action. + depth : int | float + Depth at site (m). + + Yields + ------ + vessel.task representing time to install mooring line. + """ + + install_time = 0.005 * depth + + yield vessel.task( + "Install Mooring Line", + install_time, + constraints=vessel.transit_limits, + **kwargs, + ) + +class MooringSystem(Cargo): + """Mooring System Cargo""" -class MooringLine(Cargo): - pass + def __init__( + self, + num_lines=None, + line_mass=None, + anchor_mass=None, + anchor_type="Suction Pile", + **kwargs, + ): + """Creates an instance of MooringSystem""" + + self.num_lines = num_lines + self.line_mass = line_mass + self.anchor_mass = anchor_mass + self.anchor_type = anchor_type + + self.deck_space = 0 + + @property + def mass(self): + """Returns total system mass in t.""" + + return self.num_lines * (self.line_mass + self.anchor_mass) + + @staticmethod + def load(**kwargs): + """Returns time required to load a mooring system at port.""" + + key = "mooring_system_load_time" + time = kwargs.get(key, pt[key]) + + return "Load Mooring System", time + + @staticmethod + def fasten(**kwargs): + """Dummy method to work with `get_list_of_items_from_port`.""" + + return "", 0 + + @staticmethod + 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 -class MooringAnchor(Cargo): - pass + elif self.anchor_type == "Drag Embedment": + fixed = 5 + else: + raise ValueError( + f"Mooring System Anchor Type: {self.anchor_type} not recognized." + ) -f + return fixed + 0.005 * depth diff --git a/library/vessels/example_support_vessel.yaml b/library/vessels/example_support_vessel.yaml index 89dbf161..d0ca1b99 100644 --- a/library/vessels/example_support_vessel.yaml +++ b/library/vessels/example_support_vessel.yaml @@ -7,3 +7,7 @@ vessel_specs: overall_length: 150 # m mobilization_days: 7 # days mobilization_mult: 1 # Mobilization multiplier applied to 'day_rate' +storage_specs: + max_cargo: 5000 # t + max_deck_load: 8 # t/m^2 + max_deck_space: 1000 # m^2 From 050d7b3ecd120d0f98c6226c27671e8d07fd7e7f Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Fri, 29 May 2020 15:52:44 -0600 Subject: [PATCH 31/43] Added tests for MooringSystemInstallation. --- ORBIT/manager.py | 2 + .../phases/install/mooring_install/mooring.py | 17 ++-- .../config/mooring_system_install.yaml | 10 ++ .../library/vessels/test_support_vessel.yaml | 13 +++ .../mooring_install/test_mooring_install.py | 94 +++++++++++++++++++ 5 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 tests/data/library/project/config/mooring_system_install.yaml create mode 100644 tests/data/library/vessels/test_support_vessel.yaml create mode 100644 tests/phases/install/mooring_install/test_mooring_install.py diff --git a/ORBIT/manager.py b/ORBIT/manager.py index 8748ef2e..25dc38e1 100644 --- a/ORBIT/manager.py +++ b/ORBIT/manager.py @@ -36,6 +36,7 @@ MooredSubInstallation, ArrayCableInstallation, ExportCableInstallation, + MooringSystemInstallation, ScourProtectionInstallation, OffshoreSubstationInstallation, ) @@ -73,6 +74,7 @@ class ProjectManager: ExportCableInstallation, ScourProtectionInstallation, MooredSubInstallation, + MooringSystemInstallation, ] def __init__(self, config, library_path=None, weather=None): diff --git a/ORBIT/phases/install/mooring_install/mooring.py b/ORBIT/phases/install/mooring_install/mooring.py index 1d058fed..e0d3f4f0 100644 --- a/ORBIT/phases/install/mooring_install/mooring.py +++ b/ORBIT/phases/install/mooring_install/mooring.py @@ -54,7 +54,12 @@ def __init__(self, config, weather=None, **kwargs): self.setup_simulation(**kwargs) def setup_simulation(self, **kwargs): - """""" + """ + Sets up the required simulation infrastructure: + - initializes port + - initializes installation vessel + - initializes mooring systems at port. + """ self.initialize_port() self.initialize_installation_vessel() @@ -290,20 +295,14 @@ def mass(self): return self.num_lines * (self.line_mass + self.anchor_mass) @staticmethod - def load(**kwargs): - """Returns time required to load a mooring system at port.""" + def fasten(**kwargs): + """Dummy method to work with `get_list_of_items_from_port`.""" key = "mooring_system_load_time" time = kwargs.get(key, pt[key]) return "Load Mooring System", time - @staticmethod - def fasten(**kwargs): - """Dummy method to work with `get_list_of_items_from_port`.""" - - return "", 0 - @staticmethod def release(**kwargs): """Dummy method to work with `get_list_of_items_from_port`.""" diff --git a/tests/data/library/project/config/mooring_system_install.yaml b/tests/data/library/project/config/mooring_system_install.yaml new file mode 100644 index 00000000..a9e34a60 --- /dev/null +++ b/tests/data/library/project/config/mooring_system_install.yaml @@ -0,0 +1,10 @@ +plant: + num_turbines: 50 +mooring_install_vessel: test_support_vessel +site: + depth: 40 + distance: 30 +mooring_system: + num_lines: 3 + line_mass: 500 + anchor_mass: 100 diff --git a/tests/data/library/vessels/test_support_vessel.yaml b/tests/data/library/vessels/test_support_vessel.yaml new file mode 100644 index 00000000..d0ca1b99 --- /dev/null +++ b/tests/data/library/vessels/test_support_vessel.yaml @@ -0,0 +1,13 @@ +transport_specs: + max_waveheight: 3 # m + max_windspeed: 20 # m/s + transit_speed: 10 # km/h +vessel_specs: + day_rate: 100000 # USD/day + overall_length: 150 # m + mobilization_days: 7 # days + mobilization_mult: 1 # Mobilization multiplier applied to 'day_rate' +storage_specs: + max_cargo: 5000 # t + max_deck_load: 8 # t/m^2 + max_deck_space: 1000 # m^2 diff --git a/tests/phases/install/mooring_install/test_mooring_install.py b/tests/phases/install/mooring_install/test_mooring_install.py new file mode 100644 index 00000000..48570cfc --- /dev/null +++ b/tests/phases/install/mooring_install/test_mooring_install.py @@ -0,0 +1,94 @@ +""" +Testing framework for the `MooringSystemInstallation` class. +""" + +__author__ = "Jake Nunemaker" +__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" +__maintainer__ = "Jake Nunemaker" +__email__ = "Jake.Nunemaker@nrel.gov" + + +from copy import deepcopy + +import pandas as pd +import pytest + +from tests.data import test_weather +from ORBIT.library import extract_library_specs +from ORBIT.core._defaults import process_times as pt +from ORBIT.phases.install import MooringSystemInstallation + +config = extract_library_specs("config", "mooring_system_install") + + +def test_simulation_creation(): + sim = MooringSystemInstallation(config) + + assert sim.config == config + assert sim.env + assert sim.port + assert sim.vessel + assert sim.number_systems + + +@pytest.mark.parametrize( + "weather", (None, test_weather), ids=["no_weather", "test_weather"] +) +def test_full_run_logging(weather): + sim = MooringSystemInstallation(config, weather=weather) + sim.run() + + lines = ( + config["plant"]["num_turbines"] * config["mooring_system"]["num_lines"] + ) + + df = pd.DataFrame(sim.env.actions) + df = df.assign(shift=(df.time - df.time.shift(1))) + assert (df.duration - df["shift"]).fillna(0.0).abs().max() < 1e-9 + assert df[df.action == "Install Mooring Line"].shape[0] == lines + + assert ~df["cost"].isnull().any() + _ = sim.agent_efficiencies + _ = sim.detailed_output + + +@pytest.mark.parametrize( + "anchor, key", + [ + ("Suction Pile", "suction_pile_install_time"), + ("Drag Embedment", "drag_embed_install_time"), + ], +) +def test_kwargs(anchor, key): + + new = deepcopy(config) + new["mooring_system"]["anchor_type"] = anchor + + sim = MooringSystemInstallation(new) + sim.run() + baseline = sim.total_phase_time + + keywords = ["mooring_system_load_time", "mooring_site_survey_time", key] + + failed = [] + + for kw in keywords: + + default = pt[kw] + kwargs = {kw: default + 2} + + new_sim = MooringSystemInstallation(new, **kwargs) + new_sim.run() + new_time = new_sim.total_phase_time + + if new_time > baseline: + pass + + else: + failed.append(kw) + + if failed: + raise Exception(f"'{failed}' not affecting results.") + + else: + assert True From 7927c6e7feecfb95d4f3a6e2ee68baa2cb1428b6 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Fri, 29 May 2020 16:06:54 -0600 Subject: [PATCH 32/43] Added initial documentation for MooringSystemDesign and MooringSystemInstallation. API references are building but nothing in the doc_ files yet. --- docs/source/api_DesignPhase.rst | 1 + docs/source/api_InstallPhase.rst | 2 ++ docs/source/doc_DesignPhase.rst | 1 + docs/source/doc_InstallPhase.rst | 2 ++ docs/source/phases/design/api_MooringSystemDesign.rst | 8 ++++++++ docs/source/phases/design/doc_MooringSystemDesign.rst | 10 ++++++++++ .../install/mooring/api_MooringSystemInstallation.rst | 8 ++++++++ .../install/mooring/doc_MooringSystemInstallation.rst | 10 ++++++++++ 8 files changed, 42 insertions(+) create mode 100644 docs/source/phases/design/api_MooringSystemDesign.rst create mode 100644 docs/source/phases/design/doc_MooringSystemDesign.rst create mode 100644 docs/source/phases/install/mooring/api_MooringSystemInstallation.rst create mode 100644 docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst diff --git a/docs/source/api_DesignPhase.rst b/docs/source/api_DesignPhase.rst index 3b0741ce..b668a8a1 100644 --- a/docs/source/api_DesignPhase.rst +++ b/docs/source/api_DesignPhase.rst @@ -18,5 +18,6 @@ trends but are not intended to be used for actual designs. phases/design/api_OffshoreSubstationDesign phases/design/api_SemiSubmersibleDesign phases/design/api_SparDesign + phases/design/api_MooringSystemDesign .. phases/design/api_JacketDesign diff --git a/docs/source/api_InstallPhase.rst b/docs/source/api_InstallPhase.rst index 77ef2189..8902c390 100644 --- a/docs/source/api_InstallPhase.rst +++ b/docs/source/api_InstallPhase.rst @@ -17,5 +17,7 @@ description of vessel scheduling within ORBIT, please see `add link`. phases/install/array/api_ArrayCableInstall phases/install/export/api_ExportCableInstall phases/install/oss/api_OffshoreSubstationInstall + phases/install/quayside_towout/api_MooredSubInstallation + phases/install/mooring/api_MooringSystemInstallation .. phases/install/jacket/api_JacketInstall diff --git a/docs/source/doc_DesignPhase.rst b/docs/source/doc_DesignPhase.rst index f5468518..f4121504 100644 --- a/docs/source/doc_DesignPhase.rst +++ b/docs/source/doc_DesignPhase.rst @@ -16,5 +16,6 @@ the model. phases/design/doc_OffshoreSubstationDesign phases/design/doc_SemiSubmersibleDesign phases/design/doc_SparDesign + phases/design/doc_MooringSystemDesign .. phases/design/doc_JacketDesign diff --git a/docs/source/doc_InstallPhase.rst b/docs/source/doc_InstallPhase.rst index f8a13456..be568c3e 100644 --- a/docs/source/doc_InstallPhase.rst +++ b/docs/source/doc_InstallPhase.rst @@ -15,5 +15,7 @@ available in the model. phases/install/array/doc_ArrayCableInstall phases/install/export/doc_ExportCableInstall phases/install/oss/doc_OffshoreSubstationInstall + phases/install/quayside_towout/doc_MooredSubInstallation + phases/install/mooring/doc_MooringSystemInstallation .. phases/install/jacket/doc_JacketInstall diff --git a/docs/source/phases/design/api_MooringSystemDesign.rst b/docs/source/phases/design/api_MooringSystemDesign.rst new file mode 100644 index 00000000..bdc7b7a1 --- /dev/null +++ b/docs/source/phases/design/api_MooringSystemDesign.rst @@ -0,0 +1,8 @@ +Mooring System Design API +========================= + +For detailed methodology, please see +:doc:`Mooring System Design `. + +.. autoclass:: ORBIT.phases.design.MooringSystemDesign + :members: diff --git a/docs/source/phases/design/doc_MooringSystemDesign.rst b/docs/source/phases/design/doc_MooringSystemDesign.rst new file mode 100644 index 00000000..5b1a5b8a --- /dev/null +++ b/docs/source/phases/design/doc_MooringSystemDesign.rst @@ -0,0 +1,10 @@ +Mooring System Design Methodology +================================= + +For details of the code implementation, please see +:doc:`Mooring System Design API `. + +Mooring System Design +--------------------- + +Coming soon! diff --git a/docs/source/phases/install/mooring/api_MooringSystemInstallation.rst b/docs/source/phases/install/mooring/api_MooringSystemInstallation.rst new file mode 100644 index 00000000..2f2295a5 --- /dev/null +++ b/docs/source/phases/install/mooring/api_MooringSystemInstallation.rst @@ -0,0 +1,8 @@ +Mooring System Installation API +=============================== + +For detailed methodology, please see +:doc:`Mooring System Installation Methodology `. + +.. autoclass:: ORBIT.phases.install.MooringSystemInstallation + :members: diff --git a/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst b/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst new file mode 100644 index 00000000..b3fe56a1 --- /dev/null +++ b/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst @@ -0,0 +1,10 @@ +Mooring System Installation Methodology +======================================= + +For details of the code implementation, please see +:doc:`Mooring System Installation API `. + +Overview +-------- + +Coming soon! From 3eb6dbb95fc22716310774efd8813643ccd0b492 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 3 Jun 2020 13:45:40 -0600 Subject: [PATCH 33/43] Added catenary calculation to cable design modules to calculate free_cable_length of floating depth projects. --- ORBIT/phases/design/_cables.py | 67 +++++++++++++++++++ ORBIT/phases/design/array_system_design.py | 5 +- ORBIT/phases/design/export_system_design.py | 6 +- .../phases/design/test_array_system_design.py | 33 +++++++++ .../design/test_export_system_design.py | 21 ++++++ 5 files changed, 129 insertions(+), 3 deletions(-) diff --git a/ORBIT/phases/design/_cables.py b/ORBIT/phases/design/_cables.py index e81eeedf..9611ce6e 100755 --- a/ORBIT/phases/design/_cables.py +++ b/ORBIT/phases/design/_cables.py @@ -10,6 +10,7 @@ from collections import Counter, OrderedDict import numpy as np +from scipy.optimize import fsolve from ORBIT.library import extract_library_specs from ORBIT.phases.design import DesignPhase @@ -310,6 +311,72 @@ def _initialize_cables(self): name = specs["name"] self.cables[name] = Cable(specs) + def _get_touchdown_distance(self): + """ + Returns the cable touchdown distance measured from the centerpoint of + the substructure. + + If depth <= 60, default is 0km (straight down assumed for fixed bottom). + If depth > 60, default is 0.3 * depth. + """ + + _design = f"{self.cable_type}_system_design" + depth = self.config["site"]["depth"] + touchdown = self.config[_design].get("touchdown_distance", None) + + if touchdown is not None: + self.touchdown = touchdown + + else: + if depth <= 60: + self.touchdown = 0 + + else: + self.touchdown = depth * 0.3 + + @staticmethod + def _catenary(a, *data): + """Simple catenary equation.""" + + d, h = data + res = a * np.cosh(h / a) - (d + a) + return res + + def _get_catenary_length(self, d, h): + """ + Returns the catenary length of a cable that touches down at depth `d` + and horizontal distance `h`. + + Returns + ------- + float + Catenary length. + """ + + a = fsolve(self._catenary, 8, (d, h)) + + x = np.linspace(0, h) + y = a * np.cosh(x / a) - a + + if not np.isclose(y[-1], d): + print( + "Warning: Catenary calculation failed. Reverting to simple vertical profile." + ) + return d + + return np.trapz(np.sqrt(1 + np.gradient(y, x) ** 2), x) + + @property + def free_cable_length(self): + """Returns the length of the vertical portion of a cable section in km.""" + + depth = self.config["site"]["depth"] + + if not self.touchdown: + return depth / 1000 + + return self._get_catenary_length(depth, self.touchdown) / 1000 + @property def cable_lengths_by_type(self): """ diff --git a/ORBIT/phases/design/array_system_design.py b/ORBIT/phases/design/array_system_design.py index d9b1887d..c8904ff6 100644 --- a/ORBIT/phases/design/array_system_design.py +++ b/ORBIT/phases/design/array_system_design.py @@ -79,6 +79,7 @@ class ArraySystemDesign(CableSystem): "array_system_design": { "design_time": "hrs (optional)", "cables": "list | str", + "touchdown_distance": "m (optional, default: 0)", "average_exclusion_percent": "float (optional)", }, } @@ -102,6 +103,7 @@ def __init__(self, config, **kwargs): self.exclusion = 1 + self.config["array_system_design"].get( "average_exclusion_percent", 0.0 ) + self._get_touchdown_distance() self.extract_phase_kwargs(**kwargs) self.system = Plant(self.config) @@ -331,7 +333,8 @@ def _create_cable_section_lengths(self): if getattr(self, "sections_cable_lengths", np.zeros(1)).sum() == 0: self.sections_cable_lengths = ( self.sections_distance * self.exclusion - + (2 * self.system.site_depth) + + (2 * self.free_cable_length) + - (2 * self.touchdown / 1000) ) self.sections_cables = np.full( (self.num_strings, self.num_turbines_full_string), None diff --git a/ORBIT/phases/design/export_system_design.py b/ORBIT/phases/design/export_system_design.py index 91bee5ec..e415cb91 100755 --- a/ORBIT/phases/design/export_system_design.py +++ b/ORBIT/phases/design/export_system_design.py @@ -42,6 +42,7 @@ class ExportSystemDesign(CableSystem): "export_system_design": { "cables": "str", "num_redundant": "int (optional)", + "touchdown_distance": "m (optional, default: 0)", "percent_added_length": "float (optional)", }, } @@ -78,6 +79,7 @@ def __init__(self, config, **kwargs): self._depth = config["site"]["depth"] self._plant_capacity = self.config["plant"]["capacity"] self._distance_to_landfall = config["site"]["distance_to_landfall"] + self._get_touchdown_distance() try: self._distance_to_interconnection = config["landfall"][ "interconnection_distance" @@ -136,8 +138,8 @@ def compute_cable_length(self): added_length = 1.0 + self._design.get("percent_added_length", 0.0) self.length = round( ( - (self._depth / 1000.0) # convert to km - + self._distance_to_landfall + self.free_cable_length + + (self._distance_to_landfall - self.touchdown / 1000) + self._distance_to_interconnection ) * added_length, diff --git a/tests/phases/design/test_array_system_design.py b/tests/phases/design/test_array_system_design.py index e71f55a5..e464c5e6 100644 --- a/tests/phases/design/test_array_system_design.py +++ b/tests/phases/design/test_array_system_design.py @@ -206,3 +206,36 @@ def test_correct_turbines(): with pytest.raises(ValueError): array.run() + + +def test_floating_calculations(): + + base = deepcopy(config_full_ring) + base["site"]["depth"] = 50 + number = base["plant"]["num_turbines"] + + sim = ArraySystemDesign(base) + sim.run() + + base_length = sim.total_length + + floating_no_cat = deepcopy(base) + floating_no_cat["site"]["depth"] = 250 + floating_no_cat["array_system_design"]["touchdown_distance"] = 0 + + sim2 = ArraySystemDesign(floating_no_cat) + sim2.run() + + no_cat_length = sim2.total_length + assert no_cat_length == pytest.approx( + base_length + 2 * (200 / 1000) * number + ) + + floating_cat = deepcopy(base) + floating_cat["site"]["depth"] = 250 + + sim3 = ArraySystemDesign(floating_cat) + sim3.run() + + with_cat_length = sim3.total_length + assert with_cat_length < no_cat_length diff --git a/tests/phases/design/test_export_system_design.py b/tests/phases/design/test_export_system_design.py index c8a8565d..76ffcae0 100644 --- a/tests/phases/design/test_export_system_design.py +++ b/tests/phases/design/test_export_system_design.py @@ -5,6 +5,7 @@ __maintainer__ = "Rob Hammond" __email__ = "robert.hammond@nrel.gov" +from copy import deepcopy import pytest @@ -100,3 +101,23 @@ def test_design_result(): assert cables["sections"] == [export.length] assert cables["number"] == 11 assert cables["linear_density"] == export.cable.linear_density + + +def test_floating_length_calculations(): + + base = deepcopy(config) + base["site"]["depth"] = 250 + base["export_system_design"]["touchdown_distance"] = 0 + + sim = ExportSystemDesign(base) + sim.run() + + base_length = sim.total_length + + with_cat = deepcopy(config) + with_cat["site"]["depth"] = 250 + + new = ExportSystemDesign(with_cat) + new.run() + + assert new.total_length < base_length From 6b4e62c2e90cd832b6d8b82f27847d19768e5166 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 3 Jun 2020 15:28:31 -0600 Subject: [PATCH 34/43] Updated cable installation modules to subtract the free cable length from the trench and burial processes. --- ORBIT/phases/install/cable_install/array.py | 33 ++++++++++++------- ORBIT/phases/install/cable_install/export.py | 31 ++++++++++++----- .../cable_install/test_export_install.py | 2 +- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/ORBIT/phases/install/cable_install/array.py b/ORBIT/phases/install/cable_install/array.py index 670eb233..a64f7f46 100755 --- a/ORBIT/phases/install/cable_install/array.py +++ b/ORBIT/phases/install/cable_install/array.py @@ -40,9 +40,10 @@ class ArrayCableInstallation(InstallPhase): "array_cable_install_vessel": "str", "array_cable_bury_vessel": "str (optional)", "array_cable_trench_vessel": "str (optional)", - "site": {"distance": "km"}, + "site": {"distance": "km", "depth": "m"}, "array_system": { "num_strings": "int (optional, default: 10)", + "free_cable_length": "km (optional, default: 'depth')", "cables": { "name (variable)": { "linear_density": "t/km", @@ -82,14 +83,18 @@ def setup_simulation(self, **kwargs): - """ + depth = self.config["site"]["depth"] + system = self.config["array_system"] + self.free_cable_length = system.get("free_cable_length", depth / 1000) + self.initialize_installation_vessel() self.initialize_burial_vessel() self.initialize_trench_vessel() - self.num_strings = self.config["array_system"].get("num_strings", 10) + self.num_strings = system.get("num_strings", 10) self.cable_data = [ (Cable(data["linear_density"]), deepcopy(data["cable_sections"])) - for _, data in self.config["array_system"]["cables"].items() + for _, data in system["cables"].items() ] # Perform cable installation @@ -100,6 +105,7 @@ def setup_simulation(self, **kwargs): num_strings=self.num_strings, burial_vessel=self.bury_vessel, trench_vessel=self.trench_vessel, + free_cable_length=self.free_cable_length, **kwargs, ) @@ -171,6 +177,7 @@ def install_array_cables( num_strings, burial_vessel=None, trench_vessel=None, + free_cable_length=None, **kwargs, ): """ @@ -197,14 +204,17 @@ def install_array_cables( breakpoints = list(np.linspace(1 / num_strings, 1, num_strings)) trench_sections = [] - total_cable_distance = 0 + total_cable_length = 0 installed = 0 for cable, sections in cable_data: for s in sections: - d_i, num_i, *_ = s - trench_sections.extend([d_i] * num_i) - total_cable_distance += d_i * num_i + l, num_i, *_ = s + total_cable_length += l * num_i + + _trench_length = max(0, l - 2 * free_cable_length) + if _trench_length: + trench_sections.extend([_trench_length] * num_i) ## Trenching Process # Conduct trenching along cable routes before laying cable @@ -299,7 +309,9 @@ def install_array_cables( else: yield lay_cable(vessel, section, **specs) - to_bury.append(section) + _bury = max(0, (section - 2 * free_cable_length)) + if _bury: + to_bury.append(_bury) # Post cable laying procedure (at substructure 2) yield prep_cable(vessel, **kwargs) @@ -308,10 +320,7 @@ def install_array_cables( if burial_vessel is None: breakpoints = check_for_completed_string( - vessel, - installed, - total_cable_distance, - breakpoints, + vessel, installed, total_cable_length, breakpoints ) # Transit back to port diff --git a/ORBIT/phases/install/cable_install/export.py b/ORBIT/phases/install/cable_install/export.py index 41850ac5..612c1e71 100755 --- a/ORBIT/phases/install/cable_install/export.py +++ b/ORBIT/phases/install/cable_install/export.py @@ -85,7 +85,10 @@ def setup_simulation(self, **kwargs): - Routes to specific setup scripts based on configured strategy. """ + depth = self.config["site"]["depth"] system = self.config["export_system"] + self.free_cable_length = system.get("free_cable_length", depth / 1000) + self.cable = Cable(system["cable"]["linear_density"]) self.sections = system["cable"]["sections"] self.number = system["cable"].get("number", 1) @@ -106,6 +109,7 @@ def setup_simulation(self, **kwargs): distances=self.distances, burial_vessel=self.bury_vessel, trench_vessel=self.trench_vessel, + free_cable_length=self.free_cable_length, **kwargs, ) @@ -172,7 +176,9 @@ def calculate_onshore_transmission_cost(self, **kwargs): ) switchyard_cost = 18115 * voltage + 165944 - onshore_substation_cost = (0.165 * 1e6) * capacity # From BNEF Tomorrow's Cost of Offshore Wind + onshore_substation_cost = ( + 0.165 * 1e6 + ) * capacity # From BNEF Tomorrow's Cost of Offshore Wind onshore_misc_cost = 11795 * capacity ** 0.3549 + 350000 transmission_line_cost = (1176 * voltage + 218257) * ( distance ** (1 - 0.1063) @@ -253,6 +259,7 @@ def install_export_cables( distances, burial_vessel=None, trench_vessel=None, + free_cable_length=None, **kwargs, ): """ @@ -286,25 +293,33 @@ def install_export_cables( the cable lay vessel and digs a trench. """ + ground_distance = -free_cable_length + for s in sections: + try: + length, speed = s + + except TypeError: + length = s + + ground_distance += length + # Conduct trenching operations if trench_vessel is None: pass + else: for _ in range(number): - # Total export cable length along which to dig trench - total_sections_distance = sum(sections) - # Trenching vessel can dig a trench during inbound or outbound journey if trench_vessel.at_port: trench_vessel.at_port = False yield dig_export_cables_trench( - trench_vessel, total_sections_distance, **kwargs + trench_vessel, ground_distance, **kwargs ) trench_vessel.at_site = True elif trench_vessel.at_site: trench_vessel.at_site = False yield dig_export_cables_trench( - trench_vessel, total_sections_distance, **kwargs + trench_vessel, ground_distance, **kwargs ) trench_vessel.at_port = True @@ -312,7 +327,7 @@ def install_export_cables( # TODO: replace with demobilization method if trench_vessel.at_site: trench_vessel.at_site = False - yield trench_vessel.transit(total_sections_distance, **kwargs) + yield trench_vessel.transit(ground_distance, **kwargs) trench_vessel.at_port = True for _ in range(number): @@ -378,7 +393,7 @@ def install_export_cables( else: vessel.submit_debug_log(message="Export cable lay process completed!") - bury_export_cables(burial_vessel, length, number, **kwargs) + bury_export_cables(burial_vessel, ground_distance, number, **kwargs) @process diff --git a/tests/phases/install/cable_install/test_export_install.py b/tests/phases/install/cable_install/test_export_install.py index 31b6a004..6da9258f 100644 --- a/tests/phases/install/cable_install/test_export_install.py +++ b/tests/phases/install/cable_install/test_export_install.py @@ -122,7 +122,7 @@ def test_kwargs_for_export_install(): new_export_system = { "cable": {"linear_density": 50.0, "sections": [1000], "number": 1} } - new_site = {"distance": 50} + new_site = {"distance": 50, "depth": 20} new_config = deepcopy(base_config) new_config["export_system"] = new_export_system From 2774d5991b3f8e13b0da97e8dae777503b958ddb Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 17 Jun 2020 15:25:28 -0600 Subject: [PATCH 35/43] Review related modifications. --- ORBIT/phases/design/mooring_system_design.py | 16 ++++++----- .../quayside_assembly_tow/gravity_base.py | 16 +++++------ .../install/quayside_assembly_tow/moored.py | 28 +++++++++---------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py index b9f002f5..f14d0876 100644 --- a/ORBIT/phases/design/mooring_system_design.py +++ b/ORBIT/phases/design/mooring_system_design.py @@ -20,7 +20,7 @@ class MooringSystemDesign(DesignPhase): "plant": {"num_turbines": "int"}, "mooring_system_design": { "num_lines": "int | float (optional, default: 4)", - "anchor_type": "str (optional, default: 'suction')", + "anchor_type": "str (optional, default: 'Suction Pile')", "mooring_line_cost_rate": "int | float (optional)", "drag_embedment_fixed_length": "int | float (optional, default: .5km)", }, @@ -151,12 +151,14 @@ def design_result(self): """Returns the results of the design phase.""" return { - "num_lines": self.num_lines, - "line_diam": self.line_diam, - "line_mass": self.line_mass, - "line_length": self.line_length, - "anchor_mass": self.anchor_mass, - "anchor_type": self.anchor_type, + "mooring_system": { + "num_lines": self.num_lines, + "line_diam": self.line_diam, + "line_mass": self.line_mass, + "line_length": self.line_length, + "anchor_mass": self.anchor_mass, + "anchor_type": self.anchor_type, + } } @property diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index 86fc0b35..71f18284 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -29,7 +29,7 @@ class GravityBasedInstallation(InstallPhase): "towing_vessel": "str", "towing_vessel_groups": { "towing_vessels": "int", - "stabilization_vessels": "int", + "station_keeping_vessels": "int", "num_groups": "int (optional)", }, "substructure": { @@ -214,8 +214,8 @@ def initialize_support_vessel(self, **kwargs): vessel.initialize() self.support_vessel = vessel - stabilization = self.config["towing_vessel_groups"][ - "stabilization_vessels" + station_keeping_vessels = self.config["towing_vessel_groups"][ + "station_keeping_vessels" ] install_gravity_base_foundations( @@ -223,7 +223,7 @@ def initialize_support_vessel(self, **kwargs): self.active_group, self.distance, self.num_turbines, - stabilization, + station_keeping_vessels, **kwargs, ) @@ -327,7 +327,7 @@ def transfer_gbf_substructures_from_storage( @process def install_gravity_base_foundations( - vessel, queue, distance, substructures, stabilization_vessels, **kwargs + vessel, queue, distance, substructures, station_keeping_vessels, **kwargs ): """ Logic that a Multi-Purpose Support Vessel uses at site to complete the @@ -341,8 +341,8 @@ def install_gravity_base_foundations( Distance between port and site (km). substructures : int Number of substructures to install before transiting back to port. - stabilization_vessels : int - Number of vessels to use for substructure stabilization during final + station_keeping_vessels : int + Number of vessels to use for substructure station keeping during final installation at site. """ @@ -387,7 +387,7 @@ def install_gravity_base_foundations( "Positioning Support", group_time, location="site", - num_vessels=stabilization_vessels, + num_vessels=station_keeping_vessels, ) yield queue.vessel.release.succeed() diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 92bfa130..1d04cb87 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -29,7 +29,7 @@ class MooredSubInstallation(InstallPhase): "towing_vessel": "str", "towing_vessel_groups": { "towing_vessels": "int", - "stabilization_vessels": "int", + "station_keeping_vessels": "int", "num_groups": "int (optional)", }, "substructure": { @@ -213,8 +213,8 @@ def initialize_support_vessel(self, **kwargs): vessel.initialize(mobilize=False) self.support_vessel = vessel - stabilization = self.config["towing_vessel_groups"][ - "stabilization_vessels" + station_keeping_vessels = self.config["towing_vessel_groups"][ + "station_keeping_vessels" ] install_moored_substructures( @@ -222,7 +222,7 @@ def initialize_support_vessel(self, **kwargs): self.active_group, self.distance, self.num_turbines, - stabilization, + station_keeping_vessels, **kwargs, ) @@ -298,14 +298,14 @@ def transfer_moored_substructures_from_storage( "Ballast to Towing Draft", 6, num_vessels=towing_vessels, - constraints={"windspeed": le(15), "waveheight": le(2)}, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) yield group.group_task( "Tow Substructure", towing_time, num_vessels=towing_vessels, - constraints={"windspeed": le(15), "waveheight": le(2)}, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) # At Site @@ -336,7 +336,7 @@ def transfer_moored_substructures_from_storage( @process def install_moored_substructures( - vessel, queue, distance, substructures, stabilization_vessels, **kwargs + vessel, queue, distance, substructures, station_keeping_vessels, **kwargs ): """ Logic that a Multi-Purpose Support Vessel uses at site to complete the @@ -350,8 +350,8 @@ def install_moored_substructures( Distance between port and site (km). substructures : int Number of substructures to install before transiting back to port. - stabilization_vessels : int - Number of vessels to use for substructure stabilization during final + station_keeping_vessels : int + Number of vessels to use for substructure station keeping during final installation at site. """ @@ -367,24 +367,24 @@ def install_moored_substructures( yield vessel.task( "Position Substructure", 2, - constraints={"windspeed": le(15), "waveheight": le(2)}, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) yield vessel.task( "Ballast to Operational Draft", 6, - constraints={"windspeed": le(15), "waveheight": le(2)}, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) yield vessel.task( "Connect Mooring Lines", 22, suspendable=True, - constraints={"windspeed": le(15), "waveheight": le(2)}, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) yield vessel.task( "Check Mooring Lines", 12, suspendable=True, - constraints={"windspeed": le(15), "waveheight": le(2)}, + constraints={"windspeed": le(15), "waveheight": le(2.5)}, ) group_time = vessel.env.now - start @@ -392,7 +392,7 @@ def install_moored_substructures( "Positioning Support", group_time, location="site", - num_vessels=stabilization_vessels, + num_vessels=station_keeping_vessels, ) yield queue.vessel.release.succeed() From 2133c9c0955b01b9e793f105239d8e8ce1c3e460 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 17 Jun 2020 15:49:05 -0600 Subject: [PATCH 36/43] Added option to disable onshore construction in ExportCableInstallation. --- ORBIT/phases/install/cable_install/export.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ORBIT/phases/install/cable_install/export.py b/ORBIT/phases/install/cable_install/export.py index 612c1e71..62dbcf26 100755 --- a/ORBIT/phases/install/cable_install/export.py +++ b/ORBIT/phases/install/cable_install/export.py @@ -98,7 +98,9 @@ def setup_simulation(self, **kwargs): self.initialize_trench_vessel() # Perform onshore construction - self.onshore_construction(**kwargs) + onshore = kwargs.get("include_onshore_construction", True) + if onshore: + self.onshore_construction(**kwargs) # Perform cable installation install_export_cables( From acf3c9587282ceb669e48b0fe6809441c61f5593 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 17 Jun 2020 15:53:53 -0600 Subject: [PATCH 37/43] Added GravityBasedInstallation to ProjectManager. --- ORBIT/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ORBIT/manager.py b/ORBIT/manager.py index 25dc38e1..1bf3fe0f 100644 --- a/ORBIT/manager.py +++ b/ORBIT/manager.py @@ -36,6 +36,7 @@ MooredSubInstallation, ArrayCableInstallation, ExportCableInstallation, + GravityBasedInstallation, MooringSystemInstallation, ScourProtectionInstallation, OffshoreSubstationInstallation, @@ -75,6 +76,7 @@ class ProjectManager: ScourProtectionInstallation, MooredSubInstallation, MooringSystemInstallation, + GravityBasedInstallation, ] def __init__(self, config, library_path=None, weather=None): From d55a3b4911dd3b307afcfdc0c276a0b7820b7582 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 23 Jun 2020 11:07:14 -0600 Subject: [PATCH 38/43] Updated MooringSystemDesign docs. --- .../phases/design/doc_MooringSystemDesign.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/source/phases/design/doc_MooringSystemDesign.rst b/docs/source/phases/design/doc_MooringSystemDesign.rst index 5b1a5b8a..cc6a5df2 100644 --- a/docs/source/phases/design/doc_MooringSystemDesign.rst +++ b/docs/source/phases/design/doc_MooringSystemDesign.rst @@ -4,7 +4,14 @@ Mooring System Design Methodology For details of the code implementation, please see :doc:`Mooring System Design API `. -Mooring System Design ---------------------- +Overview +-------- -Coming soon! +The mooring system design module in ORBIT is based on previous modeling +efforts undertaken by NREL, [#maness2017]_. + +References +---------- + +.. [#maness2017] Michael Maness, Benjamin Maples, Aaron Smith, + NREL Offshore Balance-of-System Model, 2017 From 203edd5f28304121711aaef69324bde8bacca026 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 23 Jun 2020 11:07:45 -0600 Subject: [PATCH 39/43] Added MooringSystemInstallation docs. --- .../mooring/doc_MooringSystemInstallation.rst | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst b/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst index b3fe56a1..bd04b81d 100644 --- a/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst +++ b/docs/source/phases/install/mooring/doc_MooringSystemInstallation.rst @@ -7,4 +7,53 @@ For details of the code implementation, please see Overview -------- -Coming soon! +The ``MooringSystemInstallation`` module simulates the installation of mooring +lines and anchors at site for a floating offshore wind project. The mooring +system installation is simulated using a multi-purpose support vessel that +transports the components to site and performs the onsite installation +procedures. + +Configuration +------------- + +The primary configuration parameters available for this module are the +installation vessel and the mooring system configuration. An example of these +parameters is presented below. + +.. code-block:: python + + config = { + + + "mooring_install_vessel": "example_support_vessel", + "mooring_system": { + "num_lines": 4, # per substructure + "line_mass": 500, # t + "anchor_mass": 500, # t + "anchor_type": "Drag Embedment", # or "Suction Pile" + } + ... + } + +Processes +--------- + +The default times associated with the installation procedure are listed in the +table below. + ++---------------------------+---------------------------------+--------------+ +| Process | Inputs | Default | ++===========================+=================================+==============+ +| Loadout | ``mooring_system_load_time`` | 5h | ++---------------------------+---------------------------------+--------------+ +| Transit | ``vessel.transit_speed`` | calculated | ++---------------------------+---------------------------------+--------------+ +| Survey | ``mooring_site_survey_time`` | 3h | ++---------------------------+---------------------------------+--------------+ +| Install Anchor (repeated) | | ``suction_pile_install_time`` | calculated | +| | | ``drag_embed_install_time`` | | ++---------------------------+---------------------------------+--------------+ +| Install Line (repeated) | NA | calculated | ++---------------------------+---------------------------------+--------------+ +| Transit | ``vessel.transit_speed`` | calculated | ++---------------------------+---------------------------------+--------------+ From 0a555d0f4940bb92d6f340dde2d429e22ff16354 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Tue, 23 Jun 2020 12:38:48 -0600 Subject: [PATCH 40/43] Added initial documentation for MooredSubInstallation. --- .../doc_GravityBasedInstallation.rst | 16 +--- .../doc_MooredSubInstallation.rst | 77 ++++++++++++++++++- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst b/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst index 068c7437..070e1bdb 100644 --- a/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst +++ b/docs/source/phases/install/quayside_towout/doc_GravityBasedInstallation.rst @@ -7,18 +7,4 @@ For details of the code implementation, please see Overview -------- - -Configuration -------------- - - -Processes ---------- - - -Configuration Examples ----------------------- - - -Process Diagrams ----------------- +This module will be expanded in a future release. diff --git a/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst b/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst index f9898dc6..ad137a51 100644 --- a/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst +++ b/docs/source/phases/install/quayside_towout/doc_MooredSubInstallation.rst @@ -7,18 +7,87 @@ For details of the code implementation, please see Overview -------- +The ``MooredSubInstallation`` module simulates the manufacture and installation +of moored substuctures for a floating offshore wind project. The installation +procedures include the time required to manufacture a substructure at quayside, +assemble a turbine on the substructure, ballast the completed assembly, tow +the completed assembly to site and hook up the pre-installed moooring lines. Configuration ------------- +The primary configuration parameters available for this module are related to +the quayside assembly process and the vessels used to tow the completed +assemblies to site and complete the installation. The code block highlights +the key parameters available. + +.. code-block:: python + + config = { + + ... + + "support_vessel": "example_support_vessel", # Will perform onsite installation procedures. + "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. + "station_keeping_vessels": 3, # Vessels used for station keeping during mooring line hookups. + "num_groups": 1 # Number of independent groups. Optional, defualt: 1. + }, + + "port": { + "sub_assembly_lines": 2, # Independent substructure assembly lines. + "sub_storage": 8, # Available storage berths at port for completed substructures. + "turbine_assembly_cranes": 2, # Independent turbine assembly cranes. + "assembly_storage": 8, # Available storage berths at port for completed turbine/substructure assemblies. + }, + + "substructure": { + "takt_time": 168, # h, time to manufacture one substructure. + "towing_speed": 6, # km/h. + }, + + ... + } + Processes --------- +Quayside Assembly +~~~~~~~~~~~~~~~~~ + ++-------------------------------------------+---------+ +| Process | Default | ++===========================================+=========+ +| Substructure Assembly | 168h | ++-------------------------------------------+---------+ +| Prepare Substructure for Turbine Assembly | 12h | ++-------------------------------------------+---------+ +| Lift and Fasten Tower Section | 12h | +| (repeated if necessary) | | ++-------------------------------------------+---------+ +| Lift and Fasten Nacelle | 7h | ++-------------------------------------------+---------+ +| Lift and Fasten Blade (repeated) | 3.5h | ++-------------------------------------------+---------+ +| Mechanical Completion and Verification | 24h | ++-------------------------------------------+---------+ -Configuration Examples ----------------------- +Substructure Tow-out and Assembly +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Process Diagrams ----------------- ++-------------------------------------+------------+ +| Process | Default | ++=====================================+============+ +| Ballast to Towing Draft | 6h | ++-------------------------------------+------------+ +| Tow-out | calculated | ++-------------------------------------+------------+ +| Ballast to Operational Draft | 6h | ++-------------------------------------+------------+ +| Connect Mooring Lines | 22h | ++-------------------------------------+------------+ +| Check Mooring Lines and Connections | 12h | ++-------------------------------------+------------+ From 034008da1858cf31a4a2dff96cd209ae2071d473 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 1 Jul 2020 08:37:36 -0600 Subject: [PATCH 41/43] Branch cleanup. --- library/turbines/12MW_generic.yaml | 2 +- mooring_research.ipynb | 43 ------------------------------ 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 mooring_research.ipynb diff --git a/library/turbines/12MW_generic.yaml b/library/turbines/12MW_generic.yaml index ee69bb4b..0a190efb 100755 --- a/library/turbines/12MW_generic.yaml +++ b/library/turbines/12MW_generic.yaml @@ -1,5 +1,5 @@ blade: - deck_space: 385 # m^2 ##### Reset all deck_space = 1 to avoid vessel constraints + deck_space: 385 # m^2 length: 107 # m type: Blade mass: 54 # t diff --git a/mooring_research.ipynb b/mooring_research.ipynb deleted file mode 100644 index 16218363..00000000 --- a/mooring_research.ipynb +++ /dev/null @@ -1,43 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Jake Nunemaker\n", - "\n", - "National Renewable Energy Lab\n", - "\n", - "2/13/2020" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From e2c7ce0895547a42eaccb5ed702660c997c20f70 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 1 Jul 2020 10:21:38 -0600 Subject: [PATCH 42/43] Added basic floating substructure installation tests and related bugfixes. --- .../quayside_assembly_tow/gravity_base.py | 7 +-- .../install/quayside_assembly_tow/moored.py | 3 +- .../project/config/moored_install.yaml | 20 +++++++ .../config/moored_install_no_supply.yaml | 18 +++++++ tests/data/library/turbines/12MW_generic.yaml | 20 +++++++ .../library/vessels/test_towing_vessel.yaml | 10 ++++ .../test_gravity_based.py | 52 +++++++++++++++++++ .../quayside_assembly_tow/test_moored.py | 52 +++++++++++++++++++ 8 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 tests/data/library/project/config/moored_install_no_supply.yaml create mode 100755 tests/data/library/turbines/12MW_generic.yaml create mode 100644 tests/data/library/vessels/test_towing_vessel.yaml diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py index 71f18284..c2398a81 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py +++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py @@ -178,7 +178,7 @@ def initialize_towing_groups(self, **kwargs): for i in range(num_groups): g = TowingGroup(vessel, num=i + 1) self.env.register(g) - g.initialize(mobilize=False) + g.initialize() self.installation_groups.append(g) transfer_gbf_substructures_from_storage( @@ -211,7 +211,7 @@ def initialize_support_vessel(self, **kwargs): vessel = Vessel("Multi-Purpose Support Vessel", specs) self.env.register(vessel) - vessel.initialize() + vessel.initialize(mobilize=False) self.support_vessel = vessel station_keeping_vessels = self.config["towing_vessel_groups"][ @@ -350,11 +350,11 @@ def install_gravity_base_foundations( while n < substructures: if queue.vessel: + start = vessel.env.now if n == 0: vessel.mobilize() yield vessel.transit(distance) - start = vessel.env.now yield vessel.task( "Position Substructure", 5, @@ -390,6 +390,7 @@ def install_gravity_base_foundations( num_vessels=station_keeping_vessels, ) yield queue.vessel.release.succeed() + n += 1 else: start = vessel.env.now diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py index 1d04cb87..94260787 100644 --- a/ORBIT/phases/install/quayside_assembly_tow/moored.py +++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py @@ -359,11 +359,11 @@ def install_moored_substructures( while n < substructures: if queue.vessel: + start = vessel.env.now if n == 0: vessel.mobilize() yield vessel.transit(distance) - start = vessel.env.now yield vessel.task( "Position Substructure", 2, @@ -395,6 +395,7 @@ def install_moored_substructures( num_vessels=station_keeping_vessels, ) yield queue.vessel.release.succeed() + n += 1 else: start = vessel.env.now diff --git a/tests/data/library/project/config/moored_install.yaml b/tests/data/library/project/config/moored_install.yaml index e69de29b..90b0b5ff 100644 --- a/tests/data/library/project/config/moored_install.yaml +++ b/tests/data/library/project/config/moored_install.yaml @@ -0,0 +1,20 @@ +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 +support_vessel: test_support_vessel +towing_vessel: test_towing_vessel +towing_vessel_groups: + num_groups: 1 + station_keeping_vessels: 3 + 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 new file mode 100644 index 00000000..7a0782a2 --- /dev/null +++ b/tests/data/library/project/config/moored_install_no_supply.yaml @@ -0,0 +1,18 @@ +plant: + num_turbines: 50 +port: + assembly_storage: 1 + turbine_assembly_cranes: 1 +site: + depth: 500 + distance: 50 +substructure: + takt_time: 0 + towing_speed: 6 +support_vessel: test_support_vessel +towing_vessel: test_towing_vessel +towing_vessel_groups: + num_groups: 1 + station_keeping_vessels: 3 + towing_vessels: 1 +turbine: 12MW_generic diff --git a/tests/data/library/turbines/12MW_generic.yaml b/tests/data/library/turbines/12MW_generic.yaml new file mode 100755 index 00000000..0a190efb --- /dev/null +++ b/tests/data/library/turbines/12MW_generic.yaml @@ -0,0 +1,20 @@ +blade: + deck_space: 385 # m^2 + length: 107 # m + type: Blade + mass: 54 # t +hub_height: 132 # m +nacelle: + deck_space: 203 # m^2 + type: Nacelle + mass: 604 # t +name: 12MW Generic Turbine +rated_windspeed: 11 # m/s +rotor_diameter: 215 # m +tower: + deck_space: 50.24 # m^2 + sections: 2 # n + type: Tower + length: 132 + mass: 399 # t +turbine_rating: 12 # MW diff --git a/tests/data/library/vessels/test_towing_vessel.yaml b/tests/data/library/vessels/test_towing_vessel.yaml new file mode 100644 index 00000000..b1de857d --- /dev/null +++ b/tests/data/library/vessels/test_towing_vessel.yaml @@ -0,0 +1,10 @@ +transport_specs: + max_waveheight: 2.5 # m + max_windspeed: 20 # m/s + transit_speed: 6 # km/h +vessel_specs: + beam_length: 35 # m + day_rate: 30000 # USD/day + max_draft: 5 # m + min_draft: 4 # m + overall_length: 60 # m 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 d732f3f3..0d0eb67c 100644 --- a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py +++ b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py @@ -4,3 +4,55 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" + + +import pandas as pd +import pytest + +from tests.data import test_weather +from ORBIT.library import extract_library_specs +from ORBIT.phases.install import GravityBasedInstallation + +config = extract_library_specs("config", "moored_install") +no_supply = extract_library_specs("config", "moored_install_no_supply") + + +def test_simulation_setup(): + + sim = GravityBasedInstallation(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) + == config["port"]["turbine_assembly_cranes"] + ) + assert ( + len(sim.installation_groups) + == config["towing_vessel_groups"]["num_groups"] + ) + assert sim.num_turbines == config["plant"]["num_turbines"] + + +@pytest.mark.parametrize( + "weather", (None, test_weather), ids=["no_weather", "test_weather"] +) +@pytest.mark.parametrize("config", (config, no_supply)) +def test_for_complete_logging(weather, config): + + sim = GravityBasedInstallation(config, weather=weather) + sim.run() + + df = pd.DataFrame(sim.env.actions) + df = df.assign(shift=(df["time"] - df["time"].shift(1))) + + for vessel in df["agent"].unique(): + _df = df[df["agent"] == vessel].copy() + _df = _df.assign(shift=(_df["time"] - _df["time"].shift(1))) + assert (_df["shift"] - _df["duration"]).abs().max() < 1e-9 + + assert ~df["cost"].isnull().any() + _ = sim.agent_efficiencies + _ = sim.detailed_output diff --git a/tests/phases/install/quayside_assembly_tow/test_moored.py b/tests/phases/install/quayside_assembly_tow/test_moored.py index e6aec501..e87ddb3b 100644 --- a/tests/phases/install/quayside_assembly_tow/test_moored.py +++ b/tests/phases/install/quayside_assembly_tow/test_moored.py @@ -4,3 +4,55 @@ __copyright__ = "Copyright 2020, National Renewable Energy Laboratory" __maintainer__ = "Jake Nunemaker" __email__ = "jake.nunemaker@nrel.gov" + + +import pandas as pd +import pytest + +from tests.data import test_weather +from ORBIT.library import extract_library_specs +from ORBIT.phases.install import MooredSubInstallation + +config = extract_library_specs("config", "moored_install") +no_supply = extract_library_specs("config", "moored_install_no_supply") + + +def test_simulation_setup(): + + 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) + == config["port"]["turbine_assembly_cranes"] + ) + assert ( + len(sim.installation_groups) + == config["towing_vessel_groups"]["num_groups"] + ) + assert sim.num_turbines == config["plant"]["num_turbines"] + + +@pytest.mark.parametrize( + "weather", (None, test_weather), ids=["no_weather", "test_weather"] +) +@pytest.mark.parametrize("config", (config, no_supply)) +def test_for_complete_logging(weather, config): + + sim = MooredSubInstallation(config, weather=weather) + sim.run() + + df = pd.DataFrame(sim.env.actions) + df = df.assign(shift=(df["time"] - df["time"].shift(1))) + + for vessel in df["agent"].unique(): + _df = df[df["agent"] == vessel].copy() + _df = _df.assign(shift=(_df["time"] - _df["time"].shift(1))) + assert (_df["shift"] - _df["duration"]).abs().max() < 1e-9 + + assert ~df["cost"].isnull().any() + _ = sim.agent_efficiencies + _ = sim.detailed_output From 966c2134542f20f11c253ea831befdf8e5835824 Mon Sep 17 00:00:00 2001 From: Jake Nunemaker Date: Wed, 1 Jul 2020 10:25:58 -0600 Subject: [PATCH 43/43] Fix MooringSystemDesign test. --- tests/phases/design/test_mooring_system_design.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phases/design/test_mooring_system_design.py b/tests/phases/design/test_mooring_system_design.py index 159e6e8b..39e2cb9c 100644 --- a/tests/phases/design/test_mooring_system_design.py +++ b/tests/phases/design/test_mooring_system_design.py @@ -82,4 +82,4 @@ def test_custom_num_lines(): m = MooringSystemDesign(config) m.run() - assert m.design_result["num_lines"] == 5 + assert m.design_result["mooring_system"]["num_lines"] == 5