diff --git a/docs/source/user/moordyn/index.rst b/docs/source/user/moordyn/index.rst index 52730e521a..626d67e83e 100644 --- a/docs/source/user/moordyn/index.rst +++ b/docs/source/user/moordyn/index.rst @@ -3,8 +3,9 @@ MoorDyn Users Guide ==================== -A standalone C++ version of MoorDyn is also available outside the OpenFAST -repository. The documentation for the C++ version covers the input file format +The documentation for MoorDyn is avaible `here `_. It features instructions +for the use of MoorDynF, the module in OpenFAST, and MoorDynC, the standalone C++ code. Input file formats +are described in the `inputs section <>`_. (`MoorDyn usage `_, specifically the section for V2) usage of MoorDyn at the FAST.Farm level (`MoorDyn with FAST.Farm `_), diff --git a/modules/moordyn/README.md b/modules/moordyn/README.md index 8a7aaeff08..1042d2a075 100644 --- a/modules/moordyn/README.md +++ b/modules/moordyn/README.md @@ -16,12 +16,10 @@ The Fortran implementation of MoorDyn, which has been developed following the FAST Modularization Framework, is included as a module in OpenFAST. -For the C++ implementation of MoorDyn, see http://www.matt-hall.ca/moordyn. -"MoorDyn C" can be compiled as a dynamically-linked library and features -simpler functions for easy coupling with models or scripts coded in C/C++, -Fortran, Matlab/Simulink, etc. It has recently been integrated into WEC-Sim. +For the C++ implementation of MoorDyn, see https://github.com/FloatingArrayDesign/MoorDyn. +"MoorDynC" is more adaptable to unique use cases and couplings. It can be compiled as a dynamically-linked library or wrapped for use in Python (as a module), Fortran, and Matlab. It features simpler functions for easy coupling with models or scripts coded in C/C++, Fortran, Matlab/Simulink, etc. An example of this coupling is it’s integration into WEC-Sim. -Both forms of MoorDyn feature the same underlying mooring model, use similar +Both forms of MoorDyn feature the same underlying mooring model, use the same input and output conventions, and are being updated and improved in parallel. They follow the same version numbering, with a "C" or "F" suffix for differentiation. diff --git a/modules/moordyn/src/MoorDyn_Line.f90 b/modules/moordyn/src/MoorDyn_Line.f90 index ee381bc988..3be69f7548 100644 --- a/modules/moordyn/src/MoorDyn_Line.f90 +++ b/modules/moordyn/src/MoorDyn_Line.f90 @@ -263,7 +263,7 @@ SUBROUTINE Line_Initialize (Line, LineProp, rhoW, ErrStat, ErrMsg) CHARACTER(ErrMsgLen) :: ErrMsg2 ! Error message if ErrStat2 /= ErrID_None REAL(DbKi) :: WetWeight REAL(DbKi) :: SeabedCD = 0.0_DbKi - REAL(DbKi) :: TenTol = 0.0001_DbKi + REAL(DbKi) :: Tol = 0.0001_DbKi REAL(DbKi), ALLOCATABLE :: LSNodes(:) REAL(DbKi), ALLOCATABLE :: LNodesX(:) REAL(DbKi), ALLOCATABLE :: LNodesZ(:) @@ -335,41 +335,59 @@ SUBROUTINE Line_Initialize (Line, LineProp, rhoW, ErrStat, ErrMsg) ! are stored in a module and thus their values are saved from CALL to ! CALL). + IF (XF == 0.0) THEN - CALL Catenary ( XF , ZF , Line%UnstrLen, LineProp%EA , & - WetWeight , SeabedCD, TenTol, (N+1) , & - LSNodes, LNodesX, LNodesZ , ErrStat2, ErrMsg2) + DO J = 0,N ! Loop through all nodes per line where the line position and tension can be output + Line%r(1,J) = Line%r(1,0) + (Line%r(1,N) - Line%r(1,0))*REAL(J, DbKi)/REAL(N, DbKi) + Line%r(2,J) = Line%r(2,0) + (Line%r(2,N) - Line%r(2,0))*REAL(J, DbKi)/REAL(N, DbKi) + Line%r(3,J) = Line%r(3,0) + (Line%r(3,N) - Line%r(3,0))*REAL(J, DbKi)/REAL(N, DbKi) + END DO + + CALL WrScr(" Vertical initial profile for Line "//trim(Num2LStr(Line%IdNum))//".") + + ELSE ! If the line is not vertical, solve for the catenary profile + + CALL Catenary ( XF , ZF , Line%UnstrLen, LineProp%EA , & + WetWeight , SeabedCD, Tol, (N+1) , & + LSNodes, LNodesX, LNodesZ , ErrStat2, ErrMsg2) - IF (ErrStat2 == ErrID_None) THEN ! if it worked, use it - ! Transform the positions of each node on the current line from the local - ! coordinate system of the current line to the inertial frame coordinate - ! system: + IF ((abs(LNodesZ(N+1) - ZF) > Tol) .AND. (ErrStat2 == ErrID_None)) THEN + ! Check fairlead node z position is same as z distance between fairlead and anchor + ErrStat2 = ErrID_Warn + ErrMsg2 = ' Wrong catenary initial profile for Line '//trim(Num2LStr(Line%IdNum))//'. Fairlead and anchor vertical seperation has changed.' + ENDIF + + IF (ErrStat2 == ErrID_None) THEN ! if it worked, use it + ! Transform the positions of each node on the current line from the local + ! coordinate system of the current line to the inertial frame coordinate + ! system: - DO J = 0,N ! Loop through all nodes per line where the line position and tension can be output - Line%r(1,J) = Line%r(1,0) + LNodesX(J+1)*COSPhi - Line%r(2,J) = Line%r(2,0) + LNodesX(J+1)*SINPhi - Line%r(3,J) = Line%r(3,0) + LNodesZ(J+1) - ENDDO ! J - All nodes per line where the line position and tension can be output + DO J = 0,N ! Loop through all nodes per line where the line position and tension can be output + Line%r(1,J) = Line%r(1,0) + LNodesX(J+1)*COSPhi + Line%r(2,J) = Line%r(2,0) + LNodesX(J+1)*SINPhi + Line%r(3,J) = Line%r(3,0) + LNodesZ(J+1) + ENDDO ! J - All nodes per line where the line position and tension can be output + ELSE ! if there is a problem with the catenary approach, just stretch the nodes linearly between fairlead and anchor - ELSE ! if there is a problem with the catenary approach, just stretch the nodes linearly between fairlead and anchor + CALL SetErrStat(ErrStat2, ErrMsg2, ErrStat, ErrMsg, 'Line_Initialize') + CALL WrScr(" Catenary solve of Line "//trim(Num2LStr(Line%IdNum))//" unsuccessful. Initializing as linear.") - !CALL SetErrStat(ErrStat2, ErrMsg2, ErrStat, ErrMsg, 'Line_Initialize') - call WrScr(" Catenary solve of Line "//trim(Num2LStr(Line%IdNum))//" unsuccessful. Initializing as linear.") + ! print *, "Node positions: " -! print *, "Node positions: " + DO J = 0,N ! Loop through all nodes per line where the line position and tension can be output + Line%r(1,J) = Line%r(1,0) + (Line%r(1,N) - Line%r(1,0))*REAL(J, DbKi)/REAL(N, DbKi) + Line%r(2,J) = Line%r(2,0) + (Line%r(2,N) - Line%r(2,0))*REAL(J, DbKi)/REAL(N, DbKi) + Line%r(3,J) = Line%r(3,0) + (Line%r(3,N) - Line%r(3,0))*REAL(J, DbKi)/REAL(N, DbKi) + + ! print*, Line%r(:,J) + ENDDO + + ! print*,"FYI line end A and B node coords are" + ! print*, Line%r(:,0) + ! print*, Line%r(:,N) + ENDIF - DO J = 0,N ! Loop through all nodes per line where the line position and tension can be output - Line%r(1,J) = Line%r(1,0) + (Line%r(1,N) - Line%r(1,0))*REAL(J, DbKi)/REAL(N, DbKi) - Line%r(2,J) = Line%r(2,0) + (Line%r(2,N) - Line%r(2,0))*REAL(J, DbKi)/REAL(N, DbKi) - Line%r(3,J) = Line%r(3,0) + (Line%r(3,N) - Line%r(3,0))*REAL(J, DbKi)/REAL(N, DbKi) - -! print*, Line%r(:,J) - ENDDO - -! print*,"FYI line end A and B node coords are" -! print*, Line%r(:,0) -! print*, Line%r(:,N) ENDIF @@ -500,6 +518,7 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & INTEGER(4) :: MaxIter ! Maximum number of Newton-Raphson iterations possible before giving up (-) LOGICAL :: FirstIter ! Flag to determine whether or not this is the first time through the Newton-Raphson interation (flag) + LOGICAL :: reverseFlag ! Flag for when the anchor is above the fairlead ErrStat = ERrId_None @@ -518,9 +537,15 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & W = REAL( W_In , DbKi ) XF = REAL( XF_In , DbKi ) ZF = REAL( ZF_In , DbKi ) + IF ( ZF < 0.0 ) THEN ! .TRUE. if the fairlead has passed below its anchor + ZF = -ZF + reverseFlag = .TRUE. + CALL WrScr(' Warning from catenary: Anchor point is above the fairlead point for Line '//trim(Num2LStr(Line%IdNum))//', consider changing.') + ELSE + reverseFlag = .FALSE. + ENDIF - ! HF and VF cannot be initialized to zero when a portion of the line rests on the seabed and the anchor tension is nonzero ! Generate the initial guess values for the horizontal and vertical tensions @@ -531,9 +556,9 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & XF2 = XF*XF ZF2 = ZF*ZF - IF ( XF == 0.0_DbKi ) THEN ! .TRUE. if the current mooring line is exactly vertical - Lamda0 = 1.0D+06 - ELSEIF ( L <= SQRT( XF2 + ZF2 ) ) THEN ! .TRUE. if the current mooring line is taut + ! IF ( XF == 0.0_DbKi ) THEN ! .TRUE. if the current mooring line is exactly vertical + ! Lamda0 = 1.0D+06 + IF ( L <= SQRT( XF2 + ZF2 ) ) THEN ! .TRUE. if the current mooring line is taut Lamda0 = 0.2_DbKi ELSE ! The current mooring line must be slack and not vertical Lamda0 = SQRT( 3.0_DbKi*( ( L**2 - ZF2 )/XF2 - 1.0_DbKi ) ) @@ -549,33 +574,27 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & IF ( Tol <= EPSILON(TOL) ) THEN ! .TRUE. when the convergence tolerance is specified incorrectly ErrStat = ErrID_Warn ErrMsg = ' Convergence tolerance must be greater than zero in routine Catenary().' - return + RETURN ELSEIF ( XF < 0.0_DbKi ) THEN ! .TRUE. only when the local coordinate system is not computed correctly ErrStat = ErrID_Warn ErrMsg = ' The horizontal distance between an anchor and its'// & ' fairlead must not be less than zero in routine Catenary().' - return - - ELSEIF ( ZF < 0.0_DbKi ) THEN ! .TRUE. if the fairlead has passed below its anchor - ErrStat = ErrID_Warn - ErrMsg = " A line's fairlead is defined as below its anchor. You may need to swap a line's fairlead and anchor end nodes." - return - + RETURN ELSEIF ( L <= 0.0_DbKi ) THEN ! .TRUE. when the unstretched line length is specified incorrectly ErrStat = ErrID_Warn ErrMsg = ' Unstretched length of line must be greater than zero in routine Catenary().' - return + RETURN ELSEIF ( EA <= 0.0_DbKi ) THEN ! .TRUE. when the unstretched line length is specified incorrectly ErrStat = ErrID_Warn ErrMsg = ' Extensional stiffness of line must be greater than zero in routine Catenary().' - return + RETURN ELSEIF ( W == 0.0_DbKi ) THEN ! .TRUE. when the weight of the line in fluid is zero so that catenary solution is ill-conditioned ErrStat = ErrID_Warn ErrMsg = ' The weight of the line in fluid must not be zero. '// & ' Routine Catenary() cannot solve quasi-static mooring line solution.' - return + RETURN ELSEIF ( W > 0.0_DbKi ) THEN ! .TRUE. when the line will sink in fluid @@ -584,9 +603,9 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & IF ( ( L >= LMax ) .AND. ( CB >= 0.0_DbKi ) ) then ! .TRUE. if the line is as long or longer than its maximum possible value with seabed interaction ErrStat = ErrID_Warn - !ErrMsg = ' Unstretched mooring line length too large. '// & - ! ' Routine Catenary() cannot solve quasi-static mooring line solution.' - return + ErrMsg = ' Unstretched mooring line length too large. '// & + ' Routine Catenary() cannot solve quasi-static mooring line solution.' + RETURN END IF ENDIF @@ -717,13 +736,13 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & DET = dXFdHF*dZFdVF - dXFdVF*dZFdHF - if ( EqualRealNos( DET, 0.0_DbKi ) ) then + IF ( EqualRealNos( DET, 0.0_DbKi ) ) THEN !bjj: there is a serious problem with the debugger here when DET = 0 ErrStat = ErrID_Warn ErrMsg = ' Iteration not convergent (DET is 0). '// & ' Routine Catenary() cannot solve quasi-static mooring line solution.' - return - endif + RETURN + ENDIF dHF = ( -dZFdVF*EXF + dXFdVF*EZF )/DET ! This is the incremental change in horizontal tension at the fairlead as predicted by Newton-Raphson @@ -937,6 +956,19 @@ SUBROUTINE Catenary ( XF_In, ZF_In, L_In , EA_In, & ENDIF + IF (reverseFlag) THEN + ! Follows process of MoorPy catenary.py + s = s( size(s):1:-1 ) + X = X( size(X):1:-1 ) + Z = Z( size(Z):1:-1 ) + Te = Te( size(Te):1:-1 ) + DO I = 1,N + s(I) = L - s(I) + X(I) = XF - X(I) + Z(I) = Z(I) - ZF + ENDDO + ZF = -ZF ! Return to orginal value + ENDIF ! The Newton-Raphson iteration is only accurate in double precision, so ! convert the output arguments back into the default precision for real