"""Provides the 'OffshoreSubstationDesign` class."""
__author__ = "Jake Nunemaker"
__copyright__ = "Copyright 2026, National Laboratory of the Rockies"
__maintainer__ = "Jake Nunemaker"
__email__ = "Jake.Nunemaker@nlr.gov"
import numpy as np
from ORBIT.phases.design import DesignPhase
[docs]
class OffshoreSubstationDesign(DesignPhase):
"""Offshore Substation Design Class."""
expected_config = {
"site": {"depth": "m"},
"plant": {"num_turbines": "int"},
"turbine": {"turbine_rating": "MW"},
"substation_design": {
"mpt_cost_rate": "USD/MW (optional)",
"topside_fab_cost_rate": "USD/t (optional)",
"topside_design_cost": "USD (optional)",
"shunt_cost_rate": "USD/MW (optional)",
"switchgear_cost": "USD (optional)",
"backup_gen_cost": "USD (optional)",
"workspace_cost": "USD (optional)",
"other_ancillary_cost": "USD (optional)",
"topside_assembly_factor": "float (optional)",
"oss_substructure_cost_rate": "USD/t (optional)",
"oss_pile_cost_rate": "USD/t (optional)",
"num_substations": "int (optional)",
},
# "export_system": {
# "cable": {
# "number": "int",
# "cable_type": "str",
# },
# },
}
output_config = {
"num_substations": "int",
"offshore_substation_topside": "dict",
"offshore_substation_substructure": "dict",
}
def __init__(self, config, **kwargs):
"""
Creates an instance of OffshoreSubstationDesign.
Parameters
----------
config : dict
"""
config = self.initialize_library(config, **kwargs)
self.config = self.validate_config(config)
self._design = self.config.get("substation_design", {})
self._outputs = {}
[docs]
def run(self):
"""Main run function."""
self.calc_substructure_length()
self.calc_substructure_deck_space()
self.calc_topside_deck_space()
self.calc_num_mpt_and_rating()
self.calc_mpt_cost()
self.calc_topside_mass_and_cost()
self.calc_shunt_reactor_cost()
self.calc_switchgear_cost()
self.calc_ancillary_system_cost()
self.calc_assembly_cost()
self.calc_substructure_mass_and_cost()
self._outputs["offshore_substation_substructure"] = {
"type": "Monopile", # Substation install only supports monopiles
"deck_space": self.substructure_deck_space,
"mass": self.substructure_mass,
"length": self.substructure_length,
"unit_cost": self.substructure_cost,
}
self._outputs["offshore_substation_topside"] = {
"deck_space": self.topside_deck_space,
"mass": self.topside_mass,
"unit_cost": self.substation_cost,
}
self._outputs["num_substations"] = self.num_substations
@property
def substation_cost(self):
"""Returns total procuremet cost of the topside."""
return (
self.mpt_cost
+ self.topside_cost
+ self.shunt_reactor_cost
+ self.switchgear_costs
+ self.ancillary_system_costs
+ self.land_assembly_cost
)
@property
def total_cost(self):
"""Returns total procurement cost of the substation(s)."""
if not self._outputs:
raise Exception("Has OffshoreSubstationDesign been ran yet?")
return (
self.substructure_cost + self.substation_cost
) * self.num_substations
[docs]
def calc_substructure_length(self):
"""Calculates substructure length as the site depth + 10m."""
self.substructure_length = self.config["site"]["depth"] + 10
[docs]
def calc_substructure_deck_space(self):
"""
Calculates required deck space for the substation substructure.
Coming soon!
"""
self.substructure_deck_space = 1
[docs]
def calc_topside_deck_space(self):
"""
Calculates required deck space for the substation topside.
Coming soon!
"""
self.topside_deck_space = 1
[docs]
def calc_num_mpt_and_rating(self):
"""
Calculates the number of main power transformers (MPTs)
and their rating.
Parameters
----------
num_turbines : int
turbine_rating : float
"""
num_turbines = self.config["plant"]["num_turbines"]
turbine_rating = self.config["turbine"]["turbine_rating"]
capacity = num_turbines * turbine_rating
self.num_substations = self._design.get(
"num_substations", int(np.ceil(capacity / 1200))
)
self.num_mpt = np.ceil(
num_turbines * turbine_rating / (250 * self.num_substations)
)
self.mpt_rating = (
round(
(
(num_turbines * turbine_rating * 1.15)
/ (self.num_mpt * self.num_substations)
)
/ 10.0
)
* 10.0
)
[docs]
def calc_mpt_cost(self):
"""
Calculates the total cost for all MPTs.
Parameters
----------
mpt_cost_rate : float
"""
_key = "mpt_cost_rate"
mpt_cost_rate = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
self.mpt_cost = self.mpt_rating * self.num_mpt * mpt_cost_rate
[docs]
def calc_topside_mass_and_cost(self):
"""
Calculates the mass and cost of the substation topsides.
Parameters
----------
topside_fab_cost_rate : int | float
topside_design_cost: int | float
"""
_key = "topside_fab_cost_rate"
topside_fab_cost_rate = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
_key = "topside_design_cost"
topside_design_cost = self._design.get(
_key,
self.get_default_cost("substation_design", _key, subkey="HVAC"),
)
self.topside_mass = 3.85 * self.mpt_rating * self.num_mpt + 285
self.topside_cost = (
self.topside_mass * topside_fab_cost_rate + topside_design_cost
)
[docs]
def calc_shunt_reactor_cost(self):
"""
Calculates the cost of the shunt reactor.
Parameters
----------
shunt_cost_rate : int | float
"""
_key = "shunt_cost_rate"
shunt_cost_rate = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
self.shunt_reactor_cost = (
self.mpt_rating * self.num_mpt * shunt_cost_rate * 0.5
)
[docs]
def calc_switchgear_cost(self):
"""
Calculates the cost of the switchgear.
Parameters
----------
switchgear_cost : int | float
"""
_key = "switchgear_cost"
switchgear_cost_rate = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
self.switchgear_costs = self.num_mpt * switchgear_cost_rate
[docs]
def calc_ancillary_system_cost(self):
"""
Calculates cost of ancillary systems.
Parameters
----------
backup_gen_cost : int | float
workspace_cost : int | float
other_ancillary_cost : int | float
"""
_key = "backup_gen_cost"
backup_gen_cost = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
_key = "workspace_cost"
workspace_cost = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
_key = "other_ancillary_cost"
other_ancillary_cost = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
self.ancillary_system_costs = (
backup_gen_cost + workspace_cost + other_ancillary_cost
)
[docs]
def calc_assembly_cost(self):
"""
Calculates the cost of assembly on land.
Parameters
----------
topside_assembly_factor : int | float
"""
_key = "topside_assembly_factor"
topside_assembly_factor = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
self.land_assembly_cost = (
self.switchgear_costs
+ self.shunt_reactor_cost
+ self.ancillary_system_costs
) * topside_assembly_factor
[docs]
def calc_substructure_mass_and_cost(self):
"""
Calculates the mass and associated cost of the substation substructure.
Parameters
----------
oss_substructure_cost_rate : int | float
oss_pile_cost_rate : int | float
"""
_key = "oss_substructure_cost_rate"
oss_substructure_cost_rate = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
_key = "oss_pile_cost_rate"
oss_pile_cost_rate = self._design.get(
_key, self.get_default_cost("substation_design", _key)
)
substructure_mass = 0.4 * self.topside_mass
substructure_pile_mass = 8 * substructure_mass**0.5574
self.substructure_cost = (
substructure_mass * oss_substructure_cost_rate
+ substructure_pile_mass * oss_pile_cost_rate
)
self.substructure_mass = substructure_mass + substructure_pile_mass
@property
def design_result(self):
"""Returns the results of self.run()."""
if not self._outputs:
raise Exception("Has OffshoreSubstationDesign been ran yet?")
return self._outputs
@property
def detailed_output(self):
"""Returns detailed phase information."""
_outputs = {
"num_substations": self.num_substations,
"substation_mpt_rating": self.mpt_rating,
"substation_topside_mass": self.topside_mass,
"substation_topside_cost": self.topside_cost,
"substation_substructure_mass": self.substructure_mass,
"substation_substructure_cost": self.substructure_cost,
}
return _outputs