Skip to content

Commit 6ca9322

Browse files
authored
Merge pull request #16 from Yuksel-Rudy/main
Ability to reorient the array to allow wind direction to align with global x-axis.
2 parents 3e7a288 + f80b9c0 commit 6ca9322

File tree

3 files changed

+166
-7
lines changed

3 files changed

+166
-7
lines changed

famodel/platform/platform.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ def setPosition(self, r, heading=None, degrees=False,project=None):
107107

108108
# Heading of the mooring line
109109
heading_i = self.mooring_headings[count] + self.phi
110-
# Reposition the whole Mooring
111-
self.attachments[att]['obj'].reposition(r_center=self.r, heading=heading_i,project=project)
110+
# Reposition the whole Mooring if it is an anchored line
111+
if not self.attachments[att]['obj'].shared:
112+
self.attachments[att]['obj'].reposition(r_center=self.r, heading=heading_i,project=project)
112113

113114
count += 1
114115

famodel/project.py

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,7 +2765,9 @@ def getRAFT(self,RAFTDict,pristine=1):
27652765

27662766
if 'ID' in RAFTDict['array']['keys']:
27672767
IDindex = np.where(np.array(RAFTDict['array']['keys'])=='ID')[0][0]
2768+
IDdata = [row[IDindex] for row in RAFTDict['array']['data']]
27682769
RAFTDict['array']['keys'].pop(IDindex) # remove key for ID because this doesn't exist in RAFT array table
2770+
reinsert = True
27692771
if 'topsideID' in RAFTDict['array']['keys']:
27702772
ts_loc = RAFTDict['array']['keys'].index('topsideID')
27712773
RAFTDict['array']['keys'][ts_loc] = 'turbineID'
@@ -2789,7 +2791,8 @@ def getRAFT(self,RAFTDict,pristine=1):
27892791
if turb[tsIDindex] > ti:
27902792
turb[tsIDindex] -= 1
27912793

2792-
2794+
2795+
27932796
# create empty mooring dictionary
27942797
RAFTDict['mooring'] = {}
27952798
#RAFTDict['mooring']['currentMod']
@@ -2817,6 +2820,12 @@ def getRAFT(self,RAFTDict,pristine=1):
28172820
# connect RAFT fowt to the correct moorpy body
28182821
for i in range(0,len(self.platformList)): # do not include substations (these are made last)
28192822
self.array.fowtList[i].body = self.ms.bodyList[i]
2823+
2824+
# Reinsert 'ID' key and data back into RAFTDict
2825+
if reinsert:
2826+
RAFTDict['array']['keys'].insert(IDindex, 'ID') # Reinsert 'ID' key at its original position
2827+
for i, row in enumerate(RAFTDict['array']['data']):
2828+
row.insert(IDindex, IDdata[i]) # Reinsert 'ID' data into each row
28202829
else:
28212830
raise Exception('Platform(s) must be specified in YAML file')
28222831

@@ -4118,7 +4127,7 @@ def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9):
41184127
yaw_init = np.zeros((1, len(self.platformList.items())))
41194128
for _, pf in self.platformList.items():
41204129
if pf.entity=='FOWT':
4121-
x, y, z = pf.body.r6[0], pf.body.r6[1], pf.body.r6[2]
4130+
x, y, z = pf.r[0], pf.r[1], pf.r[2]
41224131
phi_deg = np.degrees(pf.phi) # float((90 - np.degrees(pf.phi)) % 360) # Converting FAD's rotational convention (0deg N, +ve CW) into FF's rotational convention (0deg E, +ve CCW)
41234132
phi_deg = (phi_deg + 180) % 360 - 180 # Shift range to -180 to 180
41244133
for att in pf.attachments.values():
@@ -4138,7 +4147,7 @@ def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9):
41384147

41394148
return wts, yaw_init
41404149

4141-
def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=True, removeBody=True, MDoptionsDict={}):
4150+
def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=True, removeBody=True, MDoptionsDict={}, bathymetryFile=None):
41424151
'''
41434152
Function to create FFarm-compatible MoorDyn input file:
41444153
@@ -4209,7 +4218,22 @@ def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=Tr
42094218

42104219
with open(filename, 'w') as f:
42114220
f.writelines(newLines)
4212-
4221+
4222+
if bathymetryFile:
4223+
with open(filename, 'r') as f:
4224+
lines = f.readlines()
4225+
4226+
newLines = []
4227+
4228+
for i, line in enumerate(lines):
4229+
newLines.append(line)
4230+
if '---' in line and 'OPTIONS' in line.upper():
4231+
newLines.append(f" {bathymetryFile} Seafloor File\n")
4232+
4233+
4234+
with open(filename, 'w') as f:
4235+
f.writelines(newLines)
4236+
42134237
def resetArrayCenter(self, FOWTOnly=True):
42144238
'''
42154239
Function to reset array center such that the farm origin is the mid-point between all FOWT platforms:
@@ -4251,7 +4275,116 @@ def resetArrayCenter(self, FOWTOnly=True):
42514275

42524276
if 'platforms' in self.RAFTDict or 'platform' in self.RAFTDict:
42534277
self.getRAFT(self.RAFTDict,pristine=1)
4278+
self.getMoorPyArray()
4279+
4280+
def reorientArray(self, windHeading=None, degrees=False):
4281+
'''
4282+
Reorients the array based on a given wind heading. The array will be reoriented such that wind faces East (the zero in FFarm).
4283+
Useful to allign the array with the wind direction.
4284+
4285+
Parameters
4286+
----------
4287+
windHeading, float (optional)
4288+
The heading of the wind [deg or rad] depending on
4289+
degrees parameter. The heading is based on compass convention (North=0deg and +ve CW).
4290+
degrees : bool (optional)
4291+
Determines whether to use degree or radian for heading.
4292+
'''
4293+
4294+
from scipy.interpolate import griddata
4295+
4296+
# Check if windHeading is given
4297+
if windHeading is None:
4298+
raise ValueError("windHeading is not given. Please provide a valid wind heading.")
4299+
4300+
if degrees:
4301+
windHeading = np.radians(windHeading)
4302+
4303+
# reference wind heading (aligned with x-axis)
4304+
windHeadingRef = np.radians(270)
4305+
# Calculate the phi angle with which we will rotate the array
4306+
phi = ((np.pi/2 - windHeading) + np.pi) % (2*np.pi)
4307+
4308+
# Compute rotation matrix for faster computation
4309+
R = np.array([[np.cos(phi), np.sin(phi)],
4310+
[-np.sin(phi), np.cos(phi)]])
4311+
4312+
# Rotate the boundary
4313+
self.boundary = np.dot(R, self.boundary.T).T
4314+
4315+
# Rotate the bathymetry
4316+
X, Y = np.meshgrid(self.grid_x, self.grid_y)
4317+
coords_flat = np.stack([X.flatten(), Y.flatten()], axis=-1)
4318+
rotated_coords_flat = np.dot(R, coords_flat.T).T
4319+
rotated_X_flat, rotated_Y_flat = rotated_coords_flat[:, 0], rotated_coords_flat[:, 1]
4320+
X_rot = rotated_X_flat.reshape(X.shape)
4321+
Y_rot = rotated_Y_flat.reshape(Y.shape)
4322+
4323+
min_X = np.min(X_rot)
4324+
max_X = np.max(X_rot)
4325+
min_Y = np.min(Y_rot)
4326+
max_Y = np.max(Y_rot)
4327+
4328+
self.grid_x = np.arange(min_X, max_X + np.min(np.diff(self.grid_x)), np.min(np.diff(self.grid_x)))
4329+
self.grid_y = np.arange(min_Y, max_Y + np.min(np.diff(self.grid_y)), np.min(np.diff(self.grid_y)))
4330+
X_rot, Y_rot = np.meshgrid(self.grid_x, self.grid_y)
4331+
4332+
# Interpolate self.grid_depth onto the rotated grid
4333+
depth_flat = self.grid_depth.flatten() # Flatten the depth values
4334+
rotated_depth = griddata(
4335+
points=rotated_coords_flat, # Original grid points
4336+
values=depth_flat, # Original depth values
4337+
xi=(X_rot, Y_rot), # New rotated grid points
4338+
method='linear' # Interpolation method (can also use 'nearest' or 'cubic')
4339+
)
4340+
4341+
if np.isnan(rotated_depth).any():
4342+
nan_mask = np.isnan(rotated_depth)
4343+
nearest_depth = griddata(
4344+
points=rotated_coords_flat,
4345+
values=depth_flat,
4346+
xi=(X_rot, Y_rot),
4347+
method='nearest'
4348+
)
4349+
rotated_depth[nan_mask] = nearest_depth[nan_mask]
4350+
4351+
self.grid_depth = rotated_depth
4352+
4353+
# Rotate the platforms
4354+
for pf in self.platformList.values():
4355+
pf.r[:2] = np.dot(R, pf.r[:2].T).T
4356+
pf.phi = pf.phi + windHeadingRef - windHeading
4357+
4358+
4359+
# Rotate moorings
4360+
for moor in self.mooringList.values():
4361+
# if not moor.shared:
4362+
mooringHeading = np.radians(moor.heading) + windHeadingRef - windHeading
4363+
moor.reposition(heading=mooringHeading, project=self)
4364+
# else:
4365+
# mooringHeading = np.radians(obj.heading)
4366+
# moor.reposition(heading=mooringHeading)
4367+
4368+
4369+
4370+
# if isinstance(obj, Cable):
4371+
# cableHeading = [obj.subcomponents[0].headingA + phi, obj.subcomponents[-1].headingB + phi]
4372+
# obj.reposiiton(headings=cableHeading, project=self)
4373+
4374+
# Change RAFTDict if available.
4375+
if self.RAFTDict:
4376+
x_idx = self.RAFTDict['array']['keys'].index('x_location')
4377+
y_idx = self.RAFTDict['array']['keys'].index('y_location')
4378+
p_idx = self.RAFTDict['array']['keys'].index('heading_adjust')
4379+
for i, pf in enumerate(self.platformList.values()):
4380+
self.RAFTDict['array']['data'][i][x_idx] = pf.r[0]
4381+
self.RAFTDict['array']['data'][i][y_idx] = pf.r[1]
4382+
self.RAFTDict['array']['data'][i][p_idx] = np.degrees(pf.phi)
4383+
4384+
if 'platforms' in self.RAFTDict or 'platform' in self.RAFTDict:
4385+
self.getRAFT(self.RAFTDict,pristine=1)
42544386

4387+
self.getMoorPyArray()
42554388

42564389
def updateFailureProbability(self):
42574390
'''

famodel/seabed/seabed_tools.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,32 @@ def readBathymetryFile(filename, dtype=float):
4242

4343
return bathGrid_Xs, bathGrid_Ys, bathGrid
4444

45-
45+
def writeBathymetryFile(filename, grid_x, grid_y, grid_depth):
46+
"""
47+
Writes bathymetry data to a file in the format expected by readBathymetryFile.
48+
49+
Parameters:
50+
filename (str): The name of the file to write to.
51+
grid_x (list or np.ndarray): The X coordinates of the grid.
52+
grid_y (list or np.ndarray): The Y coordinates of the grid.
53+
grid_depth (np.ndarray): The bathymetry grid (depth values).
54+
"""
55+
with open(filename, 'w') as f:
56+
# Write a placeholder header
57+
f.write("Bathymetry Data File\n")
58+
59+
# Write the number of grid values in the x and y directions
60+
f.write(f"nGridX {len(grid_x)}\n")
61+
f.write(f"nGridY {len(grid_y)}\n")
62+
63+
# Write the X coordinates
64+
f.write(" ".join(map(str, grid_x)) + "\n")
65+
66+
# Write the Y coordinates and the bathymetry grid
67+
for i, y in enumerate(grid_y):
68+
row = [y] + list(grid_depth[i, :])
69+
f.write(" ".join(map(str, row)) + "\n")
70+
4671
def getSoilTypes(filename):
4772
'''function to read in a preliminary input text file format of soil type information'''
4873

0 commit comments

Comments
 (0)