Skip to content

Commit d29f752

Browse files
committed
Changes to layout.py from old FADesign repo
-@lsirkis changes to layout translation -fix call to getDepthFromBathymetry in layout_helpers -anchor capex cost bug fix -'mooring_headings' as optional input to layout so the user can define the mooring system, defaults to uniform three line -remove requirement for three-line mooring systems -updates to example
1 parent c8af3ce commit d29f752

File tree

1 file changed

+132
-78
lines changed

1 file changed

+132
-78
lines changed

famodel/design/layout.py

Lines changed: 132 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,14 @@ def __init__(self, X, Xu, Xdb =[], wind_rose = [], ss = None, mooringAdjuster =
199199
#with open(os.path.join(dir,"CableProps_default.yaml")) as file:
200200
# source = yaml.load(file, Loader=yaml.FullLoader)
201201
#As = source['conductor_size']['size_A_df']
202-
self.iac_typical_conductor = getFromDict(kwargs, 'iac_typical_conductor',shape=-1, default = [0])
202+
self.iac_typical_conductor = getFromDict(kwargs, 'iac_typical_conductor',shape=-1, default = [300, 630, 1000])
203203
if len(self.iac_typical_conductor)==1 and self.iac_typical_conductor[0] == 0:
204204
self.iac_typical_conductor = np.array([ 70, 95, 120, 150, 185, 240, 300, 400, 500, 630,
205205
800, 1000, 1200,1400,1600,200,2500,3000,4000])
206206

207+
#moorings and mooring headings
208+
self.n_moor = getFromDict(kwargs, 'n_moor', default=3)
209+
self.mooring_headings = getFromDict(kwargs, 'mooring_headings', default = [0, 120, 240])
207210

208211
# ----- Offshore Substation -----
209212
self.noss = int(getFromDict(kwargs,'noss', default = 1))
@@ -344,29 +347,43 @@ def max_distance(x_values):
344347

345348

346349
# ----- Mooring system variables -----
350+
# depth relevant to new method of subsystem sink
351+
if 'depth' in kwargs:
352+
self.depth = kwargs['depth']
353+
else:
354+
self.depth=np.min(self.grid_depth)
355+
if 'adjuster_settings' in kwargs:
356+
from famodel.helpers import configureAdjuster
357+
method = getFromDict(kwargs['adjuster_settings'],'method', default='horizontal', dtype=str)
358+
iline = getFromDict(kwargs['adjuster_settings'],'i_line', default=0, dtype=int)
359+
if 'span' in kwargs['adjuster_settings']:
360+
span = kwargs['adjuster_settings']['span']
361+
else:
362+
span=None
363+
if 'target' in kwargs['adjuster_settings']:
364+
target= kwargs['adjuster_settings']['target']
365+
else:
366+
target=None
347367
print("setting up mooringList")
348-
self.mooringList = makeMooringListN(ss, 3*self.nt) # make Moorings
368+
self.mooringList = makeMooringListN(ss, self.n_moor*self.nt) # make Moorings
349369

350370
for mooring in self.mooringList.values(): # hackily set them up
351-
mooring.dd['sections'] = []
352-
mooring.dd['connectors'] = []
353-
for i,sec in enumerate(mooring.ss.lineList):
354-
mooring.dd['connectors'].append({'CdA':0,'m':0,'v':0})
355-
mooring.dd['sections'].append({'type':mooring.ss.lineList[i].type,
356-
'L':mooring.ss.lineList[i].L})
357-
mooring.dd['connectors'].append({'CdA':0,'m':0,'v':0})
358371
mooring.adjuster = mooringAdjuster # set the designer/adjuster function
372+
if 'adjuster_settings' in kwargs:
373+
configureAdjuster(mooring, adjuster=mooring.adjuster, method=method,
374+
i_line=iline, span=span, project=self, target=target)
359375

360376

361377
# ----- Platforms -----
362378
for i in range(self.nt):
363379
r = [self.turb_coords[i][0],self.turb_coords[i][1],0]
364-
self.platformList[i] = Platform(id=i, r=r, heading=0, mooring_headings=[0,120,240],rFair=ss.rad_fair,zFair=ss.z_fair)
380+
self.platformList[i] = Platform(id=i, r=r, heading=0,rFair=ss.rad_fair,zFair=ss.z_fair)
365381
self.platformList[i].entity = 'FOWT'
366382

367-
for j in range(3):
368-
self.platformList[i].attach(self.mooringList[i*3+j], end='b')
369-
383+
for j in range(self.n_moor):
384+
self.mooringList[i*self.n_moor+j].rel_heading = self.mooring_headings[j]
385+
self.platformList[i].attach(self.mooringList[i*self.n_moor+j], end='b')
386+
# self.mooringList[i*3+j].reposition(heading = self.mooring_headings[j],degrees=True)
370387

371388
# ---- Anchors ----
372389
self.anchorList = {}
@@ -399,7 +416,7 @@ def max_distance(x_values):
399416
elif anchor_settings and 'mass' in kwargs['anchor_settings']:
400417
anch.mass = kwargs['anchor_settings']['mass']
401418

402-
419+
403420
# --- develop anchor types ---
404421
self.anchorTypes = {}
405422
self.anchorMasses = {}
@@ -409,6 +426,7 @@ def max_distance(x_values):
409426
pf = self.platformList[0]
410427
# artificially set platform at 0,0
411428
pf.setPosition([0,0],project=self) # put in a random place and reposition moorings
429+
412430
# create ms for this platform
413431
msPF = pf.mooringSystem()
414432
# set depth artificially to mean depth
@@ -464,16 +482,20 @@ def max_distance(x_values):
464482
self.anchorCosts[name] = deepcopy(anch.getCost())
465483
except:
466484
self.anchorCosts[name] = 0
467-
468-
485+
469486

470-
self.ms_na = 3 # Number of anchors per turbine. For now ONLY 3 point mooring system.
487+
self.ms_na = self.n_moor # Number of anchors per turbine. For now equals number of mooring lines (no shared anchors/lines)
471488
#self.ms_anchor_depth = np.zeros((self.nt*self.ms_na)) # depths of anchors
472489
self.anchor_coords= np.zeros((self.nt*self.ms_na,2)) # anchor x-y coordinate list [m]
473490
self.ms_bufferzones_pos = np.zeros((self.nt,), dtype=object) # Buffer zones for moorign system
474491
self.ms_bufferzones_rout = np.zeros((self.nt,), dtype=object)
475492
self.ms_bufferzones_rout_points = np.zeros((self.nt,), dtype=object)
476-
493+
self.getMoorPyArray()
494+
495+
self.aep_evol = []
496+
self.moor_evol = []
497+
self.cab_evol = []
498+
self.lcoe_evol = []
477499

478500
# ----- Initialize the FLORIS interface fi -----
479501
self.use_FLORIS = getFromDict(kwargs,'use_FLORIS', default = False)
@@ -584,22 +606,41 @@ def generateGridPoints(self, Xu, trans_mode, boundary_index=-1):
584606

585607
# Lease area shape: Get min and max xy coordinates and calculate width
586608
min_x, min_y, max_x, max_y = boundary.bounds # self.boundary_sh.bounds
587-
xwidth = abs(max_x-min_x)
588-
ywidth = abs(max_y-min_y)
589-
590-
609+
# xwidth_old = abs(max_x-min_x)
610+
# ywidth_old = abs(max_y-min_y)
611+
xwidth = abs(max_x-min_x)+bound_centroid_x
612+
xwidth -= xwidth%grid_spacing_x
613+
ywidth = abs(max_y-min_y)+bound_centroid_y
614+
ywidth -= ywidth%grid_spacing_y
615+
616+
# ####################
617+
# x=bound_centroid_x-xwidth#-xwidth_old-bound_centroid_x
618+
# y=bound_centroid_y-ywidth#-ywidth_old-bound_centroid_y
619+
# local_x_old, local_y_old = np.dot(transformation_matrix, [x, y])
620+
# # Add grid translation offsets to local coordinates
621+
# local_x_old += grid_trans_x
622+
# local_y_old += grid_trans_y
623+
# old = np.array([local_x_old,local_y_old])
624+
# x=-xwidth
625+
# y=-ywidth
626+
# local_x, local_y = np.dot(transformation_matrix, [x, y])
627+
# new = np.array([local_x,local_y])
628+
# new_trans = old-new
629+
# ##############################
630+
591631
# LOCAL COORDINATE SYSTEM WITH (0,0) LEASE AREA CENTROID
592632
# Therefore, +/- self.boundary_centroid_y/x cover the entire area
593633
# Loop through y values within the boundary_centroid_y range with grid_spacing_y increments
594634
column_count = 0
595635
rotations = []
596636
grid_position =[]
597-
for y in np.arange(-bound_centroid_y-ywidth, bound_centroid_y+ywidth, grid_spacing_y):
637+
for y in np.arange(-ywidth, ywidth, grid_spacing_y):
638+
# for y in np.arange(-bound_centroid_y-ywidth, bound_centroid_y+ywidth, grid_spacing_y):
598639
column_count += 1
599640
row_count = 0
600641
# Loop through x values within the boundary_centroid_x range with grid_spacing_x increments
601-
for x in np.arange(-bound_centroid_x-xwidth, bound_centroid_x+xwidth, grid_spacing_x):
602-
642+
for x in np.arange(-xwidth, xwidth, grid_spacing_x):
643+
# for x in np.arange(-bound_centroid_x-xwidth, bound_centroid_x+xwidth, grid_spacing_x):
603644
row_count += 1
604645
# Apply transformation matrix to x, y coordinates
605646
local_x, local_y = np.dot(transformation_matrix, [x, y])
@@ -616,7 +657,6 @@ def generateGridPoints(self, Xu, trans_mode, boundary_index=-1):
616657
#store column, row for each turbine
617658
grid_position.append([column_count, row_count])
618659

619-
620660
# remove points that are not in boundaries
621661
bound_lines = boundary.boundary # get boundary lines for shapely analysis
622662
out_lines = [bound_lines]
@@ -869,7 +909,7 @@ def updateLayout(self, X, level=0, refresh=False):
869909
buffer_group_rout = []
870910

871911
for j in range(self.ms_na):
872-
im = 3*i + j # global index of mooring/anchor
912+
im = self.n_moor*i + j # global index of mooring/anchor
873913

874914
moor_bf_start = get_point_along_line(self.turb_coords[i,:], self.mooringList[im].rA[:2], self.turb_minrad)
875915
# Buffer zone mooring line
@@ -1470,6 +1510,12 @@ def getLCOE2(self):
14701510
self.getAEP()
14711511
self.getCost()
14721512
self.lcoe = ((self.cost_total+capex)*fcr+ opex)/self.aep*1e6 # [$ / MWh]
1513+
1514+
self.aep_evol.append(self.aep)
1515+
costs = self.getArrayCost()
1516+
self.moor_evol.append(costs['moor cost'])
1517+
self.cab_evol.append(costs['cable cost'])
1518+
self.lcoe_evol.append(self.lcoe)
14731519

14741520
#return self.cost
14751521

@@ -1534,7 +1580,7 @@ def calcDerivatives(self):
15341580
>>> PLACEHOLDER <<<
15351581
'''
15361582

1537-
nDOF = 3*self.nt
1583+
nDOF = self.n_moor*self.nt
15381584
'''
15391585
# Perturb each DOF in turn and compute AEP results
15401586
J_AEP = np.zeros([nt,nt])
@@ -1666,9 +1712,9 @@ def plotLayout(self, ax=None, bare=False, save=False):
16661712

16671713
# ----- mooring lines
16681714
for i in range(self.nt):
1669-
for j in range(3):
1670-
plt.plot([self.turb_coords[i,0], self.mooringList[3*i+j].rA[0]],
1671-
[self.turb_coords[i,1], self.mooringList[3*i+j].rA[1]], 'k', lw=0.5)
1715+
for j in range(self.n_moor):
1716+
plt.plot([self.turb_coords[i,0], self.mooringList[self.n_moor*i+j].rA[0]],
1717+
[self.turb_coords[i,1], self.mooringList[self.n_moor*i+j].rA[1]], 'k', lw=0.5)
16721718

16731719
# plt.plot([self.turb_coords[i,0], self.anchor_coords[3*i+j,0]],
16741720
# [self.turb_coords[i,1], self.anchor_coords[3*i+j,1]], 'k', lw=0.5)
@@ -1719,48 +1765,49 @@ def plotLayout(self, ax=None, bare=False, save=False):
17191765
# ----- Cables
17201766
# Create a colormap and a legend entry for each unique cable section
17211767
# Find unique values
1722-
unique_cables = np.unique([x['conductor_area'] for x in self.iac_dic]) #(self.iac_dic['minimum_con'].values)
1723-
colors = plt.cm.viridis(np.linspace(0, 1, len(unique_cables))) # Create a colormap based on the number of unique sections
1724-
section_to_color = {sec: col for sec, col in zip(unique_cables, colors)}
1725-
1726-
1727-
# ----- Cables in Cluster
1728-
# Cable array
1729-
iac_array = self.iac_dic
1730-
count = 0
1731-
# Loop over each cluster
1732-
for ic in range(self.n_cluster*self.noss):
1733-
# Plot vertices
1734-
#plt.scatter(self.cluster_arrays[ic][:, 0], self.cluster_arrays[ic][:, 1], color='red', label='Turbines')
1735-
1736-
# Annotate each point with its index
1737-
#for i, point in enumerate(self.cluster_arrays[ic]):
1738-
#plt.annotate(str(i), (point[0], point[1]), textcoords="offset points", xytext=(0, 10), ha='center')
1739-
1740-
# Get index of cluster
1741-
#ind_cluster = np.where(iac_array[:, 0] == 0)[0]
1742-
# Loop over edges / cable ids
1743-
len_cluster = len(np.where(np.array([x['cluster_id']==ic for x in iac_array]))[0])
1744-
for i in range(len_cluster):
1745-
ix = np.where((np.array([x['cluster_id']== ic for x in iac_array])) & (np.array([y['cable_id']== count for y in iac_array]) ))[0]
1746-
if len(ix)<1:
1747-
breakpoint()
1748-
ind = ix[0]
1749-
#ind = np.where((iac_array[:, 0] == ic) & (iac_array[:, 2] == i))[0][0]
1750-
# Plot edge
1751-
#edge = self.iac_edges[ic][i]
1752-
start = iac_array[ind]['coordinates'][0]#self.cluster_arrays[ic][edge[0]]
1753-
end = iac_array[ind]['coordinates'][1]
1754-
# Cable selection
1755-
color = section_to_color[iac_array[ind]['conductor_area']]
1756-
ax.plot([start[0], end[0]], [start[1], end[1]], color=color, label=f'Section {int(iac_array[ind]["conductor_area"])} mm²' if int(iac_array[ind]["conductor_area"]) not in plt.gca().get_legend_handles_labels()[1] else "")
1757-
#plt.text((start[0] + end[0]) / 2, (start[1] + end[1]) / 2, str(i), fontsize=9, color='black')
1758-
# for sid in oss_ids:
1759-
# if iac_array[ix]['turbineA_glob_id'] == sid or iac_array[ix]['turbineB_glob_id'] == sid:
1760-
# iac_array_oss.append(iac_array[ix])
1761-
# iac_oss_id.append(sid)
1768+
if hasattr(self,'iac_dic'):
1769+
unique_cables = np.unique([x['conductor_area'] for x in self.iac_dic]) #(self.iac_dic['minimum_con'].values)
1770+
colors = plt.cm.viridis(np.linspace(0, 1, len(unique_cables))) # Create a colormap based on the number of unique sections
1771+
section_to_color = {sec: col for sec, col in zip(unique_cables, colors)}
1772+
1773+
1774+
# ----- Cables in Cluster
1775+
# Cable array
1776+
iac_array = self.iac_dic
1777+
count = 0
1778+
# Loop over each cluster
1779+
for ic in range(self.n_cluster*self.noss):
1780+
# Plot vertices
1781+
#plt.scatter(self.cluster_arrays[ic][:, 0], self.cluster_arrays[ic][:, 1], color='red', label='Turbines')
1782+
1783+
# Annotate each point with its index
1784+
#for i, point in enumerate(self.cluster_arrays[ic]):
1785+
#plt.annotate(str(i), (point[0], point[1]), textcoords="offset points", xytext=(0, 10), ha='center')
17621786

1763-
count += 1
1787+
# Get index of cluster
1788+
#ind_cluster = np.where(iac_array[:, 0] == 0)[0]
1789+
# Loop over edges / cable ids
1790+
len_cluster = len(np.where(np.array([x['cluster_id']==ic for x in iac_array]))[0])
1791+
for i in range(len_cluster):
1792+
ix = np.where((np.array([x['cluster_id']== ic for x in iac_array])) & (np.array([y['cable_id']== count for y in iac_array]) ))[0]
1793+
if len(ix)<1:
1794+
breakpoint()
1795+
ind = ix[0]
1796+
#ind = np.where((iac_array[:, 0] == ic) & (iac_array[:, 2] == i))[0][0]
1797+
# Plot edge
1798+
#edge = self.iac_edges[ic][i]
1799+
start = iac_array[ind]['coordinates'][0]#self.cluster_arrays[ic][edge[0]]
1800+
end = iac_array[ind]['coordinates'][1]
1801+
# Cable selection
1802+
color = section_to_color[iac_array[ind]['conductor_area']]
1803+
ax.plot([start[0], end[0]], [start[1], end[1]], color=color, label=f'Section {int(iac_array[ind]["conductor_area"])} mm²' if int(iac_array[ind]["conductor_area"]) not in plt.gca().get_legend_handles_labels()[1] else "")
1804+
#plt.text((start[0] + end[0]) / 2, (start[1] + end[1]) / 2, str(i), fontsize=9, color='black')
1805+
# for sid in oss_ids:
1806+
# if iac_array[ix]['turbineA_glob_id'] == sid or iac_array[ix]['turbineB_glob_id'] == sid:
1807+
# iac_array_oss.append(iac_array[ix])
1808+
# iac_oss_id.append(sid)
1809+
1810+
count += 1
17641811

17651812
# Plot gate as a diamond marker
17661813
#plt.scatter(self.gate_coords[ic][0], self.gate_coords[ic][1], marker='D', color='green', label='Gate')
@@ -2245,8 +2292,10 @@ def get_point_along_line(start, end, diste):
22452292

22462293
# Wind rose
22472294
from floris import WindRose
2295+
import os
2296+
direct = '../scripts'
22482297
wind_rose = WindRose.read_csv_long(
2249-
'humboldt_rose.csv', wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06)
2298+
os.path.join(direct,'humboldt_rose.csv'), wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06)
22502299

22512300

22522301
# ----- LEASE AREA BOUNDARIES -----
@@ -2263,10 +2312,15 @@ def get_point_along_line(start, end, diste):
22632312

22642313
# Make a sample Subsystem to hold the mooring design (used for initialization)
22652314
print("Making subsystem")
2266-
newFile = '..\scripts\input_files\GoMxOntology.yaml'
2267-
project = Project(file=newFile,raft=0)
2268-
project.getMoorPyArray()
2269-
ss = deepcopy(project.ms.lineList[0])
2315+
newFile = os.path.join(direct,'input_files','maine_moordyn.dat')
2316+
#project = Project(file=newFile,raft=0)
2317+
#project.getMoorPyArray()
2318+
ms = mp.System(file=newFile,depth=200)
2319+
ms.initialize()
2320+
ms.solveEquilibrium()
2321+
from moorpy.helpers import lines2ss
2322+
ms = lines2ss(ms)
2323+
ss = deepcopy(ms.lineList[0])
22702324

22712325
# ----- Set optimization mode
22722326
opt_mode = 'CAPEX'
@@ -2390,7 +2444,7 @@ def get_point_along_line(start, end, diste):
23902444
anchor_settings = {}
23912445
anchor_settings['anchor_design'] = {'L':20,'D':4.5,'zlug':13.3} # geometry of anchor
23922446
anchor_settings['anchor_type'] = 'suction' # anchor type
2393-
anchor_settings['anchor_resize'] = True # bool to resize the anchor or not
2447+
anchor_settings['anchor_resize'] = False # bool to resize the anchor or not
23942448
anchor_settings['fix_zlug'] = False # bool to keep zlug the same when resizing anchor
23952449
anchor_settings['FSdiff_max'] = {'Ha':.2,'Va':.2} # max allowed difference between FS and minimum FS
23962450
anchor_settings['FS_min'] = {'Ha':2,'Va':2} # horizontal and vertical minimum safety factors

0 commit comments

Comments
 (0)