From ff28548beb0182f28bef33d930fc5b7ac3dff27d Mon Sep 17 00:00:00 2001 From: Yuksel-Rudy Date: Wed, 19 Feb 2025 07:35:50 -0700 Subject: [PATCH 1/4] Adding capability to extract Farm-level Information for FFarm Integration: > 1 - A new method in project class called extractFarmInfo that generates a dictionary table of farm-level information needed to create FFarm cases to run FFarm simulations > 2 - Angles are adjusted based on variation in rotational convention between FAM and FFarm. > 3 - General input parameters are optional and default values are given --- famodel/project.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/famodel/project.py b/famodel/project.py index 404c0ae..432dd1f 100644 --- a/famodel/project.py +++ b/famodel/project.py @@ -4292,7 +4292,48 @@ def gothroughlist(dat): with open(file,'w') as f: yaml.dump(output,f) - + def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9): + ''' + Function to extract farm-level information required to create FAST.Farm case simulations. [Under developement]: + + Parameters + ---------- + cmax : float, optional + maximum blade chord (m) + fmax: maximum excitation frequency (Hz) + Cmeander: Meandering constant (-) + + Returns + ------- + wts : dict + General farm-level information needed for FAST.Farm from project class + yaw_init : list + initial yaw offset values (for not it's set as just the platform orientation adjusted for rotational convention variation between FAM and FF) + ''' + + + # ----------- Extract Wind Farm Data + wts = {} + i = 0 + yaw_init = np.zeros((1, len(self.platformList.items()))) + for _, pf in self.platformList.items(): + x, y, z = pf.body.r6[0], pf.body.r6[1], pf.body.r6[2] + 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) + phi = (phi + 180) % 360 - 180 # Shift range to -180 to 180 + for att in pf.attachments.values(): + if isinstance(att['obj'],Turbine): + D = 240 # att['obj'].D (assuming 15MW) + zhub = att['obj'].dd['hHub'] + + wts[i] = { + 'x': x, 'y': y, 'z': z, 'phi': phi, 'D': D, 'zhub': zhub, + 'cmax': cmax, 'fmax': fmax, 'Cmeander': Cmeander + } + yaw_init[0, i] = phi + i += 1 + + return wts, yaw_init + def updateFailureProbability(self): ''' Function to populate (or update) failure probability dictionaries in each object From 25e02e69a623ce9321e3ab8b85f931b093169059 Mon Sep 17 00:00:00 2001 From: Yuksel-Rudy Date: Thu, 27 Feb 2025 14:16:27 -0700 Subject: [PATCH 2/4] Features for FFarm Integration tool: 1) Extract wind farm information such as platform locations and orientations and so on. 2) outputs FFarm Compatible MD output with the orientation of the different platforms in place. --- famodel/project.py | 52 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/famodel/project.py b/famodel/project.py index 432dd1f..4806be7 100644 --- a/famodel/project.py +++ b/famodel/project.py @@ -4318,7 +4318,7 @@ def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9): yaw_init = np.zeros((1, len(self.platformList.items()))) for _, pf in self.platformList.items(): x, y, z = pf.body.r6[0], pf.body.r6[1], pf.body.r6[2] - 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) + phi = 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) phi = (phi + 180) % 360 - 180 # Shift range to -180 to 180 for att in pf.attachments.values(): if isinstance(att['obj'],Turbine): @@ -4329,11 +4329,59 @@ def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9): 'x': x, 'y': y, 'z': z, 'phi': phi, 'D': D, 'zhub': zhub, 'cmax': cmax, 'fmax': fmax, 'Cmeander': Cmeander } - yaw_init[0, i] = phi + yaw_init[0, i] = -phi i += 1 + # store farm-level wind turbine information + self.wts = wts + return wts, yaw_init + + def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=True): + ''' + Function to create FFarm-compatible MoorDyn input file: + + Parameters + ---------- + filename : str + Name of the MoorDyn output file (.dat) + unrotateTurbines: bool, optional + A flag to unrotate turbine (body) objects when passing it to MoorPy unload function [FFarm takes fairlead points in the local-unrotated reference frame] + renameBody: bool, optional + A flag to rename `Body` objects in the output MD file into `Turbine` to be compatible with FFarm. + + ''' + from moorpy.helpers import subsystem2Line + + # convert SS to lines + ms_temp = deepcopy(self.ms) # copy to avoid affecting self.ms + lineCount = len(ms_temp.lineList) + for _ in range(lineCount): + subsystem2Line(ms_temp, 0) + + # Unrotate turbines if needed + if unrotateTurbines: + if self.wts: + phiV = [wt['phi'] for wt in self.wts.values()] # [180, 0] # to unrotate the platforms when unloading MoorDyn + else: + raise ValueError("wts is empty. Please run project.extractFarmInfo first before extracting MoorDyn") + else: + phiV = None + + ms_temp.unload(fileName=filename, phiV=phiV) + # rename Body to Turbine if needed + if renameBody: + # Rename Body to Turbine: + with open(filename, 'r') as file: + filedata = file.read() + + filedata = filedata.replace('Body', 'Turbine') + with open(filename, 'w') as file: + file.write(filedata) + + file.close() + def updateFailureProbability(self): ''' Function to populate (or update) failure probability dictionaries in each object From c1f34e07779b576f6ae72cc0ad801d7409803269 Mon Sep 17 00:00:00 2001 From: Yuksel-Rudy Date: Fri, 28 Feb 2025 13:05:43 -0700 Subject: [PATCH 3/4] Updating FFarmCompatibleMDOutput method: 1) The method now removes the bodies listed by default in MoorDyn (So platform mass is not accounted twice, once in MD and once in FAST.Farm. 2) The method also can accept MD options dictionary for users to experiment with varying options which can help with dynamic relaxation investigation --- famodel/project.py | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/famodel/project.py b/famodel/project.py index 4806be7..36e6644 100644 --- a/famodel/project.py +++ b/famodel/project.py @@ -4337,7 +4337,7 @@ def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9): return wts, yaw_init - def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=True): + def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=True, removeBody=True, MDoptionsDict={}): ''' Function to create FFarm-compatible MoorDyn input file: @@ -4349,7 +4349,10 @@ def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=Tr A flag to unrotate turbine (body) objects when passing it to MoorPy unload function [FFarm takes fairlead points in the local-unrotated reference frame] renameBody: bool, optional A flag to rename `Body` objects in the output MD file into `Turbine` to be compatible with FFarm. - + removeBody: boo., optional + A flag to remove 'Body' objects in the Bodies list in the output MD file to be compatible with FFarm. + MDoptionsDict: dictionary, optional + MoorDyn Options. If not given, default options are considered. ''' from moorpy.helpers import subsystem2Line @@ -4368,19 +4371,44 @@ def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=Tr else: phiV = None - ms_temp.unload(fileName=filename, phiV=phiV) + ms_temp.unload(fileName=filename, phiV=phiV, MDoptionsDict=MDoptionsDict) # rename Body to Turbine if needed if renameBody: # Rename Body to Turbine: - with open(filename, 'r') as file: - filedata = file.read() + with open(filename, 'r') as f: + filedata = f.read() filedata = filedata.replace('Body', 'Turbine') - with open(filename, 'w') as file: - file.write(filedata) + with open(filename, 'w') as f: + f.write(filedata) + + f.close() + + if removeBody: + with open(filename, 'r') as f: + lines = f.readlines() + + newLines = [] + skipCount = 0 + + for i, line in enumerate(lines): + if '---' in line and ('BODIES' in line.upper() or 'BODY LIST' in line.upper() or 'BODY PROPERTIES' in line.upper()): + newLines.append(line) + newLines.append(next(iter(lines[i+1:]))) # Append 2 lines + newLines.append(next(iter(lines[i+2:]))) - file.close() + skipCount = 2 + len(self.platformList) # Skip the number of platforms and the already appended lines above + continue + + if skipCount > 0: + skipCount -= 1 + else: + newLines.append(line) + + with open(filename, 'w') as f: + f.writelines(newLines) + def updateFailureProbability(self): ''' From d232ef006997a5142056a1d29b419d03f8a59ad0 Mon Sep 17 00:00:00 2001 From: Yuksel-Rudy Date: Wed, 5 Mar 2025 13:28:00 -0700 Subject: [PATCH 4/4] changing the name from phi to phi_deg --- famodel/project.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/famodel/project.py b/famodel/project.py index 36e6644..bf70189 100644 --- a/famodel/project.py +++ b/famodel/project.py @@ -4318,24 +4318,24 @@ def extractFarmInfo(self, cmax=5, fmax=10/6, Cmeander=1.9): yaw_init = np.zeros((1, len(self.platformList.items()))) for _, pf in self.platformList.items(): x, y, z = pf.body.r6[0], pf.body.r6[1], pf.body.r6[2] - phi = 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) - phi = (phi + 180) % 360 - 180 # Shift range to -180 to 180 + 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) + phi_deg = (phi_deg + 180) % 360 - 180 # Shift range to -180 to 180 for att in pf.attachments.values(): if isinstance(att['obj'],Turbine): D = 240 # att['obj'].D (assuming 15MW) zhub = att['obj'].dd['hHub'] wts[i] = { - 'x': x, 'y': y, 'z': z, 'phi': phi, 'D': D, 'zhub': zhub, + 'x': x, 'y': y, 'z': z, 'phi_deg': phi_deg, 'D': D, 'zhub': zhub, 'cmax': cmax, 'fmax': fmax, 'Cmeander': Cmeander } - yaw_init[0, i] = -phi + yaw_init[0, i] = -phi_deg i += 1 # store farm-level wind turbine information self.wts = wts - return wts, yaw_init + return wts, yaw_init def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=True, removeBody=True, MDoptionsDict={}): ''' @@ -4365,7 +4365,7 @@ def FFarmCompatibleMDOutput(self, filename, unrotateTurbines=True, renameBody=Tr # Unrotate turbines if needed if unrotateTurbines: if self.wts: - phiV = [wt['phi'] for wt in self.wts.values()] # [180, 0] # to unrotate the platforms when unloading MoorDyn + phiV = [wt['phi_deg'] for wt in self.wts.values()] # to unrotate the platforms when unloading MoorDyn else: raise ValueError("wts is empty. Please run project.extractFarmInfo first before extracting MoorDyn") else: