From 38b2c6f836b10520f61cd1832e0aeb46b632fb6a Mon Sep 17 00:00:00 2001 From: RyanDavies19 Date: Wed, 26 Feb 2025 12:44:51 -0700 Subject: [PATCH 1/5] IO: Updated OpenFAST_io for MoorDyn input files --- openfast_io/openfast_io/FAST_reader.py | 174 ++++++++++++++++++++----- openfast_io/openfast_io/FAST_writer.py | 85 ++++++++---- 2 files changed, 200 insertions(+), 59 deletions(-) diff --git a/openfast_io/openfast_io/FAST_reader.py b/openfast_io/openfast_io/FAST_reader.py index 8bd0d3438e..ae3c5feb15 100644 --- a/openfast_io/openfast_io/FAST_reader.py +++ b/openfast_io/openfast_io/FAST_reader.py @@ -31,6 +31,22 @@ def readline_filterComments(f): read = False return line +def readline_ignoreComments(f, char = '#'): # see line 64 in NWTC_IO.f90 + """ + returns line before comment character + + Args: + f: file handle + char: comment character + + Returns: + line: content of next line in the file before comment character + """ + + line = f.readline().strip().split(char) + + return line[0] + def read_array(f,len,split_val=None,array_type=str): """ Read an array of values from a line in a file @@ -2903,25 +2919,48 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Diam'] = [] self.fst_vt['MoorDyn']['MassDen'] = [] self.fst_vt['MoorDyn']['EA'] = [] + self.fst_vt['MoorDyn']['NonLinearEA'] = [] self.fst_vt['MoorDyn']['BA_zeta'] = [] self.fst_vt['MoorDyn']['EI'] = [] self.fst_vt['MoorDyn']['Cd'] = [] self.fst_vt['MoorDyn']['Ca'] = [] self.fst_vt['MoorDyn']['CdAx'] = [] self.fst_vt['MoorDyn']['CaAx'] = [] - data_line = f.readline().strip().split() - while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same + self.fst_vt['MoorDyn']['Cl'] = [] + self.fst_vt['MoorDyn']['dF'] = [] + self.fst_vt['MoorDyn']['cF'] = [] + data_line = readline_filterComments(f).split() + while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same. self.fst_vt['MoorDyn']['Name'].append(str(data_line[0])) self.fst_vt['MoorDyn']['Diam'].append(float(data_line[1])) self.fst_vt['MoorDyn']['MassDen'].append(float(data_line[2])) - self.fst_vt['MoorDyn']['EA'].append(float(data_line[3])) - self.fst_vt['MoorDyn']['BA_zeta'].append(float(data_line[4])) + self.fst_vt['MoorDyn']['EA'].append([float_read(dl) for dl in data_line[3].split('|')]) + self.fst_vt['MoorDyn']['BA_zeta'].append([float(dl) for dl in data_line[4].split('|')]) self.fst_vt['MoorDyn']['EI'].append(float(data_line[5])) self.fst_vt['MoorDyn']['Cd'].append(float(data_line[6])) self.fst_vt['MoorDyn']['Ca'].append(float(data_line[7])) self.fst_vt['MoorDyn']['CdAx'].append(float(data_line[8])) self.fst_vt['MoorDyn']['CaAx'].append(float(data_line[9])) - data_line = f.readline().strip().split() + if len(data_line) == 10: + self.fst_vt['MoorDyn']['Cl'].append(None) + self.fst_vt['MoorDyn']['dF'].append(None) + self.fst_vt['MoorDyn']['cF'].append(None) + elif (len(data_line) == 11): + self.fst_vt['MoorDyn']['Cl'].append(float(data_line[10])) + self.fst_vt['MoorDyn']['dF'].append(None) + self.fst_vt['MoorDyn']['cF'].append(None) + elif (len(data_line) == 13): + self.fst_vt['MoorDyn']['Cl'].append(float(data_line[10])) + self.fst_vt['MoorDyn']['dF'].append(float(data_line[11])) + self.fst_vt['MoorDyn']['cF'].append(float(data_line[12])) + + if type(self.fst_vt['MoorDyn']['EA'][0]) is str: + EA_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['EA'][0])) + self.fst_vt['MoorDyn']['NonLinearEA'].append(self.read_NonLinearEA(EA_file)) + else: + self.fst_vt['MoorDyn']['NonLinearEA'].append(None) # Empty to keep track of which non-linear EA files go with what line + + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join elif 'rodtypes' in data_line or 'roddictionary' in data_line: @@ -2935,7 +2974,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Rod_CdEnd'] = [] self.fst_vt['MoorDyn']['Rod_CaEnd'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['MoorDyn']['Rod_Name'].append(data_line[0]) self.fst_vt['MoorDyn']['Rod_Diam'].append(float(data_line[1])) @@ -2944,7 +2983,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Rod_Ca'].append(float(data_line[4])) self.fst_vt['MoorDyn']['Rod_CdEnd'].append(float(data_line[5])) self.fst_vt['MoorDyn']['Rod_CaEnd'].append(float(data_line[6])) - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly @@ -2967,7 +3006,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Body_CdA'] = [] self.fst_vt['MoorDyn']['Body_Ca'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['MoorDyn']['Body_ID'].append(int(data_line[0])) self.fst_vt['MoorDyn']['Body_Attachment'].append(data_line[1]) @@ -2983,7 +3022,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Body_Volume'].append(float(data_line[11])) self.fst_vt['MoorDyn']['Body_CdA'].append([float(dl) for dl in data_line[12].split('|')]) self.fst_vt['MoorDyn']['Body_Ca'].append([float(dl) for dl in data_line[13].split('|')]) - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly @@ -3002,7 +3041,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Rod_NumSegs'] = [] self.fst_vt['MoorDyn']['RodOutputs'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['MoorDyn']['Rod_ID'].append(data_line[0]) self.fst_vt['MoorDyn']['Rod_Type'].append(data_line[1]) @@ -3015,7 +3054,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Zb'].append(float(data_line[8])) self.fst_vt['MoorDyn']['Rod_NumSegs'].append(int(data_line[9])) self.fst_vt['MoorDyn']['RodOutputs'].append(data_line[10]) - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly elif 'points' in data_line or 'connectionproperties' in data_line or \ @@ -3033,7 +3072,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['V'] = [] self.fst_vt['MoorDyn']['CdA'] = [] self.fst_vt['MoorDyn']['CA'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['MoorDyn']['Point_ID'].append(int(data_line[0])) self.fst_vt['MoorDyn']['Attachment'].append(str(data_line[1])) @@ -3044,7 +3083,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['V'].append(float(data_line[6])) self.fst_vt['MoorDyn']['CdA'].append(float(data_line[7])) self.fst_vt['MoorDyn']['CA'].append(float(data_line[8])) - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly elif 'lines' in data_line or 'lineproperties' in data_line or 'linelist' in data_line: @@ -3059,7 +3098,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['UnstrLen'] = [] self.fst_vt['MoorDyn']['NumSegs'] = [] self.fst_vt['MoorDyn']['Outputs'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['MoorDyn']['Line_ID'].append(int(data_line[0])) self.fst_vt['MoorDyn']['LineType'].append(str(data_line[1])) @@ -3068,8 +3107,27 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['UnstrLen'].append(float(data_line[4])) self.fst_vt['MoorDyn']['NumSegs'].append(int(data_line[5])) self.fst_vt['MoorDyn']['Outputs'].append(str(data_line[6])) - data_line = f.readline().strip().split() - data_line = ''.join(data_line) # re-join for reading next section uniformly + data_line = readline_filterComments(f).split() + data_line = ''.join(data_line) # re-join for reading next section uniformly + + elif 'failure' in data_line.lower(): + f.readline() + f.readline() + + self.fst_vt['MoorDyn']['Failure_ID'] = [] + self.fst_vt['MoorDyn']['Failure_Point'] = [] + self.fst_vt['MoorDyn']['Failure_Line(s)'] = [] + self.fst_vt['MoorDyn']['FailTime'] = [] + self.fst_vt['MoorDyn']['FailTen'] = [] + data_line = readline_filterComments(f).split() + while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same + self.fst_vt['MoorDyn']['Failure_ID'].append(int(data_line[0])) + self.fst_vt['MoorDyn']['Failure_Point'].append(data_line[1]) + self.fst_vt['MoorDyn']['Failure_Line(s)'].append([int(dl) for dl in data_line[2].split(',')]) + self.fst_vt['MoorDyn']['FailTime'].append(float(data_line[3])) + self.fst_vt['MoorDyn']['FailTen'].append(float(data_line[4])) + data_line = readline_filterComments(f).split() + data_line = ''.join(data_line) # re-join for reading next section uniformly elif 'control' in data_line.lower(): f.readline() @@ -3079,7 +3137,7 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['ChannelID'] = [] self.fst_vt['MoorDyn']['Lines_Control'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['MoorDyn']['ChannelID'].append(int(data_line[0])) # Line(s) is a list of mooring lines, spaces are allowed between commas @@ -3093,31 +3151,58 @@ def read_MoorDyn(self, moordyn_file): control_lines.remove('') self.fst_vt['MoorDyn']['Lines_Control'].append(control_lines) - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly + elif 'external' in data_line.lower(): + f.readline() + f.readline() + + self.fst_vt['MoorDyn']['External_ID'] = [] + self.fst_vt['MoorDyn']['Object'] = [] + self.fst_vt['MoorDyn']['Fext'] = [] + self.fst_vt['MoorDyn']['Blin'] = [] + self.fst_vt['MoorDyn']['Bquad'] = [] + self.fst_vt['MoorDyn']['CSys'] = [] + data_line = readline_filterComments(f).split() + while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same + self.fst_vt['MoorDyn']['External_ID'].append(int(data_line[0])) + self.fst_vt['MoorDyn']['Object'].append(data_line[1]) + self.fst_vt['MoorDyn']['Fext'].append([float(dl) for dl in data_line[2].split('|')]) + self.fst_vt['MoorDyn']['Blin'].append([float(dl) for dl in data_line[3].split('|')]) + self.fst_vt['MoorDyn']['Bquad'].append([float(dl) for dl in data_line[4].split('|')]) + self.fst_vt['MoorDyn']['CSys'].append(data_line[5]) + data_line = readline_filterComments(f).split() + data_line = ''.join(data_line) # re-join for reading next section uniformly elif 'options' in data_line: # MoorDyn lets options be written in any order # Solver options - self.fst_vt['MoorDyn']['options'] = [] # keep list of MoorDyn options - - string_options = ['WaterKin'] + self.fst_vt['MoorDyn']['option_values'] = [] + self.fst_vt['MoorDyn']['option_names'] = [] # keep list of MoorDyn options + self.fst_vt['MoorDyn']['option_descriptions'] = [] - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same - raw_value = data_line[0] + option_value = data_line[0] option_name = data_line[1] - - self.fst_vt['MoorDyn']['options'].append(option_name) - if option_name in string_options: - self.fst_vt['MoorDyn'][option_name] = raw_value.strip('"') + if len(data_line) > 2: + option_description = ' '.join(data_line[2:]) else: - self.fst_vt['MoorDyn'][option_name] = float(raw_value) + option_description = '' + + if option_name.upper() == 'WATERKIN': + self.fst_vt['MoorDyn']['WaterKin'] = option_value.strip('"') + WaterKin_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['WaterKin'])) + self.read_WaterKin(WaterKin_file) + + self.fst_vt['MoorDyn']['option_values'] = float_read(option_value.strip('"')) # some options values can be strings or floats + self.fst_vt['MoorDyn']['option_names'].append(option_name) + self.fst_vt['MoorDyn']['option_descriptions'].append(option_description) - data_line = f.readline().strip().split() + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly elif 'outputs' in data_line: @@ -3127,12 +3212,11 @@ def read_MoorDyn(self, moordyn_file): f.close() break - if 'WaterKin' in self.fst_vt['MoorDyn']['options']: - WaterKin_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['WaterKin'])) - self.read_WaterKin(WaterKin_file) - def read_WaterKin(self,WaterKin_file): - print('here') + + self.fst_vt['WaterKin']['z-depth'] = [] + self.fst_vt['WaterKin']['x-current'] = [] + self.fst_vt['WaterKin']['x-current'] = [] f = open(WaterKin_file) f.readline() @@ -3152,7 +3236,29 @@ def read_WaterKin(self,WaterKin_file): self.fst_vt['WaterKin']['Z_Grid'] = read_array(f,None,split_val='-',array_type=float) f.readline() self.fst_vt['WaterKin']['CurrentMod'] = int_read(f.readline().split()[0]) + f.readline() + f.readline() + data_line = readline_filterComments(f).split() + while data_line: + self.fst_vt['WaterKin']['z-depth'].append(float(data_line[0])) + self.fst_vt['WaterKin']['x-current'].append(float(data_line[1])) + self.fst_vt['WaterKin']['y-current'].append(float(data_line[2])) + f.close() + + def read_NonLinearEA(self,Stiffness_file): # read and return the nonlinear line stiffness lookup table for a given line + + f = open(Stiffness_file) + f.readline() + f.readline() + f.readline() + NonLinearEA = {"Strain" : [], "Tension" : []} + data_line = readline_filterComments(f).split() + while data_line: + NonLinearEA['Strain'].append([float(data_line[0])]) + NonLinearEA['Tension'].append([float(data_line[1])]) + data_line = readline_filterComments(f).split() f.close() + return NonLinearEA def execute(self): diff --git a/openfast_io/openfast_io/FAST_writer.py b/openfast_io/openfast_io/FAST_writer.py index d142ff9361..da2b8f8b46 100644 --- a/openfast_io/openfast_io/FAST_writer.py +++ b/openfast_io/openfast_io/FAST_writer.py @@ -236,7 +236,7 @@ def execute(self): self.write_MAP() elif self.fst_vt['Fst']['CompMooring'] == 3: self.write_MoorDyn() - if 'options' in self.fst_vt['MoorDyn'] and 'WaterKin' in self.fst_vt['MoorDyn']['options']: + if 'option_names' in self.fst_vt['MoorDyn'] and 'WATERKIN' in self.fst_vt['MoorDyn']['option_names'].upper(): self.write_WaterKin(os.path.join(self.FAST_runDirectory,self.fst_vt['MoorDyn']['WaterKin_file'])) if self.fst_vt['Fst']['CompElast'] == 2: @@ -2344,14 +2344,18 @@ def write_MoorDyn(self): ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Name'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Diam'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['MassDen'][i])) - ln.append('{:^11.4e}'.format(self.fst_vt['MoorDyn']['EA'][i])) - ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['BA_zeta'][i])) + ln.append('|'.join([float_default_out(a) for a in self.fst_vt['MoorDyn']['EA'][i]])) + ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['BA_zeta'][i]])) ln.append('{:^11.4e}'.format(self.fst_vt['MoorDyn']['EI'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Cd'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Ca'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['CdAx'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['CaAx'][i])) f.write(" ".join(ln) + '\n') + + if self.fst_vt['MoorDyn']['NonLinearEA'][i] != None: + self.write_NonLinearEA(self.fst_vt['MoorDyn']['EA'][i][0], self.fst_vt['MoorDyn']['Name'][i], self.fst_vt['MoorDyn']['NonLinearEA'][i]) + if 'Rod_Name' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Rod_Name']: f.write('----------------------- ROD TYPES ------------------------------------------\n') f.write(" ".join(['{:^11s}'.format(i) for i in ['TypeName', 'Diam', 'Mass/m', 'Cd', 'Ca', 'CdEnd', 'CaEnd']])+'\n') @@ -2439,6 +2443,19 @@ def write_MoorDyn(self): ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Outputs'][i])) f.write(" ".join(ln) + '\n') + if 'Failure_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Failure_ID']: # there are failure inputs + f.write('---------------------- FAILURE ----------------------\n') + f.write('FailureID Point Line(s) FailTime FailTen\n') + f.write('() () (,) (s or 0) (N or 0)\n') + for i_line in range(len(self.fst_vt['MoorDyn']['Failure_ID'])): + ln = [] + ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Failure_ID'][i_line])) + ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Failure_Point'][i_line])) + ln.append(','.join(['{:^11d}'.format(a) for a in self.fst_vt['MoorDyn']['Failure_Line(s)'][i_line]])) + ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['FailTime'][i_line])) + ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['FailTen'][i_line])) + f.write(" ".join(ln) + '\n') + if 'ChannelID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['ChannelID']: # There are control inputs f.write('---------------------- CONTROL ---------------------------------------\n') f.write(" ".join(['{:^11s}'.format(i) for i in ['ChannelID', 'Line(s)']])+'\n') @@ -2449,31 +2466,35 @@ def write_MoorDyn(self): ln.append(','.join(self.fst_vt['MoorDyn']['Lines_Control'][i_line])) f.write(" ".join(ln) + '\n') - f.write('---------------------- SOLVER OPTIONS ---------------------------------------\n') - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['dtM'], 'dtM', '- time step to use in mooring integration (s)\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['kbot'], 'kbot', '- bottom stiffness (Pa/m)\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['cbot'], 'cbot', '- bottom damping (Pa-s/m)\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['dtIC'], 'dtIC', '- time interval for analyzing convergence during IC gen (s)\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['TmaxIC'], 'TmaxIC', '- max time for ic gen (s)\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['CdScaleIC'], 'CdScaleIC', '- factor by which to scale drag coefficients during dynamic relaxation (-)\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['threshIC'], 'threshIC', '- threshold for IC convergence (-)\n')) - if 'options' in self.fst_vt['MoorDyn']: - if 'inertialF' in self.fst_vt['MoorDyn']['options']: - f.write('{:<22d} {:<11} {:}'.format(int(self.fst_vt['MoorDyn']['inertialF']), 'inertialF', '- Compute the inertial forces (0: no, 1: yes). Switch to 0 if you get: Warning: extreme pitch moment from body-attached Rod.\n')) - - if 'WaterKin' in self.fst_vt['MoorDyn']['options']: + if 'External_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['External_ID']: # there are external load outputs + f.write('---------------------- EXTERNAL LOADS --------------------------------\n') + f.write('ID Object Fext Blin Bquad CSys\n') + f.write('(#) (name) (N) (Ns/m) (Ns^2/m^2) (-)\n') + for i_line in range(len(self.fst_vt['MoorDyn']['External_ID'])): + ln = [] + ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['External_ID'][i_line])) + ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Object'][i_line])) + ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Fext'][i_line]])) + ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Blin'][i_line]])) + ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Bquad'][i_line]])) + ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['CSys'][i_line])) + f.write(" ".join(ln) + '\n') + + f.write('---------------------- SOLVER OPTIONS ---------------------------------------\n') + for i in range(len(self.fst_vt['MoorDyn']['option_values'])): + + if 'WATERKIN' in self.fst_vt['MoorDyn']['option_names'][i].upper(): self.fst_vt['MoorDyn']['WaterKin_file'] = self.FAST_namingOut + '_WaterKin.dat' - f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['MoorDyn']['WaterKin_file']+'"', 'WaterKin', '- WaterKin input file\n')) + f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['MoorDyn']['WaterKin_file']+'"', self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_description'][i]+'\n')) + else: # if not waterkin handle normally + f.write('{:<22d} {:<11} {:}'.format(float_default_out(self.fst_vt['MoorDyn']['option_values'][i]), self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_description'][i]+'\n')) - - # f.write('{:^11s} {:<11} {:}'.format(self.fst_vt['MoorDyn']['WaterKin'], 'WaterKin', 'Handling of water motion (0=off, 1=on)\n')) f.write('------------------------ OUTPUTS --------------------------------------------\n') outlist = self.get_outlist(self.fst_vt['outlist'], ['MoorDyn']) for channel_list in outlist: for i in range(len(channel_list)): - f.write('"' + channel_list[i] + '"\n') - f.write('END\n') - f.write('------------------------- need this line --------------------------------------\n') + f.write(channel_list[i] + '\n') + f.write('------------------------- END --------------------------------------\n') f.flush() os.fsync(f) @@ -2482,8 +2503,8 @@ def write_MoorDyn(self): def write_WaterKin(self,WaterKin_file): f = open(WaterKin_file, 'w') - f.write('MoorDyn v2 (Feb 2022) Waves and Currents input file set up for USFLOWT\n') - f.write('Wave kinematics that will have an impact over the cans and the mooring lines.\n') + f.write('MoorDyn v2 Waves and Currents input file set up\n') + f.write('This file was written by FAST_writer.py, comments from seed file have been dropped.\n') f.write('--------------------------- WAVES -------------------------------------\n') f.write('{:<22} {:<11} {:}'.format(self.fst_vt['WaterKin']['WaveKinMod'], 'WaveKinMod', '- type of wave input {0 no waves; 3 set up grid of wave data based on time series}\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['WaterKin']['WaveKinFile'], 'WaveKinFile', '- file containing wave elevation time series at 0,0,0\n')) @@ -2496,16 +2517,30 @@ def write_WaterKin(self,WaterKin_file): f.write('{:<22} {:}'.format(self.fst_vt['WaterKin']['Z_Type'], '- Z wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -Zlim, Zlim, num)\n')) f.write('{:<22} {:}'.format(', '.join(['{:.3f}'.format(i) for i in self.fst_vt['WaterKin']['Z_Grid']]), '- Z wave grid point data separated by commas\n')) f.write('--------------------------- CURRENT -------------------------------------\n') - f.write('0 CurrentMod - type of current input {0 no current; 1 steady current profile described below} \n') + f.write('{:} CurrentMod - type of current input {0 no current; 1 steady current profile described below} \n'.format(self.fst_vt['WaterKin']['CurrentMod'])) f.write('z-depth x-current y-current\n') f.write('(m) (m/s) (m/s)\n') + if self.fst_vt['WaterKin']['z-depth']: + for i in range(len(self.fst_vt['WaterKin']['z-depth'])): + f.write('{:.3f} {:.3f} {:.3f}'.format(self.fst_vt['WaterKin']['z-depth'][i], self.fst_vt['WaterKin']['x-current'][i], self.fst_vt['WaterKin']['y-current'][i])) f.write('--------------------- need this line ------------------\n') f.flush() os.fsync(f) f.close() + def write_NonLinearEA(self,Stiffness_file, Linetype, NonLinearEA): + f = open(Stiffness_file, 'w') + + f.write('---{:}---\n'.format(Linetype)) + f.write('Strain Tension\n') + f.write('(-) (N)\n') + for i in range(len(NonLinearEA["Strain"])): + f.write('{:.3f} {:.3f}'.format(NonLinearEA["Strain"][i], NonLinearEA["Tension"][i])) + f.flush() + os.fsync(f) + f.close() def write_StC(self,StC_vt,StC_filename): From f98c8fdf3c8b9bd8325a9532763596e6ee91c520 Mon Sep 17 00:00:00 2001 From: RyanDavies19 Date: Wed, 26 Feb 2025 15:19:42 -0700 Subject: [PATCH 2/5] IO: clean up openfast_io for MD after testing, fix *** error in MD output files --- modules/moordyn/src/MoorDyn_IO.f90 | 8 +-- openfast_io/openfast_io/FAST_reader.py | 17 +++-- openfast_io/openfast_io/FAST_vars_out.py | 5 +- openfast_io/openfast_io/FAST_writer.py | 89 ++++++++++++++---------- 4 files changed, 67 insertions(+), 52 deletions(-) diff --git a/modules/moordyn/src/MoorDyn_IO.f90 b/modules/moordyn/src/MoorDyn_IO.f90 index 4eb8a6e2a0..f3473b61a3 100644 --- a/modules/moordyn/src/MoorDyn_IO.f90 +++ b/modules/moordyn/src/MoorDyn_IO.f90 @@ -1583,7 +1583,7 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg ) if ( p%NumOuts > 0_IntKi .and. p%MDUnOut > 0 ) then ! Write the output parameters to the file - Frmt = '(F10.4,'//TRIM(Int2LStr(p%NumOuts))//'(A1,ES15.7E2))' ! should evenutally use user specified format? + Frmt = '(F10.4,'//TRIM(Int2LStr(p%NumOuts))//'(A1,ES25.7E2))' ! should evenutally use user specified format? WRITE(p%MDUnOut,Frmt) Time, ( p%Delim, y%WriteOutput(I), I=1,p%NumOuts ) END IF @@ -1605,9 +1605,9 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg ) + m%LineList(I)%N*SUM(m%LineList(I)%OutFlagList(10:18)) if (m%LineList(I)%OutFlagList(2) == 1) THEN ! if node positions are included, make them using a float format for higher precision - Frmt = '(F10.4,'//TRIM(Int2LStr(3*(m%LineList(I)%N + 1)))//'(A1,ES15.7E2),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES15.7E2))' + Frmt = '(F10.4,'//TRIM(Int2LStr(3*(m%LineList(I)%N + 1)))//'(A1,ES25.7E2),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES25.7E2))' else - Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES15.7E2))' ! should evenutally use user specified format? + Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES25.7E2))' ! should evenutally use user specified format? end if L = 1 ! start of index of line output file at first entry 12345.7890 @@ -1755,7 +1755,7 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg ) + m%RodList(I)%N*SUM(m%RodList(I)%OutFlagList(12:18)) - Frmt = '(F10.4,'//TRIM(Int2LStr(RodNumOuts))//'(A1,ES15.7E2))' ! should evenutally use user specified format? + Frmt = '(F10.4,'//TRIM(Int2LStr(RodNumOuts))//'(A1,ES25.7E2))' ! should evenutally use user specified format? L = 1 ! start of index of line output file at first entry diff --git a/openfast_io/openfast_io/FAST_reader.py b/openfast_io/openfast_io/FAST_reader.py index ae3c5feb15..b9490dc494 100644 --- a/openfast_io/openfast_io/FAST_reader.py +++ b/openfast_io/openfast_io/FAST_reader.py @@ -2895,8 +2895,6 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Rod_Name'] = [] self.fst_vt['MoorDyn']['Body_ID'] = [] self.fst_vt['MoorDyn']['Rod_ID'] = [] - self.fst_vt['MoorDyn']['ChannelID'] = [] - # MoorDyn f.readline() @@ -3186,19 +3184,19 @@ def read_MoorDyn(self, moordyn_file): data_line = readline_filterComments(f).split() while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same - option_value = data_line[0] - option_name = data_line[1] + option_value = data_line[0].upper() # MD is case insensitive + option_name = data_line[1].upper() # MD is case insensitive if len(data_line) > 2: option_description = ' '.join(data_line[2:]) else: - option_description = '' + option_description = '-' if option_name.upper() == 'WATERKIN': self.fst_vt['MoorDyn']['WaterKin'] = option_value.strip('"') WaterKin_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['WaterKin'])) self.read_WaterKin(WaterKin_file) - self.fst_vt['MoorDyn']['option_values'] = float_read(option_value.strip('"')) # some options values can be strings or floats + self.fst_vt['MoorDyn']['option_values'].append(float_read(option_value.strip('"'))) # some options values can be strings or floats self.fst_vt['MoorDyn']['option_names'].append(option_name) self.fst_vt['MoorDyn']['option_descriptions'].append(option_description) @@ -3216,7 +3214,7 @@ def read_WaterKin(self,WaterKin_file): self.fst_vt['WaterKin']['z-depth'] = [] self.fst_vt['WaterKin']['x-current'] = [] - self.fst_vt['WaterKin']['x-current'] = [] + self.fst_vt['WaterKin']['y-current'] = [] f = open(WaterKin_file) f.readline() @@ -3239,10 +3237,11 @@ def read_WaterKin(self,WaterKin_file): f.readline() f.readline() data_line = readline_filterComments(f).split() - while data_line: + while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same self.fst_vt['WaterKin']['z-depth'].append(float(data_line[0])) self.fst_vt['WaterKin']['x-current'].append(float(data_line[1])) - self.fst_vt['WaterKin']['y-current'].append(float(data_line[2])) + self.fst_vt['WaterKin']['y-current'].append(float(data_line[2])) + data_line = readline_filterComments(f).split() f.close() def read_NonLinearEA(self,Stiffness_file): # read and return the nonlinear line stiffness lookup table for a given line diff --git a/openfast_io/openfast_io/FAST_vars_out.py b/openfast_io/openfast_io/FAST_vars_out.py index f1c9fea3be..e08e2e78d9 100644 --- a/openfast_io/openfast_io/FAST_vars_out.py +++ b/openfast_io/openfast_io/FAST_vars_out.py @@ -9577,10 +9577,11 @@ """ MoorDyn """ # THIS IS NOT A COMPLETE LIST! # the "flexible naming system" discussed on page 7-8 of the documentation is not included -# http://www.matt-hall.ca/files/MoorDyn-Users-Guide-2017-08-16.pdf +# https://moordyn.readthedocs.io/en/latest/inputs.html#id5 # also assuming that like other OpenFAST variables, it is limited to 9 output locations per veriable, i.e. FairTen1-FairTen9 -# TODO: Handle the flexible outputs for moordyn. This will require a different approach than the current dictionary structure. +# TODO: Handle the flexible outputs for moordyn. This will require a different approach than the current dictionary structure. +# Update the message in FAST_writer.write_MoorDyn() when this is finished MoorDyn = {} MoorDyn['FairTen1'] = False # (); ; diff --git a/openfast_io/openfast_io/FAST_writer.py b/openfast_io/openfast_io/FAST_writer.py index da2b8f8b46..de9dcb89ae 100644 --- a/openfast_io/openfast_io/FAST_writer.py +++ b/openfast_io/openfast_io/FAST_writer.py @@ -30,35 +30,49 @@ def auto_format(f, var): elif isinstance(var, float): f.write('{: 2.15e}\n'.format(var)) -def float_default_out(val): +def float_default_out(val, trim = False): """ Formatted float output when 'default' is an option args: val: value to be formatted + trim: trim the whitespace from the returned value returns: formatted value """ if type(val) is float: - return '{: 22f}'.format(val) + if trim: + return '{:.4f}'.format(val) + else: + return '{: 22f}'.format(val) else: - return '{:<22}'.format(val) + if trim: + return '{:}'.format(val) + else: + return '{:<22}'.format(val) -def int_default_out(val): +def int_default_out(val, trim = False): """ Formatted int output when 'default' is an option args: val: value to be formatted + trim: trim the whitespace from the returned value returns: formatted value """ if type(val) is float: - return '{:<22d}'.format(val) + if trim: + return '{:d}'.format(val) + else: + return '{:<22d}'.format(val) else: - return '{:<22}'.format(val) + if trim: + return '{:}'.format(val) + else: + return '{:<22}'.format(val) def get_dict(vartree, branch): """ @@ -236,7 +250,7 @@ def execute(self): self.write_MAP() elif self.fst_vt['Fst']['CompMooring'] == 3: self.write_MoorDyn() - if 'option_names' in self.fst_vt['MoorDyn'] and 'WATERKIN' in self.fst_vt['MoorDyn']['option_names'].upper(): + if 'option_names' in self.fst_vt['MoorDyn'] and 'WATERKIN' in self.fst_vt['MoorDyn']['option_names']: self.write_WaterKin(os.path.join(self.FAST_runDirectory,self.fst_vt['MoorDyn']['WaterKin_file'])) if self.fst_vt['Fst']['CompElast'] == 2: @@ -2335,16 +2349,17 @@ def write_MoorDyn(self): f.write('--------------------- MoorDyn Input File ------------------------------------\n') f.write('Generated with OpenFAST_IO\n') + f.write('comments from seed file have been dropped. Output Channels are not yet fully supported\n') f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['Echo'], 'Echo', '- echo the input file data (flag)\n')) f.write('----------------------- LINE TYPES ------------------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['Name', 'Diam', 'MassDen', 'EA', 'BA/-zeta', 'EI', 'Cd', 'Ca', 'CdAx', 'CaAx']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['(-)', '(m)', '(kg/m)', '(N)', '(N-s/-)', '(-)', '(-)', '(-)', '(-)', '(-)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['Name', 'Diam', 'MassDen', 'EA', 'BA/-zeta', 'EI', 'Cd', 'Ca', 'CdAx', 'CaAx', 'Cl (optional)', 'dF (optional)', 'cF (optional)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(m)', '(kg/m)', '(N)', '(N-s/-)', '(N-m^2)', '(-)', '(-)', '(-)', '(-)', '(-)', '(-)', '(-)']])+'\n') for i in range(len(self.fst_vt['MoorDyn']['Name'])): ln = [] - ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Name'][i])) + ln.append('{:<11}'.format(self.fst_vt['MoorDyn']['Name'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Diam'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['MassDen'][i])) - ln.append('|'.join([float_default_out(a) for a in self.fst_vt['MoorDyn']['EA'][i]])) + ln.append('|'.join([float_default_out(a, trim=True) for a in self.fst_vt['MoorDyn']['EA'][i]])) ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['BA_zeta'][i]])) ln.append('{:^11.4e}'.format(self.fst_vt['MoorDyn']['EI'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Cd'][i])) @@ -2358,11 +2373,11 @@ def write_MoorDyn(self): if 'Rod_Name' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Rod_Name']: f.write('----------------------- ROD TYPES ------------------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['TypeName', 'Diam', 'Mass/m', 'Cd', 'Ca', 'CdEnd', 'CaEnd']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['(name)', '(m)', '(kg/m)', '(-)', '(-)', '(-)', '(-)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['TypeName', 'Diam', 'Mass/m', 'Cd', 'Ca', 'CdEnd', 'CaEnd']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['(name)', '(m)', '(kg/m)', '(-)', '(-)', '(-)', '(-)']])+'\n') for i in range(len(self.fst_vt['MoorDyn']['Rod_Name'])): ln = [] - ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_Name'][i])) + ln.append('{:<11}'.format(self.fst_vt['MoorDyn']['Rod_Name'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Rod_Diam'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Rod_MassDen'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Rod_Cd'][i])) @@ -2374,11 +2389,11 @@ def write_MoorDyn(self): if 'Body_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Body_ID']: f.write('----------------------- BODIES ------------------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['ID', 'Attachement', 'X0', 'Y0', 'Z0', 'r0', 'p0','y0','Mass','CG*','I*','Volume','CdA*','Ca*']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['(#)', '(word)', '(m)', '(m)', '(m)', '(deg)', '(deg)','(deg)','(kg)','(m)','(kg-m^2)','(m^3)','m^2','(kg/m^3)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['ID', 'Attachement', 'X0', 'Y0', 'Z0', 'r0', 'p0','y0','Mass','CG*','I*','Volume','CdA*','Ca*']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['(#)', '(word)', '(m)', '(m)', '(m)', '(deg)', '(deg)','(deg)','(kg)','(m)','(kg-m^2)','(m^3)','m^2','(kg/m^3)']])+'\n') for i in range(len(self.fst_vt['MoorDyn']['Body_ID'])): ln = [] - ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Body_ID'][i])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Body_ID'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Body_Attachment'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['X0'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Y0'][i])) @@ -2396,11 +2411,11 @@ def write_MoorDyn(self): if 'Rod_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Rod_ID']: f.write('----------------------- RODS ------------------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['ID', 'RodType', 'Attachment', 'Xa', 'Ya', 'Za', 'Xb','Yb','Zb','NumSegs','RodOutputs']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['(#)', '(name)', '(word/ID)', '(m)', '(m)', '(m)', '(m)','(m)','(m)','(-)','(-)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['ID', 'RodType', 'Attachment', 'Xa', 'Ya', 'Za', 'Xb','Yb','Zb','NumSegs','RodOutputs']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['(#)', '(name)', '(word/ID)', '(m)', '(m)', '(m)', '(m)','(m)','(m)','(-)','(-)']])+'\n') for i in range(len(self.fst_vt['MoorDyn']['Rod_ID'])): ln = [] - ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_ID'][i])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Rod_ID'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_Type'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_Attachment'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Xa'][i])) @@ -2415,11 +2430,11 @@ def write_MoorDyn(self): f.write('---------------------- POINTS --------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['ID', 'Attachment', 'X', 'Y', 'Z', 'M', 'V', 'CdA', 'CA']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['(-)', '(-)', '(m)', '(m)', '(m)', '(kg)', '(m^3)', '(m^2)', '(-)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['ID', 'Attachment', 'X', 'Y', 'Z', 'M', 'V', 'CdA', 'CA']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(-)', '(m)', '(m)', '(m)', '(kg)', '(m^3)', '(m^2)', '(-)']])+'\n') for i in range(len(self.fst_vt['MoorDyn']['Point_ID'])): ln = [] - ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Point_ID'][i])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Point_ID'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Attachment'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['X'][i])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Y'][i])) @@ -2430,11 +2445,11 @@ def write_MoorDyn(self): ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['CA'][i])) f.write(" ".join(ln) + '\n') f.write('---------------------- LINES --------------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['Line', 'LineType', 'AttachA', 'AttachB', 'UnstrLen', 'NumSegs', 'Outputs']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['(-)', '(-)', '(-)', '(-)', '(m)', '(-)', '(-)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['Line', 'LineType', 'AttachA', 'AttachB', 'UnstrLen', 'NumSegs', 'Outputs']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(-)', '(-)', '(-)', '(m)', '(-)', '(-)']])+'\n') for i in range(len(self.fst_vt['MoorDyn']['Line_ID'])): ln = [] - ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Line_ID'][i])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Line_ID'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['LineType'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['AttachA'][i])) ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['AttachB'][i])) @@ -2449,20 +2464,20 @@ def write_MoorDyn(self): f.write('() () (,) (s or 0) (N or 0)\n') for i_line in range(len(self.fst_vt['MoorDyn']['Failure_ID'])): ln = [] - ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Failure_ID'][i_line])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Failure_ID'][i_line])) ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Failure_Point'][i_line])) - ln.append(','.join(['{:^11d}'.format(a) for a in self.fst_vt['MoorDyn']['Failure_Line(s)'][i_line]])) + ln.append('{:^11s}'.format(','.join(['{:^d}'.format(a) for a in self.fst_vt['MoorDyn']['Failure_Line(s)'][i_line]]))) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['FailTime'][i_line])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['FailTen'][i_line])) f.write(" ".join(ln) + '\n') if 'ChannelID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['ChannelID']: # There are control inputs f.write('---------------------- CONTROL ---------------------------------------\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['ChannelID', 'Line(s)']])+'\n') - f.write(" ".join(['{:^11s}'.format(i) for i in ['()', '(,)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['ChannelID', 'Line(s)']])+'\n') + f.write(" ".join(['{:<11s}'.format(i) for i in ['()', '(,)']])+'\n') for i_line in range(len(self.fst_vt['MoorDyn']['ChannelID'])): ln = [] - ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['ChannelID'][i_line])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['ChannelID'][i_line])) ln.append(','.join(self.fst_vt['MoorDyn']['Lines_Control'][i_line])) f.write(" ".join(ln) + '\n') @@ -2472,7 +2487,7 @@ def write_MoorDyn(self): f.write('(#) (name) (N) (Ns/m) (Ns^2/m^2) (-)\n') for i_line in range(len(self.fst_vt['MoorDyn']['External_ID'])): ln = [] - ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['External_ID'][i_line])) + ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['External_ID'][i_line])) ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Object'][i_line])) ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Fext'][i_line]])) ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Blin'][i_line]])) @@ -2483,11 +2498,11 @@ def write_MoorDyn(self): f.write('---------------------- SOLVER OPTIONS ---------------------------------------\n') for i in range(len(self.fst_vt['MoorDyn']['option_values'])): - if 'WATERKIN' in self.fst_vt['MoorDyn']['option_names'][i].upper(): + if 'WATERKIN' in self.fst_vt['MoorDyn']['option_names'][i]: self.fst_vt['MoorDyn']['WaterKin_file'] = self.FAST_namingOut + '_WaterKin.dat' - f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['MoorDyn']['WaterKin_file']+'"', self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_description'][i]+'\n')) + f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['MoorDyn']['WaterKin_file']+'"', self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_descriptions'][i]+'\n')) else: # if not waterkin handle normally - f.write('{:<22d} {:<11} {:}'.format(float_default_out(self.fst_vt['MoorDyn']['option_values'][i]), self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_description'][i]+'\n')) + f.write('{:<22} {:<11} {:}'.format(float_default_out(self.fst_vt['MoorDyn']['option_values'][i]), self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_descriptions'][i]+'\n')) f.write('------------------------ OUTPUTS --------------------------------------------\n') outlist = self.get_outlist(self.fst_vt['outlist'], ['MoorDyn']) @@ -2517,7 +2532,7 @@ def write_WaterKin(self,WaterKin_file): f.write('{:<22} {:}'.format(self.fst_vt['WaterKin']['Z_Type'], '- Z wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -Zlim, Zlim, num)\n')) f.write('{:<22} {:}'.format(', '.join(['{:.3f}'.format(i) for i in self.fst_vt['WaterKin']['Z_Grid']]), '- Z wave grid point data separated by commas\n')) f.write('--------------------------- CURRENT -------------------------------------\n') - f.write('{:} CurrentMod - type of current input {0 no current; 1 steady current profile described below} \n'.format(self.fst_vt['WaterKin']['CurrentMod'])) + f.write('{:} CurrentMod - type of current input (0 no current; 1 steady current profile described below) \n'.format(self.fst_vt['WaterKin']['CurrentMod'])) f.write('z-depth x-current y-current\n') f.write('(m) (m/s) (m/s)\n') if self.fst_vt['WaterKin']['z-depth']: @@ -2667,7 +2682,7 @@ def write_StC(self,StC_vt,StC_filename): 'glue-codes', 'openfast', '5MW_Land_BD_DLL_WTurb') # Path to fst directory files - check_rtest_cloned(os.path.join(fast.FAST_directory, fast.FAST_InputFile)) + check_rtest_cloned(os.path.join(fast.FAST_directory)) fast.execute() From 56719649cd6f6d1c9b61b5259447c4600bba1c25 Mon Sep 17 00:00:00 2001 From: RyanDavies19 Date: Thu, 27 Feb 2025 08:33:47 -0700 Subject: [PATCH 3/5] MD: update version --- modules/moordyn/src/MoorDyn.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/moordyn/src/MoorDyn.f90 b/modules/moordyn/src/MoorDyn.f90 index 80d21a7810..0e1f1ae812 100644 --- a/modules/moordyn/src/MoorDyn.f90 +++ b/modules/moordyn/src/MoorDyn.f90 @@ -34,7 +34,7 @@ MODULE MoorDyn PRIVATE - TYPE(ProgDesc), PARAMETER :: MD_ProgDesc = ProgDesc( 'MoorDyn', 'v2.2.2', '2024-01-16' ) + TYPE(ProgDesc), PARAMETER :: MD_ProgDesc = ProgDesc( 'MoorDyn', 'v2.3.8', '2025-02-27' ) INTEGER(IntKi), PARAMETER :: wordy = 0 ! verbosity level. >1 = more console output From 39783ec84c6850b39f81afc127cbf3023df8eaee Mon Sep 17 00:00:00 2001 From: RyanDavies19 Date: Fri, 28 Feb 2025 13:58:26 -0700 Subject: [PATCH 4/5] MD: IO output format fix --- modules/moordyn/src/MoorDyn_IO.f90 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/moordyn/src/MoorDyn_IO.f90 b/modules/moordyn/src/MoorDyn_IO.f90 index f3473b61a3..4b32b2a898 100644 --- a/modules/moordyn/src/MoorDyn_IO.f90 +++ b/modules/moordyn/src/MoorDyn_IO.f90 @@ -1583,7 +1583,7 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg ) if ( p%NumOuts > 0_IntKi .and. p%MDUnOut > 0 ) then ! Write the output parameters to the file - Frmt = '(F10.4,'//TRIM(Int2LStr(p%NumOuts))//'(A1,ES25.7E2))' ! should evenutally use user specified format? + Frmt = '(F10.4,'//TRIM(Int2LStr(p%NumOuts))//'(A1,ES15.7))' ! should evenutally use user specified format? WRITE(p%MDUnOut,Frmt) Time, ( p%Delim, y%WriteOutput(I), I=1,p%NumOuts ) END IF @@ -1605,9 +1605,9 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg ) + m%LineList(I)%N*SUM(m%LineList(I)%OutFlagList(10:18)) if (m%LineList(I)%OutFlagList(2) == 1) THEN ! if node positions are included, make them using a float format for higher precision - Frmt = '(F10.4,'//TRIM(Int2LStr(3*(m%LineList(I)%N + 1)))//'(A1,ES25.7E2),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES25.7E2))' + Frmt = '(F10.4,'//TRIM(Int2LStr(3*(m%LineList(I)%N + 1)))//'(A1,ES15.7),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES15.7))' else - Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES25.7E2))' ! should evenutally use user specified format? + Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES15.7))' ! should evenutally use user specified format? end if L = 1 ! start of index of line output file at first entry 12345.7890 @@ -1755,7 +1755,7 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg ) + m%RodList(I)%N*SUM(m%RodList(I)%OutFlagList(12:18)) - Frmt = '(F10.4,'//TRIM(Int2LStr(RodNumOuts))//'(A1,ES25.7E2))' ! should evenutally use user specified format? + Frmt = '(F10.4,'//TRIM(Int2LStr(RodNumOuts))//'(A1,ES15.7))' ! should evenutally use user specified format? L = 1 ! start of index of line output file at first entry From 608c924a5a2bc7a596a63cd3c9ee5a8dce1154e2 Mon Sep 17 00:00:00 2001 From: RyanDavies19 Date: Mon, 3 Mar 2025 13:07:02 -0700 Subject: [PATCH 5/5] IO: Fix MoorDyn outlist handling, remove MoorDyn echo file reading --- openfast_io/openfast_io/FAST_reader.py | 20 ++++++++++++++++---- openfast_io/openfast_io/FAST_vars_out.py | 3 ++- openfast_io/openfast_io/FAST_writer.py | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/openfast_io/openfast_io/FAST_reader.py b/openfast_io/openfast_io/FAST_reader.py index b9490dc494..e802a8efd8 100644 --- a/openfast_io/openfast_io/FAST_reader.py +++ b/openfast_io/openfast_io/FAST_reader.py @@ -2897,9 +2897,6 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Rod_ID'] = [] # MoorDyn - f.readline() - f.readline() - self.fst_vt['MoorDyn']['Echo'] = bool_read(f.readline().split()[0]) data_line = f.readline() while data_line: @@ -3205,11 +3202,26 @@ def read_MoorDyn(self, moordyn_file): elif 'outputs' in data_line: - self.read_outlist_freeForm(f,'MoorDyn') + data_line = readline_filterComments(f) + + while (data_line[0] and data_line[0][:3] != '---') and not ('END' in data_line): # OpenFAST searches for --- and END, so we'll do the same + + if '"' in data_line: + pattern = r'"?(.*?)"?' # grab only the text between quotes + data_line = re.findall(pattern, data_line)[0] + + channels = data_line.split(',') # split on commas + channels = [c.strip() for c in channels] # strip whitespace + for c in channels: + self.fst_vt['outlist']['MoorDyn'][c] = True + data_line = readline_filterComments(f) f.close() break + else: # we must be in the header section, unlimited lines of text allowed here so skip until we hit the first line w/ keywords 'Line Types' or 'Line Dictionary' + data_line = f.readline() + def read_WaterKin(self,WaterKin_file): self.fst_vt['WaterKin']['z-depth'] = [] diff --git a/openfast_io/openfast_io/FAST_vars_out.py b/openfast_io/openfast_io/FAST_vars_out.py index e08e2e78d9..ea11689d3b 100644 --- a/openfast_io/openfast_io/FAST_vars_out.py +++ b/openfast_io/openfast_io/FAST_vars_out.py @@ -9580,7 +9580,8 @@ # https://moordyn.readthedocs.io/en/latest/inputs.html#id5 # also assuming that like other OpenFAST variables, it is limited to 9 output locations per veriable, i.e. FairTen1-FairTen9 -# TODO: Handle the flexible outputs for moordyn. This will require a different approach than the current dictionary structure. +# TODO: Handle the flexible outputs for moordyn. This will require a different approach than the current dictionary structure. +# Right now a hackish way is used in the reader # Update the message in FAST_writer.write_MoorDyn() when this is finished MoorDyn = {} diff --git a/openfast_io/openfast_io/FAST_writer.py b/openfast_io/openfast_io/FAST_writer.py index ec8d5843c3..b7c0f0ff6f 100644 --- a/openfast_io/openfast_io/FAST_writer.py +++ b/openfast_io/openfast_io/FAST_writer.py @@ -2349,8 +2349,8 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m f.write('--------------------- MoorDyn Input File ------------------------------------\n') f.write('Generated with OpenFAST_IO\n') - f.write('comments from seed file have been dropped. Output Channels are not yet fully supported\n') - f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['Echo'], 'Echo', '- echo the input file data (flag)\n')) + f.write('Comments from seed file have been dropped. Output Channels are not yet fully supported\n') + f.write('NOTE: MoorDyn does not use ECHO, instead use WriteLog of 0, 1, 2, or 3 in the options list \n') f.write('----------------------- LINE TYPES ------------------------------------------\n') f.write(" ".join(['{:<11s}'.format(i) for i in ['Name', 'Diam', 'MassDen', 'EA', 'BA/-zeta', 'EI', 'Cd', 'Ca', 'CdAx', 'CaAx', 'Cl (optional)', 'dF (optional)', 'cF (optional)']])+'\n') f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(m)', '(kg/m)', '(N)', '(N-s/-)', '(N-m^2)', '(-)', '(-)', '(-)', '(-)', '(-)', '(-)', '(-)']])+'\n')