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 to day_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/min

    • speed_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 parameters

    • class: 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