ORBIT Introduction#
ORBIT's CapEx modeling is comprised of the both design and installation models for a variety of
offshore wind turbine subsystems. As such, the model's core functionality are split into the
design and install model classes. The design classes are intended to model the sizing and cost
of offshore wind components and the installation modules simulate the installation of these
subcomponents in a discrete event simulation framework. This tutorial will walk through the basics
of modeling the design and then installation of the monopile, leading to the introduction of the
ProjectManger to orchestrate the design and installation of multiple turbine subsystems.
To get started, we first import the required imports that will be used in this demonstration.
from pathlib import Path
from copy import deepcopy
from pprint import pprint
from ORBIT import ProjectManager, load_config, save_config
from ORBIT.phases.design import MonopileDesign, design_phases
from ORBIT.phases.install import MonopileInstallation, install_phases
While this introduction will focus on the monopile design and installation to highlight working with ORBIT, it should be noted that there are both fixed and floating substructure models. Below is an easy way to check what models are available in ORBIT.
pprint(design_phases)
['MonopileDesign',
'ScourProtectionDesign',
'SparDesign',
'SemiSubmersibleDesign',
'MooringSystemDesign',
'ArraySystemDesign',
'CustomArraySystemDesign',
'ElectricalDesign',
'ExportSystemDesign',
'OffshoreSubstationDesign',
'OffshoreFloatingSubstationDesign']
pprint(install_phases)
['MonopileInstallation',
'JacketInstallation',
'ScourProtectionInstallation',
'GravityBasedInstallation',
'MooredSubInstallation',
'MooringSystemInstallation',
'TurbineInstallation',
'ArrayCableInstallation',
'ExportCableInstallation',
'OffshoreSubstationInstallation',
'FloatingSubstationInstallation']
Configuration Basics#
Each model has a property expected_config that provides basic information about the required and
optional inputs for the model. Notice that for each input there is a provided data type, an
indication if the parameter is optional, and any nested dictionary configurations are fully mapped
in the same way as individual parameters. Below, we can see the expected configurations for both
the monopile design and installation classes. It should be noted that when combining complimentary
design and installation phases for a component, that the design model will provide most of the
installation inputs as a design_result (more details in the ProjectManager introduction).
pprint(MonopileDesign.expected_config)
{'monopile_design': {'air_density': 'kg/m3 (optional)',
'load_factor': 'float (optional)',
'material_factor': 'float (optional)',
'monopile_density': 'kg/m3 (optional)',
'monopile_modulus': 'Pa (optional)',
'monopile_steel_cost': 'USD/t (optional)',
'monopile_tp_connection_thickness': 'm (optional)',
'soil_coefficient': 'N/m3 (optional)',
'tp_steel_cost': 'USD/t (optional)',
'transition_piece_density': 'kg/m3 (optional)',
'transition_piece_length': 'm (optional)',
'transition_piece_thickness': 'm (optional)',
'turb_length_scale': 'm (optional)',
'weibull_scale_factor': 'float (optional)',
'weibull_shape_factor': 'float (optional)',
'yield_stress': 'Pa (optional)'},
'plant': {'num_turbines': 'int'},
'site': {'depth': 'm', 'mean_windspeed': 'm/s'},
'turbine': {'hub_height': 'm',
'rated_windspeed': 'm/s',
'rotor_diameter': 'm'}}
pprint(MonopileInstallation.expected_config)
{'feeder': 'dict | str (optional)',
'monopile': {'deck_space': 'm2',
'diameter': 'm',
'length': 'm',
'mass': 't',
'unit_cost': 'USD'},
'monopile_supply_chain': {'enabled': '(optional, default: False)',
'num_substructures_delivered': 'int (optional: '
'default: 1)',
'substructure_delivery_time': 'h (optional, '
'default: 168)'},
'num_feeders': 'int (optional)',
'plant': {'num_turbines': 'int'},
'port': {'monthly_rate': 'USD/mo (optional)',
'name': 'str (optional)',
'num_cranes': 'int (optional, default: 1)'},
'site': {'depth': 'm', 'distance': 'km'},
'transition_piece': {'deck_space': 'm2', 'mass': 't', 'unit_cost': 'USD'},
'turbine': {'hub_height': 'm'},
'wtiv': 'dict | str'}
Design Models#
Design phase modules in ORBIT are intended to capture broad scaling trends for offshore wind components and do not represent the required fidelity of a full engineering design. Please see NLR's WISDEM if a higher fidelity turbine design model is required.
For the sake of illustration we will provide only the required inputs, as shown below.
# Filling out the config for a simple fixed bottom project:
design_config = {
"site": {
"depth": 25,
"mean_windspeed": 9.5,
},
"plant": {
"num_turbines": 50,
},
"turbine": {
"rotor_diameter": 220,
"hub_height": 120,
"rated_windspeed": 13,
}
}
Similar to expected_config, every design and installation model contains a run method that runs
the design or installation simulation logic.
monopile_design = MonopileDesign(design_config)
monopile_design.run()
print(f"Total Substructure Cost: ${monopile_design.total_cost / 1e6:,.1f} M")
pprint(monopile_design.design_result)
ORBIT library intialized at '/opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/library'
Total Substructure Cost: $386.6 M
{'monopile': {'deck_space': np.float64(54.48035485737655),
'diameter': np.float64(7.381080873244551),
'embedment_length': np.float64(28.73789461200757),
'length': np.float64(63.737894612007565),
'mass': np.float64(1015.3457502626202),
'moment': np.float64(12.250526562877912),
'thickness': np.float64(0.08016080873244551),
'unit_cost': np.float64(3691797.147954887)},
'transition_piece': {'deck_space': np.float64(56.87275152687858),
'diameter': np.float64(7.541402490709443),
'length': 25,
'mass': np.float64(406.9956472415284),
'thickness': np.float64(0.08016080873244551),
'unit_cost': np.float64(4039838.794519411)}}
Incomplete or Incorrect Configurations#
If a required input is missing, an error message will be raised with the input and it's location
within the configuration. This error message used dot-notation to show the structure of the
dictionary. Each "." represents a lower level in the dictionary such that site.depth means the "site" subdictionary is missing the "depth" key, value pair.
In the example below, the site inputs have been removed. The following inputs will be missing:
["site.depth", "site.mean_windspeed"]
config_error = deepcopy(design_config)
_ = config_error.pop("site")
failed_monopile_design = MonopileDesign(config_error)
---------------------------------------------------------------------------
MissingInputs Traceback (most recent call last)
Cell In[8], line 4
1 config_error = deepcopy(design_config)
2 _ = config_error.pop("site")
3
----> 4 failed_monopile_design = MonopileDesign(config_error)
File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/ORBIT/phases/design/monopile_design.py:76, in MonopileDesign.__init__(self, config, **kwargs)
67 """
68 Creates an instance of MonopileDesign.
69
(...) 72 config : dict
73 """
75 config = self.initialize_library(config, **kwargs)
---> 76 self.config = self.validate_config(config)
77 self._design = self.config.get("monopile_design", {})
79 self._outputs = {}
File /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/ORBIT/phases/base.py:115, in BasePhase.validate_config(self, config)
112 missing = self._check_keys(expected, config)
114 if missing:
--> 115 raise MissingInputs(missing)
117 else:
118 return benedict(config)
MissingInputs: Input(s) '['site.depth', 'site.mean_windspeed']' missing in config.
Optional Inputs#
Optional inputs can be provided as they are available or desired in place of ORBIT's defaults. In general ORBIT's default values are updated on annual basis to align with the last complete year of inflationary data and commodity price indices. These values also align with the annual NLR Cost of Wind Energy Review.
design_config = {
"site": {
"depth": 25,
"mean_windspeed": 9.5,
},
"plant": {
"num_turbines": 50,
},
"turbine": {
"rotor_diameter": 220,
"hub_height": 120,
"rated_windspeed": 13,
},
# Overriding of the design cost defaults, both in $USD/tonne
"monopile_design": {
"monopile_steel_cost": 3500,
"tp_steel_cost": 4500,
}
}
monopile_design = MonopileDesign(design_config)
monopile_design.run()
print(f"Total Substructure Cost: ${monopile_design.total_cost / 1e6:,.2f} M")
pprint(monopile_design.design_result)
Total Substructure Cost: $269.26 M
{'monopile': {'deck_space': np.float64(54.48035485737655),
'diameter': np.float64(7.381080873244551),
'embedment_length': np.float64(28.73789461200757),
'length': np.float64(63.737894612007565),
'mass': np.float64(1015.3457502626202),
'moment': np.float64(12.250526562877912),
'thickness': np.float64(0.08016080873244551),
'unit_cost': np.float64(3553710.1259191707)},
'transition_piece': {'deck_space': np.float64(56.87275152687858),
'diameter': np.float64(7.541402490709443),
'length': 25,
'mass': np.float64(406.9956472415284),
'thickness': np.float64(0.08016080873244551),
'unit_cost': np.float64(1831480.4125868778)}}
Installation Phases#
ORBIT's installation phases tend to require more inputs and provide implicit pathways to model installation strategies. For instance, in the monopile installation, we can provide a "wtiv" vessel for a single WTIV installation strategy or provide a "feeder" configuration with "num_feeders" to model barges ferrying components to and from the site while a WTIV installs the turbines. Additionally, supply chains and ports can be configured to model component availability and port logistics.
Using the output from the above example, we can add further configurations. Note that ORBIT provides
a series of default vessels in library/vessels/ to support all possible installation strategies.
For more details on vessel configurations, please see the vessels section.
install_config = deepcopy(monopile_design.design_result)
install_config["wtiv"] = "example_wtiv"
install_config["feeder"] = "example_feeder"
install_config["num_feeders"] = 2
install_config["site"] = design_config["site"] | {"distance": 70}
install_config["plant"] = design_config["plant"]
install_config["turbine"] = design_config["turbine"]
monopile_install = MonopileInstallation(install_config)
monopile_install.run()
print(f"Total Installation Cost: ${monopile_install.installation_capex / 1e6:,.2f} M")
print(monopile_install.config.dump())
Total Installation Cost: $33.97 M
{
"feeder": {
"crane_specs": {
"max_lift": 500
},
"jacksys_specs": {
"leg_length": 85,
"max_depth": 40,
"max_extension": 60,
"speed_above_depth": 0.5,
"speed_below_depth": 0.5
},
"storage_specs": {
"max_cargo": 12000,
"max_deck_load": 8,
"max_deck_space": 1000
},
"transport_specs": {
"max_waveheight": 2.5,
"max_windspeed": 20,
"transit_speed": 6
},
"vessel_specs": {
"day_rate": 93692
}
},
"monopile": {
"deck_space": 54.48035485737655,
"diameter": 7.381080873244551,
"embedment_length": 28.73789461200757,
"length": 63.737894612007565,
"mass": 1015.3457502626202,
"moment": 12.250526562877912,
"thickness": 0.08016080873244551,
"unit_cost": 3553710.1259191707
},
"num_feeders": 2,
"plant": {
"num_turbines": 50
},
"site": {
"depth": 25,
"distance": 70,
"mean_windspeed": 9.5
},
"transition_piece": {
"deck_space": 56.87275152687858,
"diameter": 7.541402490709443,
"length": 25,
"mass": 406.9956472415284,
"thickness": 0.08016080873244551,
"unit_cost": 1831480.4125868778
},
"turbine": {
"hub_height": 120,
"rated_windspeed": 13,
"rotor_diameter": 220
},
"wtiv": {
"crane_specs": {
"max_hook_height": 100,
"max_lift": 1200,
"max_windspeed": 15
},
"jacksys_specs": {
"leg_length": 110,
"max_depth": 75,
"max_extension": 85,
"speed_above_depth": 1,
"speed_below_depth": 2.5
},
"storage_specs": {
"max_cargo": 8000,
"max_deck_load": 15,
"max_deck_space": 4000
},
"transport_specs": {
"max_waveheight": 3,
"max_windspeed": 20,
"transit_speed": 10
},
"vessel_specs": {
"day_rate": 400000,
"mobilization_days": 7,
"mobilization_mult": 1
}
}
}
Loading and Saving Configurations#
In addition to writing dictionaries in a script or Notebook file, ORBIT also provides the
load_config and save_config functions to load and save configurations for easier scenario
management. In the following example, we demonstrate a hypothetical workflow loading, updating, and
saving a new monopile design configuration.
design_config = load_config("path/to/monopile_design.yaml")
... # calculate additional properties of the monopile and update the configuration
save_config(design_config, "path/to/new_monopile_design.yaml")
Other use cases could be for creating input templates for project configurations, such
as those used by ProjectManager in the next section.
Using A Data Library#
ORBIT makes use of its own
internal library when a user-provided
library path is not provided (i.e. a value isn't provide so the default None is used in
ProjectManager(config, library_path=None)). When a value is provided, user library files will be
searched for first, and the default library will be checked for any files that were not found.
This is made visible in the installation phases section where the value
"example_wtiv" is provided to the "wtiv" key. When the configuration is loaded, ProjectManager
will attempt to find the example_wtiv.yaml file in the ORBIT default library under the vessels/
folder. Below is the expected folder structure of the library. I
# /path/to/library/
├── defaults <- Top-level default data
├── project
│ ├── config <- Configuration dictionary repository
│ ├── port <- Port specific data settings
│ ├── plant <- Wind farm specific data settings
│ ├── site <- Project site data settings
│ ├── development <- Project development cost settings
├── cables <- Cable data files: array cables, export cables
├── substructures <- Substructure data files: monopiles, jackets, etc.
├── turbines <- Turbine data files
├── vessels <- Vessel data files
│ ├── defaults <- Default data related to vessel tasks
├── weather <- Weather profiles
├── results
Vessels#
All installation models rely on at least one vessel to perform the installation routines. Similar
to turbine and cable configuration files, these should be stored in the YAML format in the
vessels library folder. Below are the
vessel_specs- General vessel parameters including day rate.day_rate: Daily cost to operate the vessel, $USD/day.min_draft: Minimum distance between the waterline and the bottom of the hull, m.overall_length: Length of the vessel, m.mobilization_days: Number days required to mobilize the vessel to site.mobilization_mult: Mobilization multiplier applied today_rate.Any other custom input that will override logistics defaults.
transport_specs- Transit related parameters and constraints.transit_speed: Average transiting speed, km/h.max_waveheight: Maximum operational wave height, m.max_windspeed: Maximum operational wind speed, m/s.
storage_specs- Storage related parameters. Required to transport items on deck.max_cargo: Maximum cargo capacity, metric tonnes.max_deck_load: Maximum capacity to be loaded on deck, metric tonnes per square meter, \(t/m^2\).max_deck_space: Maximum amount of space on deck for loading components, \(m^2\).
cable_storage: Array and export cable carousel storage parameters.max_mass: Maximum mass of the cable carousel, in metric tonnes.
spi_specs: Scouring protection installation vessel storage parameters.max_cargo_mass: Maximum mass allowed to be loaded for a single trip, in metric tonnes.
jacksys_specs: Jacking system related parameters. Currently required for all fixed substructure and turbine installations.leg_length: Length of the jackup vessel's legs, m.air_gap: Distance between sea level and the vessel bottom when fully jacked up, m.leg_pen: How far the leg penetrates the sea floor for stability, m.max_depth: Maximum water depth, m.max_extension: Maximum leg extension, m.speed_below_depth: Jackup speed when leg extension has not reached the sea floor, m/minspeed_above_depth: Jackup speed after the leg has reached the sea floor and the vessel is being raised above sea level, m/min.
dynamic_positioning_specs: Dynamic positioning related parametersclass: integer of the dynamic positioning class.
crane_specs- Crane related parameters and constraints. Required for any offshore lifts.max_lift: Maximum mass that can be lifted, metric tonnes.max_hook_height: Maximum height the hook can be raised, m.max_windspeed: Maximum operational windspeed, m/s.crane_rate: Crane lift rate, m/h.
Syncing Design and Installation with ProjectManager#
ProjectManager is the primary system for interacting with ORBIT. It provides the ability to
configure and run one or multiple design and installation at a time, allowing the user to customize
ORBIT to fit the needs of a specific project. It also provides a helper method to detail what inputs
are required to run the desired configuration.
Continuing to work with just the monopile, we can provide a barebones configuration to set the
desired phases, and output the required inputs when running the design and installation phase in
unison. Notice that the "monopile" definition is no longer required for the installation as the
design_result will be automatically passed from the design phase to the installation phase.
There are now additional project parameters to supply development and other non-modeled fixed costs
the project will incur. Similar to the design and installation models, anything that is marked as
optional will have a default value within the model.
For more details on the ProjectManager, please see the tutorial.
phases = ["MonopileDesign", "MonopileInstallation"]
config_template = ProjectManager.compile_input_dict(phases)
pprint(config_template)
{'design_phases': ['MonopileDesign'],
'feeder': 'dict | str (optional)',
'install_phases': ['MonopileInstallation'],
'monopile_design': {'air_density': 'kg/m3 (optional)',
'load_factor': 'float (optional)',
'material_factor': 'float (optional)',
'monopile_density': 'kg/m3 (optional)',
'monopile_modulus': 'Pa (optional)',
'monopile_steel_cost': 'USD/t (optional)',
'monopile_tp_connection_thickness': 'm (optional)',
'soil_coefficient': 'N/m3 (optional)',
'tp_steel_cost': 'USD/t (optional)',
'transition_piece_density': 'kg/m3 (optional)',
'transition_piece_length': 'm (optional)',
'transition_piece_thickness': 'm (optional)',
'turb_length_scale': 'm (optional)',
'weibull_scale_factor': 'float (optional)',
'weibull_shape_factor': 'float (optional)',
'yield_stress': 'Pa (optional)'},
'monopile_supply_chain': {'enabled': '(optional, default: False)',
'num_substructures_delivered': 'int (optional: '
'default: 1)',
'substructure_delivery_time': 'h (optional, '
'default: 168)'},
'num_feeders': 'int (optional)',
'orbit_version': '1.3',
'plant': {'num_turbines': 'int'},
'port': {'monthly_rate': 'USD/mo (optional)',
'name': 'str (optional)',
'num_cranes': 'int (optional, default: 1)'},
'project_parameters': {'commissioning': '$/kW (optional, default: value '
'calculated using '
'commissioning_factor)',
'commissioning_factor': 'float (optional, default: '
'0.0115)',
'construction_financing': '$/kW (optional, default: '
'value calculated using '
'construction_financing_factor))',
'construction_financing_factor': ('$/kW (optional, '
'default: value '
'calculated using '
'spend_schedule, '
'tax_rate, and '
'interest_during_construction))',),
'construction_insurance': '$/kW (optional, default: '
'value calculated using '
'construction_insurance_factor)',
'construction_insurance_factor': 'float (optional, '
'default: 0.0207)',
'construction_plan_cost': '$ (optional, default: 25e6)',
'decommissioning': '$/kW (optional, default: value '
'calculated using '
'decommissioning_factor)',
'decommissioning_factor': 'float (optional, default: '
'0.2)',
'discount_rate': 'yearly (optional, default: .025)',
'installation_contingency': '$/kW (optional, default: '
'value calculated using '
'installation_contingency_factor)',
'installation_contingency_factor': 'float (optional, '
'default: 0.345)',
'installation_plan_cost': '$ (optional, default: 25e6)',
'interest_during_construction': 'float (optional, '
'default: 0.065',
'ncf': 'float (optional, default: 0.4)',
'offtake_price': '$/MWh (optional, default: 80)',
'opex_rate': '$/kW/year (optional, default: 150)',
'procurement_contingency': '$/kW (optional, default: '
'value calculated using '
'procurement_contingency_factor)',
'procurement_contingency_factor': 'float (optional, '
'default: 0.0575)',
'project_lifetime': 'yrs (optional, default: 25)',
'site_assessment_cost': '$ (optional, default: 200e6)',
'site_auction_price': '$ (optional, default: 105e6)',
'spend_schedule': 'dict (optional, default: {0: 0.25, '
'1: 0.25, 2: 0.3, 3: 0.1, 4: 0.1, 5: '
'0.0}',
'tax_rate': 'float (optional, default: 0.26',
'turbine_capex': '$/kW (optional, default: 1300)'},
'site': {'depth': 'm', 'distance': 'km', 'mean_windspeed': 'm/s'},
'turbine': {'hub_height': 'm',
'rated_windspeed': 'm/s',
'rotor_diameter': 'm'},
'wtiv': 'dict | str'}
Now, we can combine the monopile design and installation configurations that were used in the previous examples, and run the model to get a single CapEx alongside the high level category breakdown.
project_config = deepcopy(design_config)
project_config["wtiv"] = "example_wtiv"
project_config["feeder"] = "example_feeder"
project_config["num_feeders"] = 2
project_config["site"] = install_config["site"]
project_config["turbine"]["turbine_rating"] = 12
project_config["design_phases"] = ["MonopileDesign"]
project_config["install_phases"] = ["MonopileInstallation"]
project = ProjectManager(project_config)
project.run()
print(f"{'Project Capex':>30}: {project.bos_capex / 1e6:6,.2f} M")
for category, cost in project.capex_breakdown.items():
print(f"{category:>30}: {cost / 1e6:6,.2f} M")
Project Capex: 303.23 M
Substructure: 269.26 M
Substructure Installation: 33.97 M
Onshore Substation: 0.00 M
Turbine: 780.00 M
Soft: 274.09 M
Project: 355.00 M
To continue with the previous subsection's demonstration, we can also save the final configuration in one combined file, so the project could be reloaded and rerun in the future.
config_fn = Path("monopile_demo.yaml").resolve()
save_config(project.config, config_fn)
config = load_config(config_fn)
project = ProjectManager(config)
project.run()
print(f"{'Project Capex':>30}: {project.bos_capex / 1e6:6,.2f} M")
for category, cost in project.capex_breakdown.items():
print(f"{category:>30}: {cost / 1e6:6,.2f} M")
config_fn.unlink() # delete the demo file
Project Capex: 303.23 M
Substructure: 269.26 M
Substructure Installation: 33.97 M
Onshore Substation: 0.00 M
Turbine: 780.00 M
Soft: 274.09 M
Project: 355.00 M