Skip to content

Commit c8dcf8f

Browse files
committed
Small layout.py & project fix, add layout official example
-- small layout.py fix to work with previous mooringAdjust update that allows multiple line sections to be adjusted for depth -- small project fix to allow read-in and write-out of exclusion zones to & from ontology files - also removed fake exclusion zones from examples & test ontologies to prevent confusion -- added layout example to examples/06_Design folder - includes adjusting mooring and 3D cable design for bathymetry and adjusting cable routing
1 parent ee370e4 commit c8dcf8f

19 files changed

+185974
-181
lines changed

examples/01_Visualization/project.yaml

Lines changed: 0 additions & 135 deletions
This file was deleted.
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
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

Comments
 (0)