1+ # -*- coding: utf-8 -*-
2+ """
3+ This example shows a layout optimization with varied bathymetry and soil
4+ and then application of 3D cable designs, FOWT and substation design details,
5+ cable rerouting to avoid obstacles, and more.
6+
7+ Layout objects inherit from Project, so they have access to all the
8+ project methods (including unload etc)
9+
10+ An alternative method to including layout inputs is shown in the example at the bottom of
11+ famodel/design/layout.py
12+
13+ This example will take a long time to run if not used on an HPC with the
14+ layout setting parallel=True. To simply see what the initial layout looks like
15+ instead of optimizing, comment out the sections labeled **COMMENT OUT TO SKIP OPTIMIZATION PROCESS**
16+ """
17+
18+ from copy import deepcopy
19+ import numpy as np
20+ import moorpy as mp
21+ import famodel
22+ from famodel .design .layout import Layout
23+ from floris import (
24+ TimeSeries ,
25+ WindRose ,
26+ )
27+ from moorpy .helpers import lines2ss
28+ import pandas as pd
29+ from famodel .mooring .mooring import Mooring
30+ from famodel .anchors .anchor import Anchor
31+ from famodel .helpers import adjustMooring
32+ import ruamel .yaml
33+ yaml = ruamel .yaml .YAML ()
34+ from pyswarm import pso # change to whatever optimizer you're using
35+ from famodel .helpers import createRAFTDict
36+ from famodel .helpers import route_around_anchors
37+ import os
38+
39+ # - - - - - Input Files
40+ dir = os .path .dirname (os .path .realpath (__file__ ))
41+ mdFile = os .path .join (dir ,'Layout_Inputs' ,'FCD_MoorDyn_ST.dat' )
42+ cableFile = os .path .join (dir ,'Layout_Inputs' ,'cableConfigMaine.yaml' )
43+ bathFile = os .path .join (dir ,'Layout_Inputs' ,'GulfofMaine_bathy.txt' )
44+ soilFile = os .path .join (dir ,'Layout_Inputs' ,'GulfofMaine_soil.txt' )
45+ florisFile = os .path .join (dir ,'Layout_Inputs' ,'gch_floating.yaml' )
46+ turbineFile = os .path .join (dir ,'Layout_Inputs' ,'IEA-15-240-RWT.yaml' )
47+ fowtFile = os .path .join (dir ,'Layout_Inputs' ,'VolturnUS.yaml' )
48+ subFile = os .path .join (dir ,'Layout_Inputs' ,'substation.yaml' )
49+ wfile = os .path .join (dir ,'Layout_Inputs' ,'GulfOfMaine_metocean_1hr.txt' )
50+
51+ # ----- Layout input information
52+ nt = 20 # number of turbines
53+ noss = 1 # number of substations
54+ # conductor sizes for cables
55+ iac_typical_conductor = np .array ([300 , 630 , 1000 ])
56+ # this examle uses a rectangular lease area, but can have any shape you want
57+ lease_width = 10000 # length/width of the lease area
58+ lease_length = 10000
59+ oss_coords = np .array ([[0 , 0 ]]) # approx location of oss, will be updated by optimizer to fit in uniform grid
60+ lease_coords = np .array ([
61+ (- lease_width / 2 , - lease_length / 2 ),
62+ (- lease_width / 2 , lease_length / 2 ),
63+ (lease_width / 2 , lease_length / 2 ),
64+ (lease_width / 2 , - lease_length / 2 )
65+ ])
66+ exclusion_coords = np .array ([[(2800 , 500 ), # coordinates for an exclusion zone within the lease
67+ (4000 , 500 ),
68+ (3600 , 3400 ),
69+ (2800 , 3400 ),
70+ (2800 , 500 )]])
71+
72+ # ----- Optimization inputs -----
73+ opt_mode = 'CAPEX' # what do you want to optimize? (LCOE = 'LCOE2', AEP='AEP', CapEx='CAPEX')
74+ opt_method = 'PSO' # optimizer type
75+ use_FLORIS = False # Change to true if you're running AEP or LCOE optimization
76+ # Uniform grid design variables initial values (Xu): [x-spacing [km], y-spacing [km], x-translation [km], y-translation [km], grid rotation [deg], grid skew [deg], platform rotation [deg]]
77+ Xu = [1.65 , 1.85 , 0 , 0 , 20 , 10 , 0 ] # this is the initial values for the uniform grid design variables, these will be changed by the optimizer
78+ # lower and upper boundaries for each design variable in Xu
79+ boundaries_UG = np .array (([1.111 ,2.7 ],[1.111 ,2.7 ],[- 2.0 ,2.0 ],[- 2.0 ,2.0 ],[0 ,180 ],[- 30 ,30 ],[0 ,120 ]))
80+ swarmsize = 2 # this is a small number for this example, generally would use swarmsize of well over 100+
81+ niterations = 2 # this is a small number for this example, generally would use well over 100+ iterations
82+
83+
84+
85+ # ---- LOAD MOORPY SYSTEM -----
86+ ms = mp .System (file = mdFile )
87+ ms .initialize ()
88+ ms .solveEquilibrium ()
89+
90+ # add in needed info from getLineProps (MBL,cost,d_nom)
91+ for typ in ms .lineTypes .values ():
92+ if 'chain' in typ ['material' ]:
93+ d_nom = typ ['d_vol' ]/ 1.8
94+ ms .setLineType (d_nom * 1000 ,'chain' ,name = typ ['name' ])
95+ elif 'poly' in typ ['material' ]:
96+ d_nom = typ ['d_vol' ]/ .86
97+ ms .setLineType (d_nom * 1000 ,'polyester' ,name = typ ['name' ])
98+
99+ msNew = lines2ss (ms )
100+
101+ ss = deepcopy (msNew .lineList [0 ])
102+ ss .rad_fair = 58
103+ ss .z_fair = - 14
104+ rotation_mode = True
105+ rot_rad = np .zeros ((nt ))
106+
107+
108+ # ----- LOAD WIND DATA -----
109+ colnames = ['YY' ,'MM' ,'DD' ,'hh' ,'WDIR10m' ,'WSPD10m' ,'WDIR150m' ,'WSPD150m' ,'MWD' ,'WVHT' ,'DPD' ,'CDIR' ,'CSPD' ,'ATMP10m' ,'WTMP' ]
110+
111+ df = pd .read_csv (wfile ,sep = '\t ' ,engine = 'python' ,header = 5 ,names = colnames )
112+
113+ ws = np .array (df ['WSPD150m' ].tolist ())
114+ wd = np .array (df ['WDIR150m' ].tolist ())
115+
116+ time_series = TimeSeries (
117+ wind_directions = wd , wind_speeds = ws , turbulence_intensities = 0.06
118+ )
119+ # The TimeSeries class has a method to generate a wind rose from a time series based on binning
120+ wind_rose = time_series .to_WindRose (wd_step = 5 , ws_step = 1 )
121+
122+
123+ # ----- SETUP LAYOUT SETTINGS -----
124+ anchor_settings = {'anchor_design' :{'A' :15 ,'zlug' :20 },
125+ 'anchor_type' :'DEA' ,
126+ 'anchor_resize' :False ,
127+ 'fix_zlug' :True ,
128+ 'FSdiff_max' :{'Ha' :.1 ,'Va' :.2 },
129+ 'FS_min' :{'Ha' :2 ,'Va' :0 },
130+ 'mass' :9159 }
131+
132+ # mooring line adjustment settings
133+ adjuster_settings = {'method' : 'horizontal' , # adjust horizontal pretension when bathymetry changes
134+ 'i_line' : [1 ], # index of line section to be altered to fit bathymetry
135+ 'span' : 700 - 58 , # horizontal distance from anchor to fairlead
136+ }
137+
138+ layout_settings = {
139+ 'n_turbines' : nt ,
140+ 'n_cluster' : 3 , # number of cable strings entering each substation
141+ 'turb_rot' : np .zeros ((nt )),
142+ 'rotation_mode' : True ,
143+ 'cable_mode' : True , # include cable routing
144+ 'oss_coords' : oss_coords ,
145+ 'boundary_coords' : lease_coords ,
146+ 'ss' : ss ,
147+ 'mooringAdjuster' : adjustMooring , # use fad toolset adjuster function, or replace with your own
148+ 'adjuster_settings' : adjuster_settings ,
149+ 'bathymetry_file' : bathFile ,
150+ 'soil_file' : soilFile ,
151+ 'floris_file' : florisFile ,
152+ 'exclusion_coords' : exclusion_coords ,
153+ 'use_FLORIS' : use_FLORIS ,
154+ 'wind_rose' : wind_rose ,
155+ 'mode' : opt_mode ,
156+ 'optimizer' : opt_method ,
157+ 'parallel' : False , # parallelize floris AEP calculations
158+ 'anchor_settings' : anchor_settings ,
159+ 'noss' : noss ,
160+ 'iac_typical_conductor' : iac_typical_conductor
161+ }
162+
163+ # ----- INITIALIZE LAYOUT ------
164+ layout1 = Layout (X = [], Xu = Xu , ** layout_settings )
165+
166+ # plot the layout 2 ways
167+ layout1 .plotLayout ()
168+ layout1 .plot2d ()
169+
170+ # --- RUN LAYOUT OPTIMIZATION - **COMMENT OUT TO SKIP OPTIMIZATION PROCESS**---
171+ # Note: if you're using a different optimizer, you'll need to change this section
172+ res , fopt = pso (layout1 .objectiveFunUG ,
173+ lb = boundaries_UG [:,0 ],
174+ ub = boundaries_UG [:,1 ],
175+ f_ieqcons = layout1 .constraintFunsUG ,
176+ swarmsize = swarmsize ,
177+ omega = 0.72984 ,
178+ phip = 0.6 ,
179+ phig = 0.8 ,
180+ maxiter = niterations ,
181+ minstep = 1e-8 ,
182+ minfunc = 1e-8 ,
183+ debug = True )
184+ layout1 .updateLayoutUG (Xu = res , level = 2 , refresh = True ) # do a higher-fidelity update
185+
186+ # Save best result design variables
187+ print (res )
188+ with open ('Optimization_results.txt' ,'w' ) as ofile :
189+ ofile .write (str (res ))
190+ ofile .close ()
191+
192+ # ----- ADD FULL TURBINE, PLATFORM, & SUBSTATION DESIGN -----
193+ # ensure fairlead radius and depth are right for FOWT platforms
194+ for pf in layout1 .platformList .values ():
195+ if pf .entity == 'FOWT' :
196+ pf .rFair = 58
197+ pf .zFair = - 14
198+
199+ with open (turbineFile ) as ft :
200+ turb = dict (yaml .load (ft ))
201+ layout1 .turbineTypes = [turb ]
202+ with open (fowtFile ) as ff :
203+ fowt = dict (yaml .load (ff ))
204+ layout1 .platformTypes = []
205+ layout1 .platformTypes .append (fowt )
206+
207+ # We don't have a substation design, so just make a minimal platform type
208+ # and combine it with the FOWT platform design for now...
209+ sub = {'type' : 'Substation' ,
210+ 'rFair' : 38 ,
211+ 'zFair' : - 14 }
212+ sub = sub | fowt
213+ # Note: this does not include mooring for the substation as
214+ # it's assumed this will be different.
215+ # We only minimally consider substations as a
216+ # point(s) towards which we direct the cables
217+ # If you want a full substation design with moorings
218+ # You'll have to add that in here!
219+ layout1 .platformTypes .append (sub )
220+
221+ for pf in layout1 .platformList .values ():
222+ if not pf .entity == 'Substation' :
223+ layout1 .addTurbine (typeID = 1 ,platform = pf ,turbine_dd = turb )
224+ pf .dd ['type' ] = 0
225+ else :
226+ pf .dd ['type' ] = 1
227+
228+
229+ rd = createRAFTDict (layout1 )
230+ layout1 .getRAFT (rd )
231+
232+ # ----- ADD 3D CABLE DESIGN -----
233+ # load in cable configs
234+ with open (cableFile ) as fc :
235+ cC = yaml .load (fc )
236+ cableConfig = dict (cC ) # different cable designs for max and min depths defined in the file allow us to interpolate the designs and adjust the cable length and buoyancy for dpeth
237+
238+ # apply 3D cable design from cableFile to the layout, adjust dynamic cable headings, and rout static cables around anchors
239+ layout1 .addCablesConnections (layout1 .iac_dic , # list of dictionaries describing 2D cable layout
240+ cableConfig = cableConfig , # 3D cable design configurations
241+ heading_buffer = 30 , # buffer angle between mooring lines and dynamic cabled
242+ )
243+ layout1 .plot2d ()
244+ # let's try adjusting the dynamic cable heading the other way
245+ layout1 .addCablesConnections (layout1 .iac_dic ,
246+ cableConfig = cableConfig ,
247+ heading_buffer = 30 ,
248+ adj_dir = - 1 )
249+ layout1 .plot2d ()
250+ # re-route static cables to avoid anchor crossings
251+ route_around_anchors (layout1 ,padding = 100 )
252+ # re-create moorpy array with 3D cables
253+ layout1 .getMoorPyArray ()
254+
255+ # plot
256+ layout1 .plot3d (plot_fowt = True )
257+ layout1 .plot2d ()
258+
259+ # unload to a yaml
260+ layout1 .unload ('optimized_array.yaml' )
0 commit comments