Skip to content

Commit

Permalink
Subsystem updates for dynamic tensions, new PointProps helpers:
Browse files Browse the repository at this point in the history
- helpers.py: added simple/placeholder loadPointProps and getPointProps for
  doing similarly as lineProps for connectors that could be MoorPy Points.
- SubSystem:
- - Added initialization method to initialize DAFs, etc.
- - Variables Te0/M/D now store undisplaced/mean/dynamic tensions of each section.
- - makeGeneric now has an optional "connectors" input that contains property
    info for connectors along the line.
- - added setDynamicOffset method, which includes dynamic stiffness and DAFs.
  • Loading branch information
mattEhall committed Jun 5, 2024
1 parent 976c09d commit 9e3237b
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 17 deletions.
69 changes: 68 additions & 1 deletion moorpy/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ def step_func(X, args, Y, oths, Ytarget, err, tols, iter, maxIter):
m,b = np.polyfit(Es[int(0.7*iter):iter,0], Xs[int(0.7*iter):iter,0], 1)
X = np.array([b])
Y = np.array([0.0])
print(f"Using linaer fit to estimate solution at X={b}")
if display>0: print(f"dsolve is using linear fit to estimate solution at X={b}")

break

Expand Down Expand Up @@ -807,6 +807,73 @@ def loadLineProps(source):
return output




def getPointProps(weight, rho=1025.0, g=9.81, **kwargs):
'''for now this is just getClumpMV put in a place where it could grow
into a fully versatile equivalent to getMoorProps.
'''

'''A function to provide a consistent scheme for converting a clump weight/float magnitude to the
mass and volume to use in a MoorPy Point.'''

if weight >= 0: # if the top point of the intermediate line has a clump weight
pointvol = 0.0
pointmass = weight*1000.0 # input variables are in units of tons (1000 kg), convert to kg
else:
pointvol = -weight*1200.0/rho # input variables are still in tons. Assume additional 20% of BM mass
pointmass = -weight*200.0

return dict(m=pointmass, v=pointvol)


def loadPointProps(source):
'''Loads a set of MoorPy point property scaling coefficients from
a specified YAML file or passed dictionary.
Parameters
----------
source : dict or filename
YAML file name or dictionary containing line property scaling coefficients
Returns
-------
dictionary
PointProps dictionary listing each supported mooring line type and
subdictionaries of scaling coefficients for each.
'''

'''
if type(source) is dict:
source = source
elif source is None or source=="default":
import os
mpdir = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(mpdir,"PointProps_default.yaml")) as file:
source = yaml.load(file, Loader=yaml.FullLoader)
elif type(source) is str:
with open(source) as file:
source = yaml.load(file, Loader=yaml.FullLoader)
else:
raise Exception("loadLineProps supplied with invalid source")
if 'lineProps' in source:
lineProps = source['lineProps']
else:
raise Exception("YAML file or dictionary must have a 'lineProps' field containing the data")
'''

output = dict() # output dictionary combining default values with loaded coefficients

#output['generic'] = dict(rho = , m_v = , )

return output



def getFromDict(dict, key, shape=0, dtype=float, default=None):
'''
Function to streamline getting values from design dictionary from YAML file, including error checking.
Expand Down
108 changes: 92 additions & 16 deletions moorpy/subsystem.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import numpy as np
import yaml

Expand Down Expand Up @@ -80,6 +79,9 @@ def __init__(self, mooringSys=None, num=0, depth=0, rho=1025, g=9.81,
self.rho = rho # water density [kg/m^3]
self.g = g # gravitational acceleration [m/s^2]

# Set position tolerance to use in equilibrium solves [m]
self.eqtol = getFromDict(kwargs, 'eqtol', default=0.01)

# water current - currentMod 0 = no current; 1 = steady uniform current
self.currentMod = 0 # flag for current model to use
self.current = np.zeros(3) # current velocity vector [m/s]
Expand Down Expand Up @@ -117,9 +119,36 @@ def __init__(self, mooringSys=None, num=0, depth=0, rho=1025, g=9.81,

self.MDoptions = {} # dictionary that can hold any MoorDyn options read in from an input file, so they can be saved in a new MD file if need be
self.qs = 1 # flag that it's a MoorPy analysis, so System methods don't complain. One day should replace this <<<



def makeGeneric(self, lengths, types, suspended=0):
def initialize(self, daf_dict={}):
'''Initialize the Subsystem including DAFs.'''

# Count number of line sections and total number of nodes
self.nLines = len(self.lineList)
self.nNodes = np.sum([l.nNodes for l in self.lineList]) - self.nLines + 1

# Use the System initialize method
System.initialize(self)

# dynamic amplication factor for each line section, and anchor forces
# (DAFS[-2] is for vertical load, DAFS[-1] is for horizontal load)
self.DAFs = getFromDict(daf_dict, 'DAFs', shape=self.nLines+2, default=1.0)

# mean tension [N] of each line section end [section #, end A/B] for any given mean offset
self.Te0 = np.zeros([self.nLines,2]) # undispalced values
self.TeM = np.zeros([self.nLines,2]) # mean offset values
self.TeD = np.zeros([self.nLines,2]) # dynamic values

# adjustment on laylength... positive means that the dynamic lay length is greater than linedesign laylength
self.LayLen_adj = getFromDict(daf_dict, 'LayLen_adj', shape=0, default=0.0)

# Fatigue damage over all nodes (placeholder for now)
self.damage = np.zeros(self.nNodes)


def makeGeneric(self, lengths, types, connectors=[], suspended=0):
'''Creates a cable of n components going between an anchor point and
a floating body (or a bridle point). If shared, it goes between two
floating bodies.
Expand All @@ -134,6 +163,9 @@ def makeGeneric(self, lengths, types, suspended=0):
these names must match keys in the parent system lineTypes
dictionary or the subsystem's lineTypes dictionary. If dicts,
these dicts are referred to for each lineType (by reference).
connectors : list of dicts
List of length nLines-1 with dicts of optional properties for
any interior points (between sections).
suspended : int
Selector shared/suspended cases:
- 0 (default): end A is on the seabed,
Expand All @@ -143,6 +175,10 @@ def makeGeneric(self, lengths, types, suspended=0):

# some initialization steps.
self.nLines = len(lengths)
if len(connectors) == 0:
connectors = [{}]*(self.nLines - 1)
elif not len(connectors) == self.nLines - 1:
raise Exception('Length of connectors must be nLines - 1')

if not len(types)==self.nLines:
raise Exception("The specified number of lengths and types is inconsistent.")
Expand All @@ -166,7 +202,7 @@ def makeGeneric(self, lengths, types, suspended=0):
self.addPoint(-1, rA, DOFs=[2]) # add shared line point, free only to move in z
else:
self.addPoint(-1, rA, DOFs=[0,2]) # add anchor point

# Go through each line segment and add its upper point, add the line, and connect the line to the points
for i in range(self.nLines):

Expand All @@ -189,19 +225,18 @@ def makeGeneric(self, lengths, types, suspended=0):

# add the upper end point of the segment
if i==self.nLines-1: # if this is the upper-most line
self.addPoint(-1, rB, DOFs=[0,2]) # add the fairlead point (make it coupled)
self.addPoint(-1, rB, DOFs=[0,2]) # add the fairlead point (make it coupled)
#self.bodyList[0].attachPoint(i+2, rB) # attach the fairlead point to the body (two points already created)
else: # if this is an intermediate line
m = connectors[i].get('m', 0)
v = connectors[i].get('v', 0)
# add the point, initializing linearly between anchor and fairlead/midpoint
self.addPoint(0, rA + (rB-rA)*Lcsum[i]/Lcsum[-1], DOFs=[0,2])

self.addPoint(0, rA + (rB-rA)*Lcsum[i]/Lcsum[-1], m=m, v=v, DOFs=[0,2])

# attach the line to the points
self.pointList[-2].attachLine(i+1, 0) # attach end A of the line
self.pointList[-1].attachLine(i+1, 1) # attach end B of the line

# if a points list is provided, apply any mass or other properties it contains?



def setEndPosition(self, r, endB, sink=False):
'''Sets either end position of the subsystem in the global/system
Expand Down Expand Up @@ -235,7 +270,7 @@ def setEndPosition(self, r, endB, sink=False):
raise LineError("setEndPosition: endB value has to be either 1 or 0")


def staticSolve(self, reset=False, tol=0.01, profiles=0):
def staticSolve(self, reset=False, tol=0, profiles=0):
'''Solve internal equilibrium of the Subsystem and saves the forces
and stiffnesses at the ends in the global reference frame. All the
This method mimics the behavior of the Line.staticSolve method and
Expand All @@ -244,6 +279,9 @@ def staticSolve(self, reset=False, tol=0.01, profiles=0):
frame are also saved.
'''

if tol==0:
tol=self.eqtol

# transform end positions to SubSystem internal coordinate system
# inputs are self.rA, rB in global frame
# outputs should be pointList[0] and [N] .r
Expand Down Expand Up @@ -401,10 +439,47 @@ def setOffset(self, offset, z=0):
rBFair setting. Optional argument z can be added for a z offset.
'''

# Use static EA values and unstretched lengths
self.revertToStaticStiffness()

# Ensure end A position and set end B position to offset values
self.rA = np.array([-self.span, 0, self.rA[2]])
self.rB = np.array([-self.rBFair[0] + offset, 0, self.rBFair[2]+z])

# should we also do the static solve now?
self.staticSolve(tol=self.eqtol) # solve the subsystem

# Store some values at this offset position that may be used later
for i, line in enumerate(self.lineList):
self.TeM[i,0] = np.linalg.norm(line.fA)
self.TeM[i,1] = np.linalg.norm(line.fB)

self.anchorFx0 = self.lineList[0].fA[0]
self.anchorFz0 = self.lineList[0].fA[2]

self.TeD = np.copy(self.TeM) # set the dynamic values as well, in case they get queried right away


def setDynamicOffset(self, offset, z=0):
'''Moves end B of the Subsystem to represent a dynamic offset from
the previous offset location. Uses dynamic stiffness values and also
applies dynamic amplification factors (DAFs) on the difference from
the mean tensions (which would have been calculated in getOffset).
End A is set based on the 'span' (shouldn't change),
and B is set based on offset and the rBFair setting.
Optional argument z can be added for a z offset.
'''

self.activateDynamicStiffness() # use dynamic EA values

# adjust end B to the absolute offsets specified
self.rB = np.array([-self.rBFair[0] + offset, 0, self.rBFair[2]+z])

self.staticSolve(tol=self.eqtol) # solve the subsystem

# Store dynamic values at this offset position that may be used later
for i, line in enumerate(self.lineList):
self.TeD[i,:] = self.TeM[i,:] + self.DAFs[i]*( np.array([line.TA, line.TB]) - self.TeM[i,:] )



def activateDynamicStiffness(self, display=0):
Expand Down Expand Up @@ -484,14 +559,17 @@ def getMinSag(self):

def getTen(self, iLine):
'''Compute the end (maximum) tension for a specific line, including
a dynamic amplification factor.'''
a dynamic amplification factor.'''
line = self.lineList[iLine]

'''
dynamicTe = max([ self.Te0[iLine,0] + self.DAFs[iLine]*(np.linalg.norm(line.fA) - self.Te0[iLine,0]),
self.Te0[iLine,1] + self.DAFs[iLine]*(np.linalg.norm(line.fB) - self.Te0[iLine,1])])
'''
dynamicTe = max( self.TeD[iLine,:] )

return dynamicTe


def getTenSF(self, iLine):
'''Compute MBL/tension for a specific line.'''
return self.lineList[iLine].type['MBL'] / self.getTen(iLine)
Expand Down Expand Up @@ -537,8 +615,6 @@ def calcCurvature(self):
be in equilibrium. Also populates nodal values for S, X, Y, Z, T, and K
along the full length.'''

self.nNodes = np.sum([l.nNodes for l in self.lineList]) - self.nLines + 1

n = self.nNodes

self.Ss = np.zeros(n) # arc length along line assembly (unstretched) [m]
Expand Down Expand Up @@ -620,4 +696,4 @@ def loadData(self, dirname, rootname, line_IDs):

for i, line in enumerate(self.lineList):
line.loadData(dirname, rootname, line_IDs[i])

0 comments on commit 9e3237b

Please sign in to comment.