Custom Array Cabling Guide#
Dudgeon Wind Farm#
This guide will walk through four of the main use cases for using the custom array cable layout
functionality of ORBIT for when custom turbine locations, cable lengths or burial speeds are needed.
This example uses the Dudgeon Wind Farm turbine locations derived from their publicly available Call to Mariners documents.
Setup#
First, we'll import the necessary libraries and functionality, and setup our library reference.
from copy import deepcopy
from pprint import pprint
from pathlib import Path
import numpy as np
import pandas as pd
import ORBIT
from ORBIT import ProjectManager
from ORBIT.core import library
from ORBIT.phases.design import CustomArraySystemDesign
from ORBIT.phases.install import ArrayCableInstallation
# Set the library path for later use and initialize the ORBIT library
here = Path(".").resolve()
library_path = here.parents[1] / "library" if here.stem == "topical_guides" else here
library.initialize_library(library_path)
ORBIT library intialized at '/home/runner/work/ORBIT/ORBIT/library'
Contents#
Overview#
Working with the ORBIT Library#
In the highest level of this repository there is a folder called library where all of the example
data for this notebook is going to be stored. While any folder could be used, the folder structure
must be strictly adhered to. More details on this structure can be found in the
library section of the ORBIT introduction tutorial.
For this example of how to setup a configuration, we will be using the file
library/project/config/example_custom_array_simple.yaml.
Now, we will load the configuration file and display it below.
config = library.extract_library_specs("config", "example_custom_array_simple")
pprint(config)
{'array_system_design': {'cables': ['XLPE_400mm_33kV',
'XLPE_630mm_33kV',
'XLPE_630mm_220kV'],
'location_data': 'dudgeon_array'},
'plant': {'layout': 'custom', 'num_turbines': 67},
'site': {'depth': 20},
'turbine': 'SWT_6MW_154m_110m'}
Key Differences In A Custom Layout Configuration#
There are 2 important differences in the custom array design that are work calling out:
The
array_system_designdictionary contains thelocation_datakey, which contains the base file name for the layout file, which is assumed to be CSV file located atlibrary/cables/dudgeon_array.csvThe
plantdictionary uses the "custom" forlayoutto indicate that the custom array design workflow will be used.
Now, let's see what is contained within the additional files from the configuration dictionary. It should be noted that running the design class extracts the data from the files automatically to produce the below output.
array = CustomArraySystemDesign(config)
array.run()
print(array.config.dump())
{
"array_system_design": {
"cables": {
"XLPE_400mm_33kV": {
"ac_resistance": 0.06,
"capacitance": 225,
"conductor_size": 400,
"cost_per_km": 364352,
"current_capacity": 600,
"inductance": 0.375,
"linear_density": 35,
"name": "XLPE_400mm_33kV",
"rated_voltage": 33
},
"XLPE_630mm_220kV": {
"ac_resistance": 0.25,
"cable_type": "HVAC",
"capacitance": 160,
"conductor_size": 630,
"cost_per_km": 853557,
"current_capacity": 715,
"inductance": 0.41,
"linear_density": 96,
"name": "XLPE_630mm_220kV",
"rated_voltage": 220
},
"XLPE_630mm_33kV": {
"ac_resistance": 0.04,
"cable_type": "HVAC",
"capacitance": 300,
"conductor_size": 630,
"cost_per_km": 546528,
"current_capacity": 700,
"inductance": 0.35,
"linear_density": 42.5,
"name": "XLPE_630mm_33kV",
"rated_voltage": 33
}
},
"location_data": "dudgeon_array"
},
"plant": {
"layout": "custom",
"num_turbines": 67
},
"site": {
"depth": 20
},
"turbine": {
"blade": {
"deck_space": 100,
"length": 75,
"mass": 100
},
"hub_height": 110,
"nacelle": {
"deck_space": 200,
"mass": 360
},
"name": "SWT-6MW-154",
"rated_windspeed": 13,
"rotor_diameter": 154,
"tower": {
"deck_space": 36,
"length": 110,
"mass": 150,
"sections": 2
},
"turbine_rating": 6
}
}
UserWarning: /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/ORBIT/phases/design/array_system_design.py:1103
Missing data in columns ['cable_length', 'bury_speed']; all values will be calculated.
Custom Array Layout CSV Explanation#
When the dudgeon_array.csv file is loaded, it is not passed back into the configuration
dictionary, so let's dissect this file:
The file must have all of the columns shown below (not case-sensitive).
All columns must be completely filled out for turbines (note on substation(s) following).
cable_lengthandbury_speedare optional and if these are not known, simply fill with a 0.
A latitude and longitude must be provided for all turbines and substation(s). This can either be a WGS-84 decimal coordinate or a distance-based "coordinate" where latitude and longitude are the distances from some reference point, in kilometers; see Case 3 for more details.
Define the offshore substation(s)
For each substation, the values in columns
idandsubstation_idmust be the same.There is no need to fill in any data for the columns
String,Order,cable_lengthandbury_speed.
Define the turbines
Each turbine should have a reference to its substation in the
substation_idcolumn.In this example, there is one substation, so all of the values are "DOW_OSS".
stringandordershould be 0-indexed for their ordering and not skip any numbers.In this example, the strings are ordered in clock-wise order starting from the string with turbines labeled with an "A" in the Call to Mariners
The ordering on a string should travel from substation to the farthest end of the cable
Below is the how the Dudgeon layout has been configured.
df = pd.read_csv(library_path / "cables/dudgeon_array.csv").fillna("")
df.sort_values(by=["String", "Order"])
| id | substation_id | name | Longitude | Latitude | String | Order | cable_length | bury_speed | |
|---|---|---|---|---|---|---|---|---|---|
| 1 | DAE_A1 | DOW_OSS | DAE_A1 | 1.358783 | 53.243950 | 0.0 | 0.0 | 0.0 | 0.0 |
| 2 | DAD_A2 | DOW_OSS | DAD_A2 | 1.349033 | 53.248467 | 0.0 | 1.0 | 0.0 | 0.0 |
| 3 | DAC_A3 | DOW_OSS | DAC_A3 | 1.339283 | 53.252983 | 0.0 | 2.0 | 0.0 | 0.0 |
| 4 | DAB_A4 | DOW_OSS | DAB_A4 | 1.329550 | 53.257500 | 0.0 | 3.0 | 0.0 | 0.0 |
| 5 | DAA_A5 | DOW_OSS | DAA_A5 | 1.319800 | 53.262017 | 0.0 | 4.0 | 0.0 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 59 | DAF_L2 | DOW_OSS | DAF_L2 | 1.368533 | 53.239433 | 11.0 | 1.0 | 0.0 | 0.0 |
| 60 | DAG_L3 | DOW_OSS | DAG_L3 | 1.378250 | 53.234917 | 11.0 | 2.0 | 0.0 | 0.0 |
| 61 | DAH_L4 | DOW_OSS | DAH_L4 | 1.388000 | 53.230400 | 11.0 | 3.0 | 0.0 | 0.0 |
| 62 | DAJ_L5 | DOW_OSS | DAJ_L5 | 1.397750 | 53.225883 | 11.0 | 4.0 | 0.0 | 0.0 |
| 0 | DOW_OSS | DOW_OSS | DOW_OSS | 1.378767 | 53.264800 |
68 rows × 9 columns
Case 1: Needing to know what to collect#
In this first case, we assume little knowledge of what data are required for the CSV, and walk
through generating a sample CSV. We will use the
library/project/config/example_custom_array_no_data.csv
configuration for this example.
First, we need to load in the configuration dictionary. Then, we will create a starter file in
the <library_path>/project/config/plant folder that can be filled in for a new project, which will be saved in the initialized library folder.
config = library.extract_library_specs("config", "example_custom_array_no_data")
pprint(config)
array = CustomArraySystemDesign(config)
save_name = array.config["array_system_design"]["location_data"]
array.create_project_csv(save_name, folder="plant")
{'array_system_design': {'cables': ['XLPE_400mm_33kV', 'XLPE_630mm_33kV'],
'location_data': 'dudgeon_array_no_data'},
'plant': {'layout': 'custom', 'num_turbines': 67},
'site': {'depth': 20},
'turbine': 'SWT_6MW_154m_110m'}
+--------------------------------+
| PROJECT SPECIFICATIONS |
+---------------------------+----+
| N turbines full string | 5 |
| N full strings | 13 |
| N turbines partial string | 2 |
| N partial strings | 1 |
+---------------------------+----+
Saving custom array to: <library_path>/project/plant/dudgeon_array_no_data.csv
Save complete!
There are a few items worth noting in the layout:
The offshore substation (row 0) is indicated via the
idandsubstation_idcolumns being equalFor substations only the
id,substation_id,name,latitude, andlongitudeare requiredcable_lengthandbury_speedare optional columns for turbinesstringandorderare filled out to maximize the length of a string given the cable(s) provided, which translates to a maximum of 5 turbines in a string.The string and cable numbering are 0-indexed, so the numbering system starts with 0.
dudgeon_array_no_data = pd.read_csv(library_path / f"project/plant/{save_name}.csv")
dudgeon_array_no_data
# NOTE: remove this line if you would like to keep this data
Path(library_path / "project/plant/dudgeon_array_no_data.csv").unlink()
Case 2: Straight-Line Distance for Cable Lengths#
We have the turbine and offshore substation locations that were extracted from the Call to Mariners referenced in the Dudgeon Wind Farm Overview. However there is not any information regarding the actual cable lengths or the cable burial speeds for each section. As such, we will demonstrate using the standard straight-line distance and default cable burying rates.
This case will rely on the
library/project/config/example_custom_array_simple.yaml configuration.
config = library.extract_library_specs("config", "example_custom_array_simple")
pprint(config)
{'array_system_design': {'cables': ['XLPE_400mm_33kV',
'XLPE_630mm_33kV',
'XLPE_630mm_220kV'],
'location_data': 'dudgeon_array'},
'plant': {'layout': 'custom', 'num_turbines': 67},
'site': {'depth': 20},
'turbine': 'SWT_6MW_154m_110m'}
The below figure demonstrates the meaning of the straight-line distance between two points.
array = CustomArraySystemDesign(config)
array.run()
array.plot_array_system(show=True)
UserWarning: /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/ORBIT/phases/design/array_system_design.py:1103
Missing data in columns ['cable_length', 'bury_speed']; all values will be calculated.
Here the cable length and bury speed are still set to 0 to indicate that they are unknown, which will tell the installation phase to use either ORBIT's defaults or the vessel's settings. Notice that the latitude and longitude here are WGS-84 decimal coordinates.
array.location_data
| id | substation_id | substation_name | substation_latitude | substation_longitude | turbine_name | turbine_latitude | turbine_longitude | string | order | cable_length | bury_speed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | DAE_A1 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAE_A1 | 53.243950 | 1.358783 | 0 | 0 | 0.0 | 0.0 |
| 1 | DAD_A2 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAD_A2 | 53.248467 | 1.349033 | 0 | 1 | 0.0 | 0.0 |
| 2 | DAC_A3 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAC_A3 | 53.252983 | 1.339283 | 0 | 2 | 0.0 | 0.0 |
| 3 | DAB_A4 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAB_A4 | 53.257500 | 1.329550 | 0 | 3 | 0.0 | 0.0 |
| 4 | DAA_A5 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAA_A5 | 53.262017 | 1.319800 | 0 | 4 | 0.0 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 57 | DCE_L1 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DCE_L1 | 53.251783 | 1.368833 | 11 | 0 | 0.0 | 0.0 |
| 58 | DAF_L2 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAF_L2 | 53.239433 | 1.368533 | 11 | 1 | 0.0 | 0.0 |
| 59 | DAG_L3 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAG_L3 | 53.234917 | 1.378250 | 11 | 2 | 0.0 | 0.0 |
| 60 | DAH_L4 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAH_L4 | 53.230400 | 1.388000 | 11 | 3 | 0.0 | 0.0 |
| 61 | DAJ_L5 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAJ_L5 | 53.225883 | 1.397750 | 11 | 4 | 0.0 | 0.0 |
67 rows × 12 columns
For later comparison, we'll show the cabling costs for the straight-line cabling assumption.
print(f"{'Cable Type':<16}| {'Cost in USD':>15}")
for cable, cost in array.cost_by_type.items():
print(f"{cable:<16}| ${cost:>15,.2f}")
print(f"{'Total':<16}| ${array.total_cable_cost:>15,.2f}")
Cable Type | Cost in USD
XLPE_400mm_33kV | $ 18,701,160.61
XLPE_630mm_33kV | $ 8,144,423.11
XLPE_630mm_220kV| $ 10,361,949.24
Total | $ 37,207,532.97
Case 3: Distance-based coordinate system#
In this case, we will consider each turbine and substation on a distance-based coordinate system where the longitude and latitude are the longitudinal (x direction) and latitudinal (y direction) distances, in kilometers, from a common reference point. We are still using the Dudgeon data, but the distances were computed outside of this example and the details are not be included.
Important
For distance-based coordinate systems, all points should be be positive, meaning the reference point should either be both west and south of the farm itself, or at the west-most and south-most point.
Below, we can see that the input file
library/cables/dudgeon_distance_based.csv
is still encoded in the exact same manner as Case 2, but latitude and longitude are
relative distances and not proper coordinates.
df = pd.read_csv(library_path / "cables/dudgeon_distance_based.csv", index_col=False).fillna("")
df
| id | substation_id | name | longitude | latitude | string | order | cable_length | bury_speed | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | DOW_OSS | DOW_OSS | DOW_OSS | 16.229909 | 35.769173 | ||||
| 1 | DAE_A1 | DOW_OSS | DAE_A1 | 14.890845 | 33.450759 | 0.0 | 0.0 | 0.0 | 0.0 |
| 2 | DAD_A2 | DOW_OSS | DAD_A2 | 14.237528 | 33.953026 | 0.0 | 1.0 | 0.0 | 0.0 |
| 3 | DAC_A3 | DOW_OSS | DAC_A3 | 13.584211 | 34.455182 | 0.0 | 2.0 | 0.0 | 0.0 |
| 4 | DAB_A4 | DOW_OSS | DAB_A4 | 12.932034 | 34.957450 | 0.0 | 3.0 | 0.0 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 63 | DCE_L1 | DOW_OSS | DCE_L1 | 15.564263 | 34.321749 | 11.0 | 0.0 | 0.0 | 0.0 |
| 64 | DAF_L2 | DOW_OSS | DAF_L2 | 15.544161 | 32.948491 | 11.0 | 1.0 | 0.0 | 0.0 |
| 65 | DAG_L3 | DOW_OSS | DAG_L3 | 16.195266 | 32.446335 | 11.0 | 2.0 | 0.0 | 0.0 |
| 66 | DAH_L4 | DOW_OSS | DAH_L4 | 16.848583 | 31.944067 | 11.0 | 3.0 | 0.0 | 0.0 |
| 67 | DAJ_L5 | DOW_OSS | DAJ_L5 | 17.501899 | 31.441800 | 11.0 | 4.0 | 0.0 | 0.0 |
68 rows × 9 columns
Using the distance-based location data requires us to set distance to True in the
array_system_design section of the configuration. This change is shown below in the
library/project/config/example_custom_array_simple_distance_based.yaml configuration.
config = library.extract_library_specs("config", "example_custom_array_simple_distance_based")
pprint(config)
{'array_system_design': {'cables': ['XLPE_400mm_33kV',
'XLPE_630mm_33kV',
'XLPE_630mm_220kV'],
'distance': True,
'location_data': 'dudgeon_distance_based'},
'plant': {'layout': 'custom', 'num_turbines': 67},
'site': {'depth': 20},
'turbine': 'SWT_6MW_154m_110m'}
Alternatively, we can set the distance=True when calling the CustomArraySystemDesign, however
the configuration dictionary's setting will override this input to allow for project-level
configurations to run as expected. Below, we can see some of the cable lengths differ slightly due
to the methodology of converting the WGS-84coordinates to relative points, however the spacing is
maintained, and we can see that this is still the Dudgeon wind farm.
array_distance = CustomArraySystemDesign(config, distance=True)
array_distance.run()
array_distance.plot_array_system(show=True)
UserWarning: /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/ORBIT/phases/design/array_system_design.py:1103
Missing data in columns ['cable_length', 'bury_speed']; all values will be calculated.
Overall, the cabling cost is highly similar, with the difference being attributed to the method to convert the WGS-84 coordinates to relative coordinates.
print(f"{'Cable Type':<16} | {'Cost in USD (lat,lon)':>20} | {'Cost in USD (dist_lat,dist_lon)':>15}")
for (cable1, cost1), (cable2, cost2) in zip(array.cost_by_type.items(), array_distance.cost_by_type.items()):
print(f"{cable1:<16} | ${cost1:>20,.2f} | ${cost2:>15,.2f}")
print(f"{'Total':<16} | ${array.total_cable_cost:>20,.2f} | ${array_distance.total_cable_cost:>15,.2f}")
Cable Type | Cost in USD (lat,lon) | Cost in USD (dist_lat,dist_lon)
XLPE_400mm_33kV | $ 18,701,160.61 | $ 18,756,091.16
XLPE_630mm_33kV | $ 8,144,423.11 | $ 8,166,522.03
XLPE_630mm_220kV | $ 10,361,949.24 | $ 10,392,923.05
Total | $ 37,207,532.97 | $ 37,315,536.24
Case 4: Site-Wide Cable Length Modifications#
To account for exclusion zones from rocky soil or other seabed conditions, we use the
average_exclusion_percent input in the array_system_design configuration section. This exclusion
will be applied to all cable sections, so it's important to account for this when modeling
additional cable lengths.
In the
library/project/config/example_custom_array_exclusions.yaml
configuration, a 4.8% exclusion is applied to the entire farm. When plotting farms with exclusion
zones, they will not be shown since we are not mapping the true cable path, simply the connections
between turbines. In this case, we can also a modest increase in cabling costs resulting from the
additional cable required to account for the exclusion zones.
config = library.extract_library_specs("config", "example_custom_array_exclusions")
pprint(config)
array_exclusion = CustomArraySystemDesign(config)
array_exclusion.run()
{'array_system_design': {'average_exclusion_percent': 0.05,
'cables': ['XLPE_400mm_33kV',
'XLPE_630mm_33kV',
'XLPE_630mm_220kV'],
'location_data': 'dudgeon_array'},
'plant': {'layout': 'custom', 'num_turbines': 67},
'site': {'depth': 20},
'turbine': 'SWT_6MW_154m_110m'}
UserWarning: /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/ORBIT/phases/design/array_system_design.py:1103
Missing data in columns ['cable_length', 'bury_speed']; all values will be calculated.
print(f"{'Cable Type':<16}| {'Cost in USD':>15}")
for cable, cost in array_exclusion.cost_by_type.items():
print(f"{cable:<16}| ${cost:>15,.2f}")
print(f"{'Total':<16}| ${array_exclusion.total_cable_cost:>15,.2f}")
Cable Type | Cost in USD
XLPE_400mm_33kV | $ 19,601,240.84
XLPE_630mm_33kV | $ 8,538,527.60
XLPE_630mm_220kV| $ 10,868,096.91
Total | $ 39,007,865.35
Case 5: Custom Cable Lengths#
If we look at the map in the
Call to Mariners
there are different sized exclusions in the cables, so for this example we'll change the distances
from Case 4 to have more variation by using the cable_length column of the
location_data CSV. In addition, we will utilize the bury_speed column to demonstrate how these
columns will be used. Please note this work was performed outside the example, and we will only
show the resulting configurations.
For this example, half of the wind farm will have different soil condition, so we will use our proxy:
bury_speed by modifying the burial speed to be fast (0.5 km/h) and slow (0.05 km/hr),
respectively, to account for sandy soil and rocky soil. The purpose of this is for passing through
customized parameters in the design phase to be utilized in the installation phase as will be seen
in the final two examples.
config = library.extract_library_specs("config", "example_custom_array_custom")
pprint(config)
array_custom = CustomArraySystemDesign(config)
array_custom.run()
{'array_system_design': {'cables': ['XLPE_400mm_33kV',
'XLPE_630mm_33kV',
'XLPE_630mm_220kV'],
'location_data': 'dudgeon_custom'},
'plant': {'layout': 'custom', 'num_turbines': 67},
'site': {'depth': 20},
'turbine': 'SWT_6MW_154m_110m'}
Note that there are now cable lengths defined as well as burial speeds for the installation phase.
array_custom.location_data
| id | substation_id | substation_name | substation_latitude | substation_longitude | turbine_name | turbine_latitude | turbine_longitude | string | order | cable_length | bury_speed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | DAE_A1 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAE_A1 | 53.243950 | 1.358783 | 0 | 0 | 3.135279 | 0.50 |
| 1 | DAD_A2 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAD_A2 | 53.248467 | 1.349033 | 0 | 1 | 0.993860 | 0.50 |
| 2 | DAC_A3 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAC_A3 | 53.252983 | 1.339283 | 0 | 2 | 0.993719 | 0.50 |
| 3 | DAB_A4 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAB_A4 | 53.257500 | 1.329550 | 0 | 3 | 0.992699 | 0.50 |
| 4 | DAA_A5 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAA_A5 | 53.262017 | 1.319800 | 0 | 4 | 0.993673 | 0.50 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 62 | DCE_L1 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DCE_L1 | 53.251783 | 1.368833 | 11 | 0 | 1.712822 | 0.05 |
| 63 | DAF_L2 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAF_L2 | 53.239433 | 1.368533 | 11 | 1 | 1.483318 | 0.05 |
| 64 | DAG_L3 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAG_L3 | 53.234917 | 1.378250 | 11 | 2 | 0.901721 | 0.05 |
| 65 | DAH_L4 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAH_L4 | 53.230400 | 1.388000 | 11 | 3 | 0.903679 | 0.05 |
| 66 | DAJ_L5 | DOW_OSS | DOW_OSS | 53.2648 | 1.378767 | DAJ_L5 | 53.225883 | 1.397750 | 11 | 4 | 0.903736 | 0.05 |
67 rows × 12 columns
Once again, the cabling costs have increased.
print(f"{'Cable Type':<16}| {'Cost in USD':>15}")
for cable, cost in array_custom.cost_by_type.items():
print(f"{cable:<16}| ${cost:>15,.2f}")
print(f"{'Total':<16}| ${array_custom.total_cable_cost:>15,.2f}")
Cable Type | Cost in USD
XLPE_400mm_33kV | $ 20,841,928.36
XLPE_630mm_33kV | $ 9,307,325.71
XLPE_630mm_220kV| $ 10,158,477.79
Total | $ 40,307,731.86
Incorporating Custom Array Designs Into ProjectManager#
Using cases 2, 3, 4, and 5 we will demonstrate the project-wide effects from differing cabling layouts.
Setting Up The Cases#
Using the
library/project/config/example_array_cable_install.yaml
configuration as a base configuration, we'll create a new configuration for each of the cases
using each case's design_result as the array_system values.
base_config = library.extract_library_specs("config", "example_array_cable_install")
array_case2 = deepcopy(base_config)
array_case2["array_system"] = array.design_result["array_system"]
array_case3 = deepcopy(base_config)
array_case3["array_system"] = array_distance.design_result["array_system"]
array_case4 = deepcopy(base_config)
array_case4["array_system"] = array_exclusion.design_result["array_system"]
array_case5 = deepcopy(base_config)
array_case5["array_system"] = array_custom.design_result["array_system"]
sim2 = ArrayCableInstallation(array_case2)
sim3 = ArrayCableInstallation(array_case3)
sim4 = ArrayCableInstallation(array_case4)
sim5 = ArrayCableInstallation(array_case5)
Run And Inspect The Simulation Results#
We can see that both the installation cost and the time required to complete the installations have all increased here, corresponding to the increased cable lengths and changes to the burial speeds defined above.
names = ("straight-line distance", "distance-based coordinates", "with exclusions", "custom")
simulations = (sim2, sim3, sim4, sim5)
print(f"{'Simulation':<26} | {'Cost (in USD)':>14} | {'Time (in hours)':>16}")
for name, simulation in zip(names, simulations):
simulation.run()
cost = simulation.installation_capex
time = simulation.total_phase_time
print(f"{name:<26} | ${cost:>13,.2f} | {time:>16,.0f}")
Simulation | Cost (in USD) | Time (in hours)
straight-line distance | $24,594,075.16 | 2,372
distance-based coordinates | $24,605,520.50 | 2,373
with exclusions | $24,784,480.17 | 2,391
custom | $31,792,520.58 | 3,088
Incorporating Case 5 Into ProjectManager#
We will now incorporate the design settings from Case 5 to demonstrate incorporation
of the custom array design tooling into ProjectManager. This example will use the
library/project/config/example_custom_array_project_manager.yaml
configuration.
config = library.extract_library_specs("config", "example_custom_array_project_manager")
config["array_system_design"]["location_data"] = library.extract_library_specs(
"cables", config["array_system_design"]["location_data"], file_type="csv"
)
config
{'design_phases': ['CustomArraySystemDesign'],
'install_phases': ['ArrayCableInstallation'],
'plant': {'layout': 'custom', 'num_turbines': 67},
'port': {'monthly_rate': 10000},
'site': {'depth': 20, 'distance': 50},
'turbine': 'SWT_6MW_154m_110m',
'array_system_design': {'cables': ['XLPE_400mm_33kV',
'XLPE_630mm_33kV',
'XLPE_630mm_220kV'],
'location_data': id substation_id name latitude longitude string order \
0 DOW_OSS DOW_OSS DOW_OSS 53.264800 1.378767 NaN NaN
1 DAE_A1 DOW_OSS DAE_A1 53.243950 1.358783 0.0 0.0
2 DAD_A2 DOW_OSS DAD_A2 53.248467 1.349033 0.0 1.0
3 DAC_A3 DOW_OSS DAC_A3 53.252983 1.339283 0.0 2.0
4 DAB_A4 DOW_OSS DAB_A4 53.257500 1.329550 0.0 3.0
.. ... ... ... ... ... ... ...
63 DCE_L1 DOW_OSS DCE_L1 53.251783 1.368833 11.0 0.0
64 DAF_L2 DOW_OSS DAF_L2 53.239433 1.368533 11.0 1.0
65 DAG_L3 DOW_OSS DAG_L3 53.234917 1.378250 11.0 2.0
66 DAH_L4 DOW_OSS DAH_L4 53.230400 1.388000 11.0 3.0
67 DAJ_L5 DOW_OSS DAJ_L5 53.225883 1.397750 11.0 4.0
cable_length bury_speed
0 NaN NaN
1 3.135279 0.50
2 0.993860 0.50
3 0.993719 0.50
4 0.992699 0.50
.. ... ...
63 1.712822 0.05
64 1.483318 0.05
65 0.901721 0.05
66 0.903679 0.05
67 0.903736 0.05
[68 rows x 9 columns],
'distance': False},
'array_cable_install_vessel': 'example_cable_lay_vessel',
'array_cable_bury_vessel': 'example_cable_lay_vessel'}
Below, we can see that the results coming from the ProjectManager are the same as the additive
results of running each phase separately.
project = ProjectManager(config)
project.run()
total = array_custom.total_cable_cost + sim5.installation_capex
print(f"Custom Design | ${array_custom.total_cable_cost:>13,.2f}")
print(f"Custom Installation | ${sim5.installation_capex:>13,.2f}")
print(f"Total Custom Cost | ${total:>13,.2f}")
print(f"Project Manager Cost | ${project.bos_capex:>13,.2f}")
Custom Design | $40,307,731.86
Custom Installation | $31,792,520.58
Total Custom Cost | $72,100,252.44
Project Manager Cost | $72,100,252.44