|
10 | 10 | from collections import defaultdict |
11 | 11 | import famodel.platform.platform |
12 | 12 | import shapely as sh |
| 13 | +from famodel.helpers import wrap_angle_diff |
13 | 14 |
|
14 | 15 | class Anchor(Node): |
15 | 16 |
|
@@ -305,93 +306,184 @@ def makeMoorPyAnchor(self, ms): |
305 | 306 |
|
306 | 307 | return ms |
307 | 308 |
|
308 | | - """ |
309 | | - def calcAnchorLoads(self, level=0): |
| 309 | + |
| 310 | + def calcMudlineLoads(self, level=0, plot=False, mean_load_keys=None): |
310 | 311 | '''Compute anchor load envelope based on available information. |
311 | 312 | The 'level' input sets the type of analysis. Currently supported: |
312 | 313 | level 0 : quasi-static analysis based on mean offsets and surge |
313 | 314 | motion amplitudes. |
314 | | - ''' |
315 | 315 | |
| 316 | + level: int, optional |
| 317 | + analysis level. 0 is quasi-static. Default is 0. |
| 318 | + plot: bool, optional |
| 319 | + whether to plot the motions of the platforms. Default is False. |
| 320 | + mean_load_keys: list or str, optional |
| 321 | + key names of platform mean_load dict to describe which mean load types |
| 322 | + to sum and apply to the platform. Default is None, which will sum |
| 323 | + values of all mean_loads listed in platform.mean_loads |
| 324 | + ''' |
| 325 | + |
316 | 326 | # Identify attached moorings and platforms |
317 | 327 | moorings = [] |
318 | 328 | platforms = [] |
| 329 | + |
319 | 330 | for att in self.attachments.values(): |
320 | 331 | if isinstance(att['obj'], Mooring): |
321 | | - moorings.append(att) |
322 | 332 |
|
323 | 333 | # get the platform on the other end of the mooring |
324 | | - platform = att.attached_to[1-att[['end']] |
| 334 | + platform = att['obj'].attached_to[1-att['end']] |
325 | 335 |
|
326 | 336 | # if it's a normal platform, just save it |
327 | | - if ... |
| 337 | + if platform.entity.upper() == 'FOWT': |
328 | 338 | platforms.append(platform) |
| 339 | + moorings.append(att['obj']) |
329 | 340 | # if it's something else (like a buoy), get the platforms attached to it! |
330 | | - else: |
331 | | - for att2 in platform.attachments.values(): |
332 | | - if isinstance(att2['obj'], Mooring) and not att2==att: # look through each other mooring |
333 | | - ... |
334 | | - |
335 | | - moorings = [att for att in self.attachments.values() if isinstance(att['obj'], Mooring)] |
| 341 | + elif platform.entity.upper() == 'BUOY': |
| 342 | + for att2 in platform.getMoorings().values(): |
| 343 | + if isinstance(att2, Mooring) and not att2==att['obj']: # look through each other mooring |
| 344 | + moorings.append(att2) |
| 345 | + for att3 in att2.attached_to: |
| 346 | + if not isinstance(att3, Anchor): |
| 347 | + if att3.entity.upper() == 'FOWT': |
| 348 | + platforms.append(att3) |
| 349 | + |
| 350 | + ms = self.mpAnchor.sys |
| 351 | + ms.initialize() |
| 352 | + ms.solveEquilibrium() |
| 353 | + if plot: |
| 354 | + cols = ['red','orange','yellow','lime','green','turquoise','skyblue','blue','purple','magenta','goldenrod'] |
| 355 | + jj=0 |
| 356 | + fig,ax = plt.subplots() |
| 357 | + ax.scatter(self.r[0],self.r[1],marker='x') |
| 358 | + ax.scatter([pf.body.r6[0] for pf in platforms],[pf.body.r6[1] for pf in platforms],c='black') |
336 | 359 |
|
337 | 360 | # Calculate the loading envelope on the anchor |
338 | 361 | if level==0: # quasi-static analysis |
339 | | - loads = [] |
| 362 | + loads_h = [] |
| 363 | + loads_v = [] |
| 364 | + loads_env = [] |
| 365 | + # store initial loads |
| 366 | + loads_env.append(self.mpAnchor.getForces()) |
| 367 | + loads_h.append(np.linalg.norm(np.abs(loads_env[-1][:2]))) |
| 368 | + loads_v.append(loads_env[-1][2]) |
340 | 369 |
|
341 | 370 | # Look at a worst-case loads along each mooring direction |
342 | 371 | for mooring in moorings: |
343 | 372 | # get direction of mooring from anchor |
344 | | - heading = mooring. ... |
345 | | - |
346 | | - |
347 | | - # --- worst horizontal load --- |
| 373 | + heading = np.radians(90-(mooring.heading-180)) # mooring.heading is from direction of platform & compass |
| 374 | + u = [np.cos(heading), np.sin(heading)] |
348 | 375 |
|
| 376 | + # --- worst horizontal load --- |
349 | 377 | # Apply maximum steady load in that direction on each platform |
350 | 378 | for platform in platforms: |
351 | | - platform.body.f6ext = platform.maxLoad... |
| 379 | + platform.calcThrustTotal() |
| 380 | + if mean_load_keys == None: |
| 381 | + total_mean_load = np.sum([x for x in platform.mean_loads.values()]) |
| 382 | + elif isinstance(mean_load_keys,str): |
| 383 | + total_mean_load = platform.mean_loads[mean_load_keys] |
| 384 | + elif isinstance(mean_load_keys,(list,np.ndarray)): |
| 385 | + total_mean_load = np.sum([platform.mean_loads[x] for x in mean_load_keys]) |
| 386 | + platform.body.f6Ext = [total_mean_load*u[0], |
| 387 | + total_mean_load*u[1], |
| 388 | + 0,0,0,0] |
352 | 389 |
|
353 | 390 | # Compute array MoorPy system mean offset positions due to applied loads |
354 | | - # ms.solveEquilibrium(DOFtype='both') |
355 | | - |
| 391 | + ms.initialize() |
| 392 | + ms.solveEquilibrium(DOFtype='both') |
| 393 | + # store loads in horizontal and vertical separate |
| 394 | + loads_env.append(self.mpAnchor.getForces()) |
| 395 | + loads_h.append(np.linalg.norm(np.abs(loads_env[-1][:2]))) |
| 396 | + loads_v.append(loads_env[-1][2]) |
| 397 | + |
356 | 398 | # Add motion amplitude in loading direction to the mean offsets of each platform |
357 | 399 | for platform in platforms: |
358 | 400 | # platform.x_amp is if we somehow stored max surge amplitude info in each platform <<< |
359 | | - x = platform.x_amp*np.cos(heading) # displacement amplitude in x |
360 | | - y = platform.x_amp*np.sin(heading) # displacement amplitude in y |
| 401 | + x = platform.x_ampl*u[0] # displacement amplitude in x |
| 402 | + y = platform.x_ampl*u[1] # displacement amplitude in y |
361 | 403 | platform.body.r6 += np.array([ x, y, 0,0,0,0]) |
| 404 | + |
| 405 | + # Compute mooring tensions based on these positions, then sum up anchor tensions |
| 406 | + ms.initialize() |
| 407 | + ms.solveEquilibrium() |
| 408 | + # store loads in horizontal and vertical |
| 409 | + loads_env.append(self.mpAnchor.getForces()) |
| 410 | + loads_h.append(np.linalg.norm(np.abs(loads_env[-1][:2]))) |
| 411 | + loads_v.append(loads_env[-1][2]) |
362 | 412 |
|
363 | | - # Compute mooring tensions based on these positions, the sum up anchor tensions |
364 | | - # ms.solveEquilibrium(... |
365 | | - loads.append(self.mpAnchor.getForces()) |
| 413 | + # reset to remove added platform displacement but keep external thrust |
| 414 | + ms.solveEquilibrium(DOFtype='both') |
366 | 415 |
|
367 | 416 |
|
368 | 417 | # --- worst vertical load (assumes one upwind platform is failed/unloaded) --- |
369 | | - |
370 | | - # figure out which platform is most upwind and remove it's external load |
371 | | - idle_platform = ... |
372 | | - idle_platform.body.f6ext = np.zeros(3) |
373 | | - |
374 | | - # Compute array MoorPy system mean offset positions due to applied loads |
375 | | - # ms.solveEquilibrium(DOFtype='both') |
376 | | - |
377 | | - # Add motion amplitude in spreading-out direction to the mean offsets of each platform |
378 | | - for platform in platforms: |
379 | | - x_amp = platform.x_amp |
380 | | - heading_to_platform = ... |
381 | | - x = x_amp*np.cos(heading_to_platform) # displacement amplitude in x |
382 | | - y = x_amp*np.sin(heading_to_platform) # displacement amplitude in y |
383 | | - platform.body.r6 += np.array([ x, y, 0,0,0,0]) |
384 | | - |
385 | | - # Compute mooring tensions based on these positions, the sum up anchor tensions |
386 | | - # ms.solveEquilibrium(... |
387 | | - loads.append(self.mpAnchor.getForces()) |
388 | | - |
389 | | - |
390 | | - # maybe process the loads to make an envelope of the worst horizontal-vertical loads |
| 418 | + # get heading of mooring and ensure in bounds of 0-360. Opposite of heading above. |
| 419 | + if len(moorings)>1: # this is a shared anchor |
| 420 | + mooring_heading = np.radians((90-(mooring.heading))%360) |
| 421 | + if mooring_heading <0: |
| 422 | + mooring_heading = mooring_heading+2*np.pi |
| 423 | + |
| 424 | + # figure out which platform is most upwind and remove its external load |
| 425 | + pf_r_rels = [pf.r-self.r for pf in platforms] # get xy position of platform relative to anchor |
| 426 | + pf_heads = np.array([np.arctan2(pf_r[1],pf_r[0]) for pf_r in pf_r_rels]) # calculate heading from rel position |
| 427 | + # make sure heading is between 0-360 |
| 428 | + for i,h in enumerate(pf_heads): |
| 429 | + if h <0: |
| 430 | + pf_heads[i] = h+2*np.pi |
| 431 | + elif h>2*np.pi: |
| 432 | + pf_heads[i] = h-2*np.pi |
| 433 | + |
| 434 | + # get difference between angles, wrapping around to be between -180:180 |
| 435 | + idx = np.abs(wrap_angle_diff(pf_heads,mooring_heading,degrees=False)).argmin() |
| 436 | + # pull out platform with smallest diff between and remove external force |
| 437 | + idle_platform = platforms[idx] |
| 438 | + idle_platform.body.f6Ext = np.zeros(6) |
| 439 | + |
| 440 | + |
| 441 | + # Compute array MoorPy system mean offset positions due to applied loads |
| 442 | + ms.initialize() |
| 443 | + ms.solveEquilibrium(DOFtype='both') |
| 444 | + |
| 445 | + # store horizontal and vertical loads |
| 446 | + loads_env.append(self.mpAnchor.getForces()) |
| 447 | + loads_h.append(np.linalg.norm(np.abs(loads_env[-1][:2]))) |
| 448 | + loads_v.append(loads_env[-1][2]) |
| 449 | + if plot: |
| 450 | + ax.scatter([pf.body.r6[0] for pf in platforms],[pf.body.r6[1] for pf in platforms],c=cols[jj]) |
| 451 | + jj +=1 |
| 452 | + |
| 453 | + # Add motion amplitude in spreading-out direction to the mean offsets of each platform |
| 454 | + for i,platform in enumerate(platforms): |
| 455 | + x_amp = platform.x_ampl |
| 456 | + heading_to_platform = pf_heads[i] |
| 457 | + x = x_amp*np.cos(heading_to_platform) # displacement amplitude in x |
| 458 | + y = x_amp*np.sin(heading_to_platform) # displacement amplitude in y |
| 459 | + platform.body.r6 += np.array([ x, y, 0,0,0,0]) |
| 460 | + |
| 461 | + # Compute mooring tensions based on these positions, then sum up anchor tensions |
| 462 | + ms.initialize() |
| 463 | + ms.solveEquilibrium() |
| 464 | + if plot: |
| 465 | + ax.scatter([pf.body.r6[0] for pf in platforms],[pf.body.r6[1] for pf in platforms],c=cols[jj]) |
| 466 | + jj+=1 |
| 467 | + loads_env.append(self.mpAnchor.getForces()) |
| 468 | + loads_h.append(np.linalg.norm(np.abs(loads_env[-1][:2]))) |
| 469 | + loads_v.append(loads_env[-1][2]) |
| 470 | + |
| 471 | + self.loads['Hm'] = np.max(loads_h) |
| 472 | + self.loads['Vm'] = np.max(loads_v) |
| 473 | + self.loads['thetam'] = np.degrees(np.arctan2(self.loads['Vm'], |
| 474 | + self.loads['Hm'])) |
| 475 | + self.loads['mudline_load_type'] = 'max_force' |
| 476 | + self.loads['info'] = 'mudline loads determined with MoorPy' |
| 477 | + self.envelope['max_loads'] = loads_env |
391 | 478 |
|
| 479 | + # remove loads and set everything back to initial positions |
| 480 | + for pf in platforms: |
| 481 | + pf.body.f6Ext = np.array([0,0,0,0,0,0]) |
| 482 | + ms.initialize() |
| 483 | + ms.solveEquilibrium(DOFtype='both') |
392 | 484 | else: |
393 | 485 | raise Exception('Only level 0 is supported so far') |
394 | | - """ |
| 486 | + |
395 | 487 |
|
396 | 488 | def getLineProperties(self): |
397 | 489 | ''' |
|
0 commit comments