Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 102 additions & 4 deletions openfast_toolbox/fastfarm/FASTFarmCaseCreation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import xarray as xr

from openfast_toolbox.io import FASTInputFile, FASTOutputFile, TurbSimFile, VTKFile
from openfast_toolbox.fastfarm import writeFastFarm, fastFarmTurbSimExtent, plotFastFarmSetup
from openfast_toolbox.fastfarm.fastfarm import writeFastFarm, fastFarmTurbSimExtent, plotFastFarmSetup
from openfast_toolbox.fastfarm.TurbSimCaseCreation import TSCaseCreation, writeTimeSeriesFile

def cosd(t): return np.cos(np.deg2rad(t))
Expand Down Expand Up @@ -271,7 +271,7 @@ def _checkInputs(self):

# Create case path is doesn't exist
if not os.path.exists(self.path):
os.makedirs(self.path)
os.makedirs(self.path, exist_ok=True)

# Check the wind turbine dict
if not isinstance(self.wts,dict):
Expand Down Expand Up @@ -585,7 +585,28 @@ def copyTurbineFilesForEachCase(self, writeFiles=True):

# Recover info about the current CondXX_*/CaseYY_*
Vhub_ = self.allCond.sel(cond=cond)['vhub'].values


# Check MoorDyn file and copy
if self.mDynfilepath != 'unused':
moordyn_file_src = self.mDynfilepath
moordyn_file_dst = os.path.join(currPath, self.mDynfilename)

# Read the MoorDyn template content
with open(moordyn_file_src, "r") as src:
moordyn_content = src.readlines()

# Rotate the mooring system if wind direction is specified
if self.inflow_deg != 0.0:
moordyn_content = self._rotateMooringSystem(moordyn_content, self.inflow_deg)

# Write the updated MoorDyn file
with open(moordyn_file_dst, "w") as dst:
dst.writelines(moordyn_content)

if writeFiles:
shutilcopy2_untilSuccessful(moordyn_file_src, moordyn_file_dst)
print(f"MoorDyn file rotated and written to {moordyn_file_dst}")

# Update parameters to be changed in the HydroDyn files
if self.HydroDynFile != 'unused':
self.HydroDynFile['WaveHs'] = self.bins.sel(wspd=Vhub_, method='nearest').WaveHs.values
Expand Down Expand Up @@ -835,6 +856,21 @@ def setTemplateFilename(self, templatePath=None, templateFiles=None):
'HDfilename': 'HDtemplate.dat',
'SrvDfilename': 'SrvDtemplate.T',
'ADfilename': 'ADtemplate.dat',
'EDfilename'
'ADskfilename'
'IWfilename'
'SubDfilename'
'BDfilepath'
'bladefilename'
'towerfilename'
'turbfilename'
'libdisconfilepath'
'controllerInputfilename'
'coeffTablefilename'
'turbsimLowfilepath'
'turbsimHighfilepath'
'FFfilename'
'mDynfilename'
# Add other files as needed...
}
"""
Expand All @@ -845,6 +881,8 @@ def setTemplateFilename(self, templatePath=None, templateFiles=None):
self.BDfilepath = self.bladefilename = self.towerfilename = self.turbfilename = "unused"
self.libdisconfilepath = self.controllerInputfilename = self.coeffTablefilename = "unused"
self.turbsimLowfilepath = self.turbsimHighfilepath = self.FFfilename = "unused"
# MoorDyn support
self.mDynfilename = "unused"

if templatePath is None:
print(f'--- WARNING: No template files given. Complete setup will not be possible')
Expand All @@ -865,7 +903,7 @@ def checkIfExists(f):
for key, filename in (templateFiles or {}).items():
if filename == 'unused':
continue

print(key, filename)
# Map the template file types to the specific checks
if key.endswith('filename'):
if key.startswith('ED'):
Expand Down Expand Up @@ -971,6 +1009,13 @@ def checkIfExists(f):
self.coeffTablefilepath = os.path.join(self.templatePath, filename)
checkIfExists(self.coeffTablefilepath)
self.coeffTablefilename = filename
# MoorDyn Support
elif key.startswith('mDyn'):
if not filename.endswith('.dat'):
raise ValueError(f'The MoorDyn filename should end in `.dat`.')
self.mDynfilepath = os.path.join(self.templatePath, filename)
checkIfExists(self.mDynfilepath)
self.mDynfilename = filename

elif key.startswith('turbsimLow'):
if not filename.endswith('.inp'):
Expand Down Expand Up @@ -1180,7 +1225,60 @@ def _create_all_cases(self):
self.allCases = ds.copy()
self.nCases = len(self.allCases['case'])

# helper method for rotating mooring systems
def _rotateMooringSystem(self, moordyn_content, inflow_deg):
"""
Rotate the mooring system based on the wind direction.
This assumes mooring nodes are specified in the template file.

:param moordyn_content: List of lines in the MoorDyn template file
:param inflow_deg: Wind direction angle in degrees
:return: List of updated lines for the MoorDyn file
"""
rotated_content = []
rotation_matrix = self._createRotationMatrix(inflow_deg)

for line in moordyn_content:
# Identify node lines with XYZ coordinates
if line.strip().startswith("Node"):
parts = line.split()
if len(parts) >= 5: # Ensure line has at least X, Y, Z, M, B
try:
# Extract original X, Y, Z coordinates
x, y, z = float(parts[1]), float(parts[2]), float(parts[3])

# Rotate coordinates
rotated_coords = np.dot(rotation_matrix, np.array([x, y, z]))
parts[1], parts[2], parts[3] = map(str, rotated_coords)

# Reconstruct the line with rotated coordinates
rotated_line = " ".join(parts) + "\n"
rotated_content.append(rotated_line)
continue
except ValueError:
pass # Skip lines that don't conform to expected format
rotated_content.append(line)

return rotated_content

# Helper method to create a 3D rotation matrix
def _createRotationMatrix(self, angle_deg):
"""
Create a 3D rotation matrix for a given angle in degrees about the Z-axis.

:param angle_deg: Angle in degrees
:return: 3x3 numpy array representing the rotation matrix
"""
angle_rad = np.radians(angle_deg)
cos_theta = np.cos(angle_rad)
sin_theta = np.sin(angle_rad)

# 3D rotation matrix about the Z-axis
return np.array([
[cos_theta, -sin_theta, 0],
[sin_theta, cos_theta, 0],
[0, 0, 1]
])

def _rotate_wts(self):
# Calculate the rotated positions of the turbines wrt the reference turbine
Expand Down
2 changes: 1 addition & 1 deletion openfast_toolbox/fastfarm/examples/Ex2_FFarmInputSetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import matplotlib.pyplot as plt
import pandas as pd
# Local packages
from openfast_toolbox.fastfarm import fastFarmTurbSimExtent, writeFastFarm, plotFastFarmSetup
from openfast_toolbox.fastfarm.fastfarm import fastFarmTurbSimExtent, writeFastFarm, plotFastFarmSetup
from openfast_toolbox.io.fast_input_file import FASTInputFile

MyDir=os.path.dirname(__file__)
Expand Down
3 changes: 2 additions & 1 deletion openfast_toolbox/fastfarm/examples/Ex3_FFarmCompleteSetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ def main():
'FFfilename' : 'Model_FFarm.fstf',
'controllerInputfilename' : 'DISCON.IN',
'libdisconfilepath' : '/full/path/to/controller/libdiscon.so',

# MoorDyn Support
'mDynfilename': 'MoorDyn.dat',
# TurbSim setups
'turbsimLowfilepath' : './SampleFiles/template_Low_InflowXX_SeedY.inp',
'turbsimHighfilepath' : './SampleFiles/template_HighT1_InflowXX_SeedY.inp'
Expand Down
115 changes: 115 additions & 0 deletions openfast_toolbox/fastfarm/tests/test_moordyn_support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import os
import unittest
from pathlib import Path
from openfast_toolbox.fastfarm.FASTFarmCaseCreation import FFCaseCreation

class TestMoorDynSupport(unittest.TestCase):
def setUp(self):
"""
Setup the testing environment.
"""
# Create a temporary directory for the test
self.test_dir = Path('test_moordyn_support')
self.test_dir.mkdir(exist_ok=True)

# Define MoorDyn template
self.moordyn_template = self.test_dir / "MoorDyn_template.dat"
self.moordyn_template.write_text(
"""Node X Y Z M B
0.0 0.0 -20.0 0.0 0.0
100.0 0.0 -20.0 0.0 0.0
0.0 100.0 -20.0 0.0 0.0
"""
)
# Initialize FFCaseCreation with minimal parameters
self.case = FFCaseCreation(
path=str(self.test_dir),
wts={
0: {
'x': 0.0,
'y': 0.0,
'z': 0.0,
'D': 240, # Rotor diameter
'zhub': 150, # Hub height
'cmax': 5, # Maximum blade chord (m)
'fmax': 10 / 6, # Maximum excitation frequency (Hz)
'Cmeander': 1.9 # Meandering constant (-)
}
},
tmax=600,
zbot=1.0,
vhub=[10.0],
shear=[0.2],
TIvalue=[10],
inflow_deg=[30.0], # Rotate MoorDyn file by 30 degrees
dt_high_les=0.6,
ds_high_les=10.0,
extent_high=1.2,
dt_low_les=3.0,
ds_low_les=20.0,
extent_low=[3, 8, 3, 3, 2],
ffbin="/Users/ombahiwal/Desktop/WS24/Courses_WS24/Simulation Software Engineering/contri/openfast/glue-codes/fast-farm/FAST.Farm",
mod_wake=1,
yaw_init=None,
nSeeds=1,
LESpath=None,
refTurb_rot=0,
verbose=1,
)

# def tearDown(self):
# """
# Cleanup after tests.
# """
# for file in self.test_dir.glob("*"):
# file.unlink()
# self.test_dir.rmdir()

def test_moordyn_file_copy_and_rotation(self):
"""
Test the copying and rotation of the MoorDyn file.
"""
# TODO: Test moordyn support.
"""case = self.case
# Set the MoorDyn template
case.setTemplateFilename(str(self.test_dir), templateFiles={
"mDynfilename": self.moordyn_template.name,
"EDfilename": ""
})

# Simulate case generation
case.copyTurbineFilesForEachCase()

# Verify MoorDyn file is created
output_file = self.test_dir / "case_0_inflow30_Seed0" / "MoorDyn.dat"
self.assertTrue(output_file.exists(), "MoorDyn file was not created")

# Check the MoorDyn file content for rotation
with open(output_file, "r") as f_out:
rotated_lines = f_out.readlines()

# Expected rotated values (30 degrees rotation)
import numpy as np
rotation_matrix = np.array([
[np.cos(np.radians(30)), -np.sin(np.radians(30)), 0],
[np.sin(np.radians(30)), np.cos(np.radians(30)), 0],
[0, 0, 1],
])
expected_coordinates = [
[0.0, 0.0, -20.0],
[100.0, 0.0, -20.0],
[0.0, 100.0, -20.0],
]
rotated_coordinates = [np.dot(rotation_matrix, np.array(coord)) for coord in expected_coordinates]

# Validate each node's position
for i, expected_coord in enumerate(rotated_coordinates):
parts = rotated_lines[i + 1].split()
x, y, z = map(float, parts[1:4])
self.assertAlmostEqual(x, expected_coord[0], places=4, msg=f"Node {i} X mismatch")
self.assertAlmostEqual(y, expected_coord[1], places=4, msg=f"Node {i} Y mismatch")
self.assertAlmostEqual(z, expected_coord[2], places=4, msg=f"Node {i} Z mismatch")
"""

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Node X Y Z M B
0.0 0.0 -20.0 0.0 0.0
100.0 0.0 -20.0 0.0 0.0
0.0 100.0 -20.0 0.0 0.0

2 changes: 1 addition & 1 deletion openfast_toolbox/fastfarm/tests/test_turbsimExtent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import numpy as np

from openfast_toolbox.fastfarm import *
from openfast_toolbox.fastfarm.fastfarm import *

MyDir=os.path.dirname(__file__)

Expand Down