diff --git a/modules/moordyn/src/MoorDyn.f90 b/modules/moordyn/src/MoorDyn.f90 index f6c2eac93f..ca98b7e23e 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 diff --git a/modules/moordyn/src/MoorDyn_IO.f90 b/modules/moordyn/src/MoorDyn_IO.f90 index efe59ca038..773cf03566 100644 --- a/modules/moordyn/src/MoorDyn_IO.f90 +++ b/modules/moordyn/src/MoorDyn_IO.f90 @@ -1592,7 +1592,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,ES15.7))' ! should evenutally use user specified format? WRITE(p%MDUnOut,Frmt) Time, ( p%Delim, y%WriteOutput(I), I=1,p%NumOuts ) END IF @@ -1614,9 +1614,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,ES15.7),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES15.7))' else - Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES15.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 @@ -1773,7 +1773,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,ES15.7))' ! 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 28df0aaf95..e802a8efd8 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 @@ -2879,13 +2895,8 @@ 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() - f.readline() - self.fst_vt['MoorDyn']['Echo'] = bool_read(f.readline().split()[0]) data_line = f.readline() while data_line: @@ -2903,6 +2914,7 @@ 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'] = [] @@ -2912,13 +2924,13 @@ def read_MoorDyn(self, moordyn_file): self.fst_vt['MoorDyn']['Cl'] = [] self.fst_vt['MoorDyn']['dF'] = [] self.fst_vt['MoorDyn']['cF'] = [] - data_line = f.readline().strip().split() - while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same. TODO: make compatible with MD viscoelastic model + 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])) @@ -2936,7 +2948,14 @@ def read_MoorDyn(self, moordyn_file): 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])) - data_line = f.readline().strip().split() + + 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: @@ -2950,7 +2969,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])) @@ -2959,7 +2978,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 @@ -2982,7 +3001,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]) @@ -2998,7 +3017,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 @@ -3017,7 +3036,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]) @@ -3030,7 +3049,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 \ @@ -3048,7 +3067,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])) @@ -3059,7 +3078,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: @@ -3074,7 +3093,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])) @@ -3083,10 +3102,29 @@ 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 'control' in data_line.lower(): # TODO: add in failures section, add in user specified loads and damping section + 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() f.readline() @@ -3094,7 +3132,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 @@ -3108,46 +3146,87 @@ 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 + self.fst_vt['MoorDyn']['option_values'] = [] + self.fst_vt['MoorDyn']['option_names'] = [] # keep list of MoorDyn options + self.fst_vt['MoorDyn']['option_descriptions'] = [] - string_options = ['WaterKin'] - - 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_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('"') + 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: - 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) - data_line = f.readline().strip().split() + 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) + + data_line = readline_filterComments(f).split() data_line = ''.join(data_line) # re-join for reading next section uniformly 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 - 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) + 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): - print('here') + + self.fst_vt['WaterKin']['z-depth'] = [] + self.fst_vt['WaterKin']['x-current'] = [] + self.fst_vt['WaterKin']['y-current'] = [] f = open(WaterKin_file) f.readline() @@ -3167,7 +3246,30 @@ 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[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])) + 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 + + 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_vars_out.py b/openfast_io/openfast_io/FAST_vars_out.py index f1c9fea3be..ea11689d3b 100644 --- a/openfast_io/openfast_io/FAST_vars_out.py +++ b/openfast_io/openfast_io/FAST_vars_out.py @@ -9577,10 +9577,12 @@ """ 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. +# Right now a hackish way is used in the reader +# 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 3aa1f012cc..b7c0f0ff6f 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 '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']: self.write_WaterKin(os.path.join(self.FAST_runDirectory,self.fst_vt['MoorDyn']['WaterKin_file'])) if self.fst_vt['Fst']['CompElast'] == 2: @@ -2335,17 +2349,18 @@ 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('{!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') + 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('{:^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, 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])) ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Ca'][i])) @@ -2359,13 +2374,16 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['cF'][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') - 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])) @@ -2377,11 +2395,11 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m 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])) @@ -2399,11 +2417,11 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m 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])) @@ -2418,11 +2436,11 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m 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])) @@ -2433,11 +2451,11 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m 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])) @@ -2446,41 +2464,58 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m 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('{:<7d}'.format(self.fst_vt['MoorDyn']['Failure_ID'][i_line])) + ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Failure_Point'][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') - 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('{:<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]])) + 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]: 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_descriptions'][i]+'\n')) + else: # if not waterkin handle normally + 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('{:^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) @@ -2489,8 +2524,8 @@ def write_MoorDyn(self): # TODO: see TODO's in FAST_reader/read_MoorDyn.py and m 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')) @@ -2503,16 +2538,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): @@ -2639,7 +2688,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()