diff --git a/examples/01_Visualization/02_visual_moorings.yaml b/examples/01_Visualization/02_visual_moorings.yaml index 51dd390f..30d86fae 100644 --- a/examples/01_Visualization/02_visual_moorings.yaml +++ b/examples/01_Visualization/02_visual_moorings.yaml @@ -30,11 +30,11 @@ mooring_systems: ms1: name: 2-line semi-taut polyester mooring system with a third line shared - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType] data: - - [ semitaut-poly_1, 150 , drag-embedment1, 0 ] - - [ semitaut-poly_1, 270 , drag-embedment1, 0 ] - - [ semitaut-poly_1, 30 , drag-embedment1, 0 ] + - [ semitaut-poly_1, 150 , drag-embedment1] + - [ semitaut-poly_1, 270 , drag-embedment1] + - [ semitaut-poly_1, 30 , drag-embedment1] # Mooring line configurations diff --git a/examples/01_Visualization/03_visual_cables.py b/examples/01_Visualization/03_visual_cables.py index 806164f8..769c2fb5 100644 --- a/examples/01_Visualization/03_visual_cables.py +++ b/examples/01_Visualization/03_visual_cables.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ Simple driver file to create a 2d plot of an platform locations with -mooring lines in an array. +cables in an array. The input file only contains the bare minimum information to build a 2d plot -of the turbine locations and moorings (no cables, platform design, turbines, +of the turbine locations and cables (no moorings, platform design, turbines, site condition information, etc.) """ diff --git a/examples/01_Visualization/03_visual_cables.yaml b/examples/01_Visualization/03_visual_cables.yaml index 4963f67b..aeee9e05 100644 --- a/examples/01_Visualization/03_visual_cables.yaml +++ b/examples/01_Visualization/03_visual_cables.yaml @@ -35,7 +35,6 @@ dynamic_cable_configs: A: 300 # cable conductor area [mm^2] cable_type: dynamic_cable_66 # ID of a cable section type from famodel/cables/cableProps_default.yaml. Cable props loaded automatically from this! length: 353.505 # [m] length (unstretched) - rJTube : 5 # [m] radial distance from center of platform that J-tube is located sections: - type: Buoyancy_750m # name of buoy type from famodel/cables/cableProps_default.yaml - buoy design info read in automatically from this! @@ -51,8 +50,7 @@ dynamic_cable_configs: span: 1512 # [m] cable_type: dynamic_cable_66 # ID of a cable section type from famodel/cables/cableProps_default.yaml. Cable props loaded automatically from this! A: 300 # cable conductor area [mm^2] - length: 1550 # [m] length (unstretched) - rJTube : 58 # [m] radial distance from center of platform that J-tube is located + length: 1650 # [m] length (unstretched) sections: - type: Buoyancy_750m diff --git a/examples/08_Design_Adjustment/01_Fairleads.yaml b/examples/08_Design_Adjustment/01_Fairleads.yaml new file mode 100644 index 00000000..4fa235bd --- /dev/null +++ b/examples/08_Design_Adjustment/01_Fairleads.yaml @@ -0,0 +1,68 @@ + +# ----- Array-level inputs ----- + +# Wind turbine array layout +array: + keys : [ID, topsideID, platformID, mooringID, x_location, y_location, heading_adjust] + data : # ID# ID# ID# [m] [m] [deg] + - [fowt0, 0, 1, ms1, -1600, -1600, 180 ] + - [fowt1, 0, 1, ms1, 0, -1600, 0 ] + - [fowt2, 0, 1, ms1, 1600, -1600, 0 ] + - [fowt3, 0, 1, ms1, -1600, 0, 0 ] + - [fowt4, 0, 1, ms1, 0, 0, 45 ] + - [fowt5, 0, 1, ms1, 1600, 0, 0 ] + - [fowt6, 0, 1, ms1, -1600, 1600, 0 ] + - [fowt7, 0, 1, ms1, 0, 1600, 0 ] + - [fowt8, 0, 1, ms1, 1600, 1600, 0 ] + +platform: + type : FOWT + fairleads : # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name : fairlead1 + r_rel : [58, 0, -14] + headings : [30, 150, 270] + + + + +# ----- Mooring system ----- + +# Mooring system descriptions (each for an individual FOWT with no sharing) +mooring_systems: + + ms1: + name: 2-line semi-taut polyester mooring system with a third line shared + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ semitaut-poly_1, 135 , drag-embedment1, 2] + - [ semitaut-poly_1, 270 , drag-embedment1, 3] + - [ semitaut-poly_1, 45 , drag-embedment1, 1] + + +# Mooring line configurations +mooring_line_configs: + + semitaut-poly_1: # mooring line configuration identifier, matches MooringConfigID + + name: Semitaut polyester configuration 1 # descriptive name + + span: 642 # 2D x-y distance from fairlead to anchor + + sections: #in order from anchor to fairlead + - mooringFamily: chain # ID of a mooring line section type + d_nom: .1549 # nominal diameter of material [m] + length: 497.7 # [m] usntretched length of line section + - mooringFamily: polyester # ID of a mooring line section type + d_nom: .182 # nominal diameter of material [m] + length: 199.8 # [m] length (unstretched) + + + +# Anchor type properties +anchor_types: + + drag-embedment1: + type : DEA # type of anchor (drag-embedment anchor) + + diff --git a/examples/08_Design_Adjustment/02_Jtubes.py b/examples/08_Design_Adjustment/02_Jtubes.py new file mode 100644 index 00000000..dd00f9cf --- /dev/null +++ b/examples/08_Design_Adjustment/02_Jtubes.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" +Simple driver file to create a 2d plot of an platform locations with +cables in an array using Jtubes to define platform connection location. +The input file only contains the bare minimum information to build a 2d plot +of the turbine locations and cables connected to Jtubes (no moorings, platform design, turbines, + site condition information, etc.) +""" + +from famodel import Project +import matplotlib.pyplot as plt + +# define name of ontology input file +input_file = '02_Jtubes.yaml' + +# initialize Project class with input file, we don't need RAFT for this so mark False +project = Project(file=input_file,raft=False) + +# plot +project.plot2d() + +# to plot cables in 3d, we'll need to add depth and create a moorpy model of the system +project.depth = 200 # depth added because we did not include the site conditions section of the yaml +project.getMoorPyArray() +project.plot3d() + +plt.show() diff --git a/examples/08_Design_Adjustment/02_Jtubes.yaml b/examples/08_Design_Adjustment/02_Jtubes.yaml new file mode 100644 index 00000000..95d926fa --- /dev/null +++ b/examples/08_Design_Adjustment/02_Jtubes.yaml @@ -0,0 +1,71 @@ + +# ----- Array-level inputs ----- + +# Wind turbine array layout +array: + keys : [ID, topsideID, platformID, mooringID, x_location, y_location, heading_adjust] + data : # ID# ID# ID# [m] [m] [deg] + - [fowt0, 0, 1, 0, -1600, -1600, 0 ] + - [fowt1, 0, 1, 0, 0, -1600, 0 ] + - [fowt2, 0, 1, 0, 1600, -1600, 0 ] + - [fowt3, 0, 1, 0, -1600, 0, 0 ] + - [fowt4, 0, 1, 0, 0, 0, 0 ] + - [fowt5, 0, 1, 0, 1600, 0, 0 ] + - [fowt6, 0, 1, 0, -1600, 1600, 0 ] + - [fowt7, 0, 1, 0, 0, 1600, 0 ] + - [fowt8, 0, 1, 0, 1600, 1600, 0 ] + +platform: + type : FOWT + Jtubes : + - name: Jtube1 + r : 5 + z : -20 + headings : [90, 210, 330] # headings in degrees for the Jtube (if multiple headings, the Jtube will be repeated for each heading) + +# Array cables +array_cables: + keys: [ AttachA, AttachB, DynCableA, DynCableB, headingA, headingB, JtubeA, JtubeB, cableType] + data: + - [ fowt0, fowt1, suspended_1, None, 90, 270, 1, 2, None] # suspended cable, so only one dynamic cable configuration, no static cable + - [ fowt1, fowt2, lazy_wave1, lazy_wave1, 90, 270, 1, 3, static_cable_66] + +# Dynamic and cable configurations +dynamic_cable_configs: +# contains the subsections that make up each section of the subsea cable (i.e., what sections make up the lazywave cable in array_cable_1) + lazy_wave1: + name: Lazy wave configuration 1 (simpler approach) + voltage: 66 # [kV] + span : 195 # [m] horizontal distance to end of dynamic cable from attachment point + A: 300 # cable conductor area [mm^2] + cable_type: dynamic_cable_66 # ID of a cable section type from famodel/cables/cableProps_default.yaml. Cable props loaded automatically from this! + length: 353.505 # [m] length (unstretched) + + sections: + - type: Buoyancy_750m # name of buoy type from famodel/cables/cableProps_default.yaml - buoy design info read in automatically from this! + L_mid: 200 # [m] from platform connection + N_modules: 6 + spacing: 11.23 # [m] + V: 1 # [m^3] + + + suspended_1: + name: Dynamic suspended cable configuration 1 + voltage: 33 # [kV] + span: 1512 # [m] + cable_type: dynamic_cable_66 # ID of a cable section type from famodel/cables/cableProps_default.yaml. Cable props loaded automatically from this! + A: 300 # cable conductor area [mm^2] + length: 1650 # [m] length (unstretched) + + sections: + - type: Buoyancy_750m + L_mid: 510 # [m] from end A + N_modules: 6 + spacing: 18 # [m] + V: 2 # [m^3] + + - type: Buoyancy_750m + L_mid: 1040 # [m] from end A + N_modules: 6 + spacing: 18 # [m] + V: 2 # [m^3] \ No newline at end of file diff --git a/examples/08_Design_Adjustment/02_fairleads.py b/examples/08_Design_Adjustment/02_fairleads.py new file mode 100644 index 00000000..d1f65f08 --- /dev/null +++ b/examples/08_Design_Adjustment/02_fairleads.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +""" +Simple driver file to create an array showing moorings attached to fairelead objects. + +This allows you to connect moorings to platforms at a specific point and then run +mooring headings independent of the heading of this connection point. +The input file only contains the bare minimum information to build a 2d plot +of the turbine locations and moorings with fairleads (no cables, platform design, turbines, + site condition information, etc.) +""" + +from famodel import Project +import matplotlib.pyplot as plt + +# define name of ontology input file +input_file = '01_Fairleads.yaml' + +# initialize Project class with input file, we don't need RAFT for this so mark False +project = Project(file=input_file,raft=False) + +# plot +project.plot2d() + + +# to moorings plot in 3d, we'll need to add depth and create a moorpy model of the system +project.depth = 200 # depth added because we did not include the site conditions section of the yaml +project.getMoorPyArray() +project.plot3d() + +plt.show() + diff --git a/examples/08_Design_Adjustment/02_rotations.py b/examples/08_Design_Adjustment/02_rotations.py new file mode 100644 index 00000000..23bc0026 --- /dev/null +++ b/examples/08_Design_Adjustment/02_rotations.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Simple driver file to create an array with moorings and rotate the platforms and array. + +This allows you to rotate platforms including all of their moorings, anchors, and fairleads +The input file only contains the bare minimum information to build a 2d plot +of the turbine locations and moorings with fairleads (no cables, platform design, turbines, + site condition information, etc.) +""" + +from famodel import Project +import matplotlib.pyplot as plt + +# define name of ontology input file +input_file = '01_Fairleads.yaml' + +# initialize Project class with input file, we don't need RAFT for this so mark False +project = Project(file=input_file,raft=False) + +# plot +project.plot2d() + +# let's rotate a platform but keep in the same x,y position +pf_loc = project.platformList['fowt0'].r +new_heading = 143 # [deg] +project.platformList['fowt0'].setPosition(r=pf_loc, heading=new_heading, + degrees=True, project=project) + +# plot again to see the difference +project.plot2d() + +# let's now change the platform's position +new_r = [-2000, -2200] +project.platformList['fowt0'].setPosition(r=new_r, project=project) + +# plot again +project.plot2d() + + diff --git a/examples/Inputs/output_MD.dat b/examples/Inputs/output_MD.dat deleted file mode 100644 index 38578132..00000000 --- a/examples/Inputs/output_MD.dat +++ /dev/null @@ -1,55 +0,0 @@ -MoorDyn v2 Input File -Generated by MoorPy ----------------------- LINE TYPES -------------------------------------------------- -TypeName Diam Mass/m EA BA/-zeta EI Cd Ca CdAx CaAx -(name) (m) (kg/m) (N) (N-s/-) (N-m^2) (-) (-) (-) (-) -0_chain 0.2788 479.88 2.054e+09 -1.000e+00 0.000e+00 1.333 1.000 0.64 0.50 -1_polyester 0.1441 22.49 1.428e+08 -1.000e+00 0.000e+00 2.021 1.100 0.00 0.15 ---------------------- ROD TYPES ----------------------------------------------------- -TypeName Diam Mass/m Cd Ca CdEnd CaEnd -(name) (m) (kg/m) (-) (-) (-) (-) ------------------------ BODIES ------------------------------------------------------ -ID Attachment X0 Y0 Z0 r0 p0 y0 Mass CG* I* Volume CdA* Ca* -(#) (-) (m) (m) (m) (deg) (deg) (deg) (kg) (m) (kg-m^2) (m^3) (m^2) (-) -1 free -1499.91 1499.96 0.00 0.00 0.00 -0.00 1.9911e+07 0.00|0.00|-2.54 0.000e+00 19480.10 0.00 0.00 ----------------------- RODS --------------------------------------------------------- -ID RodType Attachment Xa Ya Za Xb Yb Zb NumSegs RodOutputs -(#) (name) (#/key) (m) (m) (m) (m) (m) (m) (-) (-) ----------------------- POINTS ------------------------------------------------------- -ID Attachment X Y Z Mass Volume CdA Ca -(#) (-) (m) (m) (m) (kg) (m^3) (m^2) (-) -1 Fixed -1150.00 893.78 -204.21 0.00 0.00 0.00 0.00 -2 Free -1391.15 1311.56 -137.43 140.00 0.13 0.00 0.00 -3 Coupled -1470.91 1449.73 -14.00 0.00 0.00 0.00 0.00 -4 Fixed -2200.00 1500.00 -203.86 0.00 0.00 0.00 0.00 -5 Free -1717.38 1499.97 -137.30 140.00 0.13 0.00 0.00 -6 Coupled -1557.91 1499.96 -14.00 0.00 0.00 0.00 0.00 -7 Fixed -1150.00 2106.22 -204.07 0.00 0.00 0.00 0.00 -8 Free -1391.18 1688.34 -137.38 140.00 0.13 0.00 0.00 -9 Coupled -1470.91 1550.19 -14.00 0.00 0.00 0.00 0.00 ----------------------- LINES -------------------------------------------------------- -ID LineType AttachA AttachB UnstrLen NumSegs LineOutputs -(#) (name) (#) (#) (m) (-) (-) -1 0_chain 1 2 497.700 10 p -2 1_polyester 2 3 199.800 10 p -3 0_chain 4 5 497.700 10 p -4 1_polyester 5 6 199.800 10 p -5 0_chain 7 8 497.700 10 p -6 1_polyester 8 9 199.800 10 p ----------------------- OPTIONS ------------------------------------------------------ -0.001 dtM -3000000.0 kb -300000.0 cb -60 TmaxIC -9.81 g -0 depth -1025 rho ------------------------ OUTPUTS ----------------------------------------------------- -FairTen1 -FairTen2 -FairTen3 -FairTen4 -FairTen5 -FairTen6 -END ---------------------- need this line ------------------------------------------------ diff --git a/examples/Inputs/Moordyn_semitaut200m.dat b/examples/Moordyn_semitaut200m.dat similarity index 100% rename from examples/Inputs/Moordyn_semitaut200m.dat rename to examples/Moordyn_semitaut200m.dat diff --git a/examples/Inputs/OntologySample200m.yaml b/examples/OntologySample200m.yaml similarity index 95% rename from examples/Inputs/OntologySample200m.yaml rename to examples/OntologySample200m.yaml index a59e0a1c..ee348243 100644 --- a/examples/Inputs/OntologySample200m.yaml +++ b/examples/OntologySample200m.yaml @@ -30,7 +30,7 @@ site: -[x2, y2] bathymetry: - file: './bathymetry200m_sample.txt' + file: 'bathymetry200m_sample.txt' seabed: x : [-10901, 0, 10000] @@ -112,12 +112,6 @@ array: - [FOWT1, 1, 1, ms3, -1600, -1600, 0, 180 ] # 2 turbine array - [FOWT2, 1, 1, ms3, 0, -1600, 0, 0 ] - [OSS1, 2, 2, ms3, 1600, -1600, 0, 180 ] # substation - # - [FOWT3, 1, 1, ms3, -1600, 0, 0, 0 ] - # - [FOWT5, 1, 1, ms3, 0, 0, 0, 45 ] - # - [FOWT6, 1, 1, ms3, 1600, 0, 0, 0 ] - # - [FOWT7, 1, 1, ms3, -1600, 1600, 0, 0 ] - # - [FOWT8, 1, 1, ms3, 0, 1600, 0, 0 ] - # - [FOWT9, 1, 1, ms3, 1600, 1600, 0, 0 ] # Array-level mooring system (in addition to any per-turbine entries later) @@ -127,16 +121,17 @@ array_mooring: anchor_data : line_keys : - [MooringConfigID , endA, endB, headingA, headingB, lengthAdjust] + [MooringConfigID , endA, endB, fairleadA, fairleadB] line_data : - # - [ rope_shared , FOWT1, FOWT2, 270, 270, 0] + # - [ rope_shared , FOWT1, FOWT2, 0, 0, None] # shared mooring doesn't need a heading specification + # - [ semitaut-poly_1, FOWT1, Anchor1, None, 0, 30] # shared anchor doesn't need a fairleadA specification # Array cables array_cables: - keys: [ AttachA, AttachB, DynCableA, DynCableB, headingA, headingB, cableType] + keys: [ AttachA, AttachB, DynCableA, DynCableB, JTubeA, JTubeB, headingA, headingB, cableType] data: - - [ FOWT2, OSS1, suspended_1, None, 90, 90, None] # suspended cable, so only one dynamic cable configuration, no static cable + - [ FOWT2, OSS1, suspended_1, None, 2, 2, 90, 90, None] # suspended cable, so only one dynamic cable configuration, no static cable # ----- Mooring system ----- @@ -147,11 +142,11 @@ mooring_systems: ms3: name: 3-line semi-taut polyester mooring system - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead, lug] # fairlead and lug listings are optional; if not specified, fairlead list follows order of fairleads in the platform definition data: - - [ semitaut-poly_1, 150 , drag-embedment1, 0 ] - - [ semitaut-poly_1, 30 , drag-embedment1, 0 ] - - [ semitaut-poly_1, 270, drag-embedment1, 0 ] + - [ semitaut-poly_1, 30 , drag-embedment1, 1, 1 ] + - [ semitaut-poly_1, 150 , drag-embedment1, 2, 1 ] + - [ semitaut-poly_1, 270, drag-embedment1, 3, 1 ] # Mooring line configurations @@ -164,23 +159,39 @@ mooring_line_configs: sections: + - subsections: # subsections are used to define segments of mooring that run in parallel, such as a bridle + - - connectorType: h_link # end A connector to the fairlead A0 + - type: chain_185 # each outer list is a separate subsection, each inner list is the segments of the subsection that are connected in series + length: 40 + - - connectorType: h_link # end A connector to the fairlead A1 + - type: chain_185 + length: 40 - connectorType: h_link - type: rope - length: 150 + length: 110 - connectorType: clump_weight_80 - type: rope - length: 1172 + length: 500 + - connectorType: triplate + - subsections: # a subsection that is not on the end of a mooring connects back to the connector on either side + - - type: chain_185 + length: 180 + - - type: chain_185 + length: 180 + - connectorType: triplate + - type: rope + length: 500 - connectorType: clump_weight_80 - type: rope - length: 150 + length: 110 - connectorType: h_link - - bridle_sections: - bridle_radius: - - type: - - connectorType: - - type: - length: + - subsections: + - - type: chain_185 + length: 40 + - connectorType: h_link # end B connector to the fairlead B0 + - - type: chain_185 + length: 40 + - connectorType: h_link # end B connector to the fairlead B1 semitaut-poly_1: # mooring line configuration identifier @@ -199,6 +210,29 @@ mooring_line_configs: d_nom: .182 length: 199.8 # [m] length (unstretched) + semitaut-poly_1_bridle: # mooring line configuration identifier + + name: Semitaut polyester configuration 1 # descriptive name + + span: 642 + + sections: #in order from anchor to fairlead + - mooringFamily: chain # ID of a mooring line section type + d_nom: .1549 + length: 497.7 # [m] usntretched length of line section + adjustable: True # flags that this section could be adjusted to accommodate different spacings... + - connectorType: h_link + - mooringFamily: polyester # ID of a mooring line section type + d_nom: .182 + length: 119.8 # [m] length (unstretched) + - subsections: + - - mooringFamily: polyester # ID of a mooring line section type + d_nom: .182 + length: 80 # [m] length (unstretched) + - - mooringFamily: polyester # ID of a mooring line section type + d_nom: .182 + length: 80 # [m] length (unstretched) + # Mooring line cross-sectional properties @@ -349,7 +383,6 @@ dynamic_cable_configs: A: 300 cable_type: dynamic_cable_66_1 # ID of a cable section type1 length: 353.505 # [m] length (unstretched) - rJTube : 5 # [m] radial distance from center of platform that J-tube is located sections: - type: buoyancy_module_1 #_w_buoy # (section properties including averaged effect of buoyancy modules) @@ -364,11 +397,10 @@ dynamic_cable_configs: suspended_1: name: Dynamic suspended cable configuration 1 voltage: 33 - span: 1512 + span: 1590 cable_type: dynamic_cable_66 # ID of a cable section type1 A: 300 - length: 1550 # [m] length (unstretched) - rJTube : 58 # [m] radial distance from center of platform that J-tube is located + length: 1610 # [m] length (unstretched) sections: - type: Buoyancy_750m #_w_buoy # (section properties including averaged effect of buoyancy modules) @@ -391,11 +423,13 @@ cables: attachID: FOWT1 # FOWT/substation/junction ID heading: 260 # [deg] heading of attachment at end A dynamicID: lazy_wave1 # ID of dynamic cable configuration at this end + JTube: 1 # index of Jtube list in platform definition endB: attachID: FOWT2 # FOWT/substation/junction ID heading: 280 # [deg] heading of attachment at end B dynamicID: lazy_wave1 # ID of dynamic cable configuration at this end + JTube: 1 # index of Jtube list in platform definition routing_x_y_r: # optional vertex points along the cable route. Nonzero radius wraps around a point at that radius. - [-900, -1450, 20] @@ -1529,8 +1563,14 @@ platforms: - potModMaster : 1 # [int] master switch for potMod variables; 0=keeps all member potMod vars the same, 1=turns all potMod vars to False (no HAMS), 2=turns all potMod vars to True (no strip) dlsMax : 5.0 # maximum node splitting section amount for platform members; can't be 0 qtfPath : 'IEA-15-240-RWT-UMaineSemi.12d' # path to the qtf file for the platform - rFair : 58 - zFair : -14 + fairleads : # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [58, 0, -14] + headings: [30, 150, 270, 35] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + Jtubes : # list of Jtube coordinates for the platform relative to platform coordinate and 0-degree heading + - name: Jtube1 + r_rel: [5, 0, -20] + headings: [90, 210, 330] # headings in degrees for the Jtube (if multiple headings, the Jtube will be repeated for each heading) type : FOWT z_location : 0 # optional to put the depth of this platform type @@ -1628,6 +1668,14 @@ platforms: qtfPath : 'IEA-15-240-RWT-UMaineSemi.12d' # path to the qtf file for the platform rFair : 58 zFair : -15 + fairleads : # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [58, 0, -14] + headings: [30, 150, 270, 35] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + Jtubes: + - name: Jtube1 + r_rel: [5, 0, -20] + headings: [90, 210, 330] # headings in degrees for the Jtube (if multiple headings, the Jtube will be repeated for each heading) type : Substation members: # list all members here diff --git a/examples/Inputs/OntologySample200m_1turb.yaml b/examples/OntologySample200m_1turb.yaml similarity index 100% rename from examples/Inputs/OntologySample200m_1turb.yaml rename to examples/OntologySample200m_1turb.yaml diff --git a/examples/Inputs/OntologySample200m_uniformArray.yaml b/examples/OntologySample200m_uniformArray.yaml similarity index 100% rename from examples/Inputs/OntologySample200m_uniformArray.yaml rename to examples/OntologySample200m_uniformArray.yaml diff --git a/examples/Inputs/OntologySample600m_shared.yaml b/examples/OntologySample600m_shared.yaml similarity index 96% rename from examples/Inputs/OntologySample600m_shared.yaml rename to examples/OntologySample600m_shared.yaml index 675d2f94..33def23b 100644 --- a/examples/Inputs/OntologySample600m_shared.yaml +++ b/examples/OntologySample600m_shared.yaml @@ -103,8 +103,8 @@ array: data : # ID# ID# ID# [m] [m] [deg] - [FOWT1, 1, 1, ms3, 0, 0, 180 ] # 2 array, shared moorings - [FOWT2, 1, 1, ms2, 1600, 0, 0 ] - - [FOWT3, 1, 1, ms1, 0, 1656, 180 ] - - [FOWT4, 1, 1, ms4, 1600, 1656, 180] + - [FOWT3, 1, 1, ms1, 0, 1700.4577, 180 ] + - [FOWT4, 1, 1, ms4, 1600, 1700.4577, 180] # - [4, 1, 2, ms4, -1200, 0, 0 ] # - [5, 1, 1, ms5, 0, 0, 0 ] # - [6, 1, 1, ms6, 1200, 0, 0 ] @@ -118,18 +118,18 @@ array_mooring: anchor_keys : [ID, type, x, y, embedment ] anchor_data : - - [ Anch1, suction_pile1, -828 , 828 , 2 ] + - [ Anch1, suction_pile1, -829 , 850.22887 , 2 ] # - [ 2, suction1, -1900 , -1200 , 2 ] # - [ 3, suction1, -850 , -1806 , 2 ] # - [ 4, suction1, -850 , 600 , 2 ] # - [ 5, suction1, -1900 , 0 , 2 ] line_keys : - [MooringConfigID , endA, endB, headingA, headingB, lengthAdjust] + [MooringConfigID , endA, endB, fairleadA, fairleadB, lengthAdjust] line_data : - - [ rope_shared , FOWT1, FOWT2, 270, 270, 0] - - [ rope_1 , Anch1, FOWT1, NONE, 135, 0] - - [ rope_1 , Anch1, FOWT3, NONE, 45, 0] + - [ rope_shared_bridle , FOWT1, FOWT2, [4,5], [4,5], 0] + - [ rope_1 , Anch1, FOWT1, NONE, 2, 0] + - [ rope_1 , Anch1, FOWT3, NONE, 1, 0] # - [ shared-2-clump , FOWT 2, FOWT 3, 0, 0, 0] @@ -1203,6 +1203,18 @@ platforms: rFair : 40.5 # platform fairlead radius zFair : -20 # platform fairlead z-location type : FOWT + fairleads : + # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [58, 0, -14] # relative coordinates of fairlead to platform center + headings: [30, 150, 270] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + - name: fairleads2 + r_rel: [-57.779,-5.055, -14] + - name: fairleads3 + r_rel: [-57.779, 5.055, -14] + rFair : 40.5 # platform fairlead radius + zFair : -20 # platform fairlead z-location + type : FOWT members: # list all members here @@ -1302,10 +1314,10 @@ mooring_systems: ms1: name: 3-line semi-taut polyester mooring system with one line shared anchor - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 270 , suction_pile1, 0 ] - - [ rope_1, 135 , suction_pile1, 0 ] + - [ rope_1_bridle, 270 , suction_pile1, [4,5] ] + - [ rope_1, 135 , suction_pile1, 2 ] ms2: name: 2-line semitaut with a third shared line @@ -1318,18 +1330,18 @@ mooring_systems: ms3: name: 3-line semi-taut polyester mooring system with one line shared anchor and one shared line - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 45 , suction_pile1, 0 ] + - [ rope_1, 45 , suction_pile1, 1 ] ms4: name: 3 line taut poly mooring system - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 45 , suction_pile1, 0 ] - - [ rope_1, 135 , suction_pile1, 0 ] - - [ rope_1, 270 , suction_pile1, 0 ] + - [ rope_1, 45 , suction_pile1, 1 ] + - [ rope_1, 135 , suction_pile1, 2 ] + - [ rope_1, 270 , suction_pile1, 3 ] # Mooring line configurations @@ -1349,6 +1361,67 @@ mooring_line_configs: length: 1170 # [m] usntretched length of line section adjustable: True # flags that this section could be adjusted to accommodate different spacings... + rope_1_bridle: # mooring line configuration identifier + + name: rope configuration 1 with a bridle # descriptive name + + span: 1131.37 + + + sections: #in order from anchor to fairlead + - type: chain_155mm + length: 20 + - type: rope # ID of a mooring line section type + length: 500 # [m] usntretched length of line section + - connectorType: triplate + - subsections: # double chain section + - - mooringFamily: chain + d_nom: 0.1 + length: 120 + - - mooringFamily: chain + d_nom: 0.1 + length: 120 + - connectorType: triplate + - type: rope # ID of a mooring line section type + length: 500 # [m] usntretched length of line section + - subsections: # bridle sections for end B + - - type: rope + length: 50 + - connectorType: shackle + - - type: rope + length: 50 + - connectorType: shackle + + rope_shared_bridle: + name: shared rope with a bridle on each end + + span: 1484 + + + sections: + - subsections: # bridle sections for end B + - - connectorType: shackle + - type: rope + length: 50 + - - connectorType: shackle + - type: rope + length: 50 + - type: rope + length: 100 + - connectorType: clump_weight_80 + - type: rope + length: 1172 + - connectorType: clump_weight_80 + - type: rope + length: 100 + - subsections: # bridle sections for end B + - - type: rope + length: 50 + - connectorType: shackle + - - type: rope + length: 50 + - connectorType: shackle + rope_shared: name: shared rope @@ -1480,6 +1553,11 @@ mooring_connector_types: shackle: m : 200 v : .2 + + triplate: + m : 1000 + v : 0.1 + # note: triplate is a connector that connects three mooring lines together, e.g., for a double chain section # Anchor type properties anchor_types: diff --git a/examples/Inputs/bathymetry200m_Array.txt b/examples/bathymetry200m_Array.txt similarity index 100% rename from examples/Inputs/bathymetry200m_Array.txt rename to examples/bathymetry200m_Array.txt diff --git a/examples/Inputs/bathymetry200m_sample.txt b/examples/bathymetry200m_sample.txt similarity index 100% rename from examples/Inputs/bathymetry200m_sample.txt rename to examples/bathymetry200m_sample.txt diff --git a/examples/Inputs/boundary_sample.csv b/examples/boundary_sample.csv similarity index 100% rename from examples/Inputs/boundary_sample.csv rename to examples/boundary_sample.csv diff --git a/examples/create_platform_from_ms.py b/examples/create_platform_from_ms.py index ab085379..a4f3b955 100644 --- a/examples/create_platform_from_ms.py +++ b/examples/create_platform_from_ms.py @@ -15,15 +15,12 @@ import matplotlib.pyplot as plt #### INPUTS #### -input_directory = 'Inputs/' # relative location of directory for input files (yaml, bath files, etc) -filename = 'MoorDyn_semitaut200m.dat' # moordyn file to create a moorpy system +dir = os.path.dirname(os.path.realpath(__file__)) +filename = dir+'\MoorDyn_semitaut200m.dat' # moordyn file to create a moorpy system rep_pf_name = 'FOWT1' # platform to replicate (look at yaml file array data table to get platform names) new_pf_loc = [-100,-1500,0] -# change to input directory -os.chdir(input_directory) - # create moorpy system ms = mp.System(file=filename) ms.initialize() diff --git a/examples/duplicate_platform.py b/examples/duplicate_platform.py index e40b77c9..b78644a1 100644 --- a/examples/duplicate_platform.py +++ b/examples/duplicate_platform.py @@ -9,13 +9,11 @@ import matplotlib.pyplot as plt #### INPUTS #### -input_directory = 'Inputs/' # relative location of directory for input files (yaml, bath files, etc) -filename = 'OntologySample200m.yaml' # yaml file to make initial platform(s) +dir = os.path.dirname(os.path.realpath(__file__)) +filename = dir+'\OntologySample200m.yaml' # yaml file to make initial platform(s) rep_pf_name = 'FOWT1' # platform to replicate (look at yaml file array data table to get platform names) new_pf_loc = [0,0] -# switch to directory of input files -os.chdir(input_directory) # first load in single platform from yaml project = Project(file=filename) diff --git a/examples/example_anchors.py b/examples/example_anchors.py index d496b11f..95bd77ed 100644 --- a/examples/example_anchors.py +++ b/examples/example_anchors.py @@ -7,10 +7,10 @@ from famodel.project import Project import os -os.chdir('./Inputs/') +dir = os.path.dirname(os.path.realpath(__file__)) # set yaml file location and name -ontology_file = 'OntologySample200m_1turb.yaml' +ontology_file = dir+'\OntologySample200m_1turb.yaml' # create project class project = Project(file=ontology_file) diff --git a/examples/example_driver.py b/examples/example_driver.py index ad1ac38b..c8d043cc 100644 --- a/examples/example_driver.py +++ b/examples/example_driver.py @@ -22,18 +22,16 @@ import os import matplotlib.pyplot as plt -os.chdir('./Inputs/') - # set yaml file location and name -ontology_file = 'OntologySample200m.yaml' +ontology_file = "OntologySample200m.yaml" #%% Section 1: Project without RAFT print('Creating project without RAFT\n') -print(os.getcwd()) + # create project object -project = Project(file=ontology_file,raft=False) +project = Project(file=ontology_file, raft=False) # create moorpy system of the array, include cables in the system -project.getMoorPyArray(cables=1) +project.getMoorPyArray(cables=True) # plot in 3d, using moorpy system for the mooring and cable plots project.plot2d() project.plot3d() diff --git a/examples/example_manual.py b/examples/example_manual.py index 7a0eed62..7fe34235 100644 --- a/examples/example_manual.py +++ b/examples/example_manual.py @@ -9,16 +9,14 @@ import matplotlib.pyplot as plt ### INPUTS ### -inputfolder = './Inputs/' -bathfile = 'bathymetry200m_Array.txt' -soilfile = 'soil_sample.txt' -boundfile = 'boundary_sample.csv' -moordynfile = 'Moordyn_semitaut200m.dat' +dir = os.path.dirname(os.path.realpath(__file__)) +bathfile = dir+'\bathymetry200m_Array.txt' +soilfile = dir+'\soil_sample.txt' +boundfile = dir+'\boundary_sample.csv' +moordynfile = dir+'\Moordyn_semitaut200m.dat' n_pfs = 4 pf_spacing = 1600 -# change to input directory -os.chdir(inputfolder) # create empty project project = Project() diff --git a/examples/example_sharedmoorings.py b/examples/example_sharedmoorings.py index 3e4d156b..b2f40083 100644 --- a/examples/example_sharedmoorings.py +++ b/examples/example_sharedmoorings.py @@ -4,21 +4,19 @@ a shared anchor. The purpose of this example is to show the Ontology inputs for shared systems and how these systems might appear in FAModel """ -import os import famodel from famodel import Project import matplotlib.pyplot as plt +import os # point to location of yaml file with uniform array info -input_directory = 'Inputs/' # relative location of directory for input files (yaml, bath files, etc) -filename = 'OntologySample600m_shared.yaml' # yaml file for project +dir = os.path.dirname(os.path.realpath(__file__)) +filename = '\OntologySample600m_shared.yaml' # yaml file for project -# switch to directory of input files -os.chdir(input_directory) - # load in yaml -project = Project(file=filename,raft=True) +project = Project(file=dir+filename, raft=True) + # plot in 2d and 3d project.plot2d() diff --git a/examples/Inputs/gch.yaml b/examples/gch.yaml similarity index 100% rename from examples/Inputs/gch.yaml rename to examples/gch.yaml diff --git a/examples/Inputs/iea_15MW.yaml b/examples/iea_15MW.yaml similarity index 100% rename from examples/Inputs/iea_15MW.yaml rename to examples/iea_15MW.yaml diff --git a/examples/Inputs/maine_rose.csv b/examples/maine_rose.csv similarity index 100% rename from examples/Inputs/maine_rose.csv rename to examples/maine_rose.csv diff --git a/examples/platform_ms.py b/examples/platform_ms.py index 07a96df8..97671673 100644 --- a/examples/platform_ms.py +++ b/examples/platform_ms.py @@ -9,13 +9,11 @@ from moorpy.helpers import subsystem2Line # get locations of files -cwd = os.getcwd() -input_dir = cwd+'/Inputs/' -yaml_file = 'OntologySample200m_uniformArray.yaml' +dir = os.path.dirname(os.path.realpath(__file__)) +yaml_file = '\OntologySample200m_uniformArray.yaml' -os.chdir(input_dir) # create project -project = Project(file=yaml_file) +project = Project(file=dir+yaml_file) # create a moorpy system for a single platform project.platformList['fowt0'].mooringSystem(project=project) diff --git a/examples/Inputs/soil_sample.txt b/examples/soil_sample.txt similarity index 100% rename from examples/Inputs/soil_sample.txt rename to examples/soil_sample.txt diff --git a/examples/uniformArray.py b/examples/uniformArray.py index 9f69a4d4..8b185179 100644 --- a/examples/uniformArray.py +++ b/examples/uniformArray.py @@ -12,17 +12,15 @@ import matplotlib.pyplot as plt # point to location of yaml file with uniform array info -input_directory = 'Inputs/' # relative location of directory for input files (yaml, bath files, etc) -filename = 'OntologySample200m_uniformArray.yaml' # yaml file to make initial platform(s) +dir = os.path.dirname(os.path.realpath(__file__)) +filename = '\OntologySample200m_uniformArray.yaml' # yaml file to make initial platform(s) # This yaml file does not contain explicit locations of each platform in the array table, # but rather has a 'uniform_array' section that describes # of rows, cols, spacing, etc. # This info is then used to automatically make a uniform array when the yaml file is loaded -# switch to directory of input files -os.chdir(input_directory) # load in yaml -project = Project(file=filename,raft=True) +project = Project(file=dir+filename,raft=True) project.plot2d() # plot the system project.plot3d(fowt=True) diff --git a/famodel/anchors/anchor.py b/famodel/anchors/anchor.py index 847c6263..28c2671a 100644 --- a/famodel/anchors/anchor.py +++ b/famodel/anchors/anchor.py @@ -123,10 +123,9 @@ def makeMoorPyAnchor(self, ms): MoorPy system ''' - # create anchor as a fixed point in MoorPy system - ms.addPoint(1,self.r) - # assign this point as mpAnchor in the anchor class instance - self.mpAnchor = ms.pointList[-1] + # create anchor as a fixed body in MoorPy system and assign to mpAnchor property + r6 = [self.r[0],self.r[1],self.r[2],0,0,0] + self.mpAnchor = ms.addBody(1,r6) # add mass if available if 'm' in self.dd['design'] and self.dd['design']['m']: @@ -550,14 +549,14 @@ def makeEqual_TaTm(mudloads): # get line type for att in self.attachments.values(): if isinstance(att['obj'],Mooring): - mtype = att['obj'].dd['sections'][0]['type']['material'] + mtype = att['obj'].sections()[0]['type']['material'] if not 'chain' in mtype: print('No chain on seafloor, setting Ta=Tm') nolugload = True break else: - md = att['obj'].dd['sections'][0]['type']['d_nom'] - mw = att['obj'].dd['sections'][0]['type']['w'] + md = att['obj'].sections()[0]['type']['d_nom'] + mw = att['obj'].sections()[0]['type']['w'] soil = next(iter(self.soilProps.keys()), None) ground_conds = self.soilProps[soil] # update soil conds as needed to be homogeneous diff --git a/famodel/cables/cable.py b/famodel/cables/cable.py index 21ca3ada..bbbe540c 100644 --- a/famodel/cables/cable.py +++ b/famodel/cables/cable.py @@ -8,8 +8,9 @@ from famodel.cables.dynamic_cable import DynamicCable from famodel.cables.static_cable import StaticCable -from famodel.cables.components import Joint +from famodel.cables.components import Joint, Jtube from famodel.famodel_base import Edge +from famodel.helpers import cableDesignInterpolation class Cable(Edge): @@ -109,8 +110,7 @@ def __init__(self, id, d=None): self.r = [] # get cable length - self.getL() - + self.getL() # failure probability self.failure_probability = {} @@ -122,7 +122,7 @@ def reposition(self,headings=None,project=None,rad_fair=[]): Parameters ---------- headings : list, optional - List of headings associated with the platform/substation attached + List of absolute compass headings associated with the platform/substation attached to each end of the cable. The default is None. project : FAModel project object, optional FAModel project object associated with this cable, only used if @@ -139,24 +139,43 @@ def reposition(self,headings=None,project=None,rad_fair=[]): ''' # reposition cable and set end points for the first and last cable sections (or the dynamic cable for a suspended cable) if not headings: - headingA = self.subcomponents[0].headingA - self.attached_to[0].phi - headingB = self.subcomponents[-1].headingB - self.attached_to[1].phi + # convert headings to unit circle + headingA = np.pi/2 - self.subcomponents[0].headingA + headingB = np.pi/2 - self.subcomponents[-1].headingB else: - headingA = headings[0] - headingB = headings[1] + # convert headings to unit circle + headingA = np.pi/2 - headings[0] + self.subcomponents[0].headingA = headings[0] + headingB = np.pi/2 - headings[1] + self.subcomponents[-1].headingB = headings[1] - if not rad_fair: - rad_fair = [self.attached_to[x].rFair if self.attached_to[x].rFair else 0 for x in range(2)] - else: - for i,r in enumerate(rad_fair): - if r==None: - rad_fair[i] = self.attached_to[i].rFair if self.attached_to[i] else 0 + if not isinstance(self.subcomponents[0].attached_to[0], Jtube): + if not rad_fair: + rf = self.attached_to[0].rFair if self.attached_to[0] else 0 + else: + if rad_fair[0] == None: + rf = self.attached_to[0].rFair if self.attached_to[0] else 0 + else: + rf = rad_fair[0] + + # calculate fairlead locations + Aloc = [self.attached_to[0].r[0]+np.cos(headingA)*rf, + self.attached_to[0].r[1]+np.sin(headingA)*rf, + self.attached_to[0].zFair+self.attached_to[0].r[2]] + self.subcomponents[0].rA = Aloc; self.rA = Aloc + if not isinstance(self.subcomponents[-1].attached_to[-1], Jtube): + if not rad_fair: + rf = self.attached_to[1].rFair if self.attached_to[1] else 0 + else: + if rad_fair[1] == None: + rf = self.attached_to[1].rFair if self.attached_to[1] else 0 + else: + rf = rad_fair[1] - # calculate fairlead locations (can't use reposition method because both ends need separate repositioning) - Aloc = [self.attached_to[0].r[0]+np.cos(headingA)*rad_fair[0], self.attached_to[0].r[1]+np.sin(headingA)*rad_fair[0], self.attached_to[0].zFair] - Bloc = [self.attached_to[1].r[0]+np.cos(headingB)*rad_fair[1], self.attached_to[1].r[1]+np.sin(headingB)*rad_fair[1], self.attached_to[1].zFair] - self.subcomponents[0].rA = Aloc; self.rA = Aloc - self.subcomponents[-1].rB = Bloc; self.rB = Bloc + Bloc = [self.attached_to[1].r[0]+np.cos(headingB)*rf, + self.attached_to[1].r[1]+np.sin(headingB)*rf, + self.attached_to[1].zFair+self.attached_to[1].r[2]] + self.subcomponents[-1].rB = Bloc; self.rB = Bloc if project: # set end points of subcomponents @@ -166,14 +185,25 @@ def reposition(self,headings=None,project=None,rad_fair=[]): if lensub > 1: for i,sub in enumerate(self.subcomponents): if i == 0: + oldz = project.getDepthAtLocation(sub.rB[0], + sub.rB[1]) # get depth at location of rB xy = [sub.rA[0]+np.cos(headingA)*sub.span, sub.rA[1]+np.sin(headingA)*sub.span] z = project.getDepthAtLocation(xy[0],xy[1]) + # adjust design if applicable + if sub.alternate_designs is not None and oldz!=z: + sub.dd = cableDesignInterpolation( + sub.dd, + sub.alternate_designs, + z) + self.reposition() # recursively call reposition # set the end B of the first subsection sub.rB = [xy[0],xy[1],-z] sub.z_anch = -z sub.depth = z + + # set joint self.subcomponents[i+1]['r'] = sub.rB # set rA of next cable section @@ -182,6 +212,13 @@ def reposition(self,headings=None,project=None,rad_fair=[]): xy = [sub.rB[0]+np.cos(headingB)*sub.span, sub.rB[1]+np.sin(headingB)*sub.span] z = project.getDepthAtLocation(xy[0],xy[1]) + # adjust design if applicable + if sub.alternate_designs is not None and oldz!=z: + sub.dd = cableDesignInterpolation( + sub.dd, + sub.alternate_designs, + z) + self.reposition() # re-call reposition # set the end A of the last subsection sub.rA = [xy[0],xy[1],-z] # update z_anch and depth of the subsection @@ -236,7 +273,7 @@ def getL(self): self.L = L def makeLine(self,buff_rad=20,include_dc=True): - + '''Make a 2D shapely linestring of the cable''' coords = [] for sub in self.subcomponents: if isinstance(sub,Joint): @@ -253,6 +290,67 @@ def makeLine(self,buff_rad=20,include_dc=True): return(line) + def dynamicCables(self): + """ Return list of dynamic cables in this cable object """ + return [a for a in self.subcomponents if isinstance(a, DynamicCable)] + + def updateTensions(self, DAF=1): + """ + Update the tensions stored in dynamic cable load dictionaries + + Returns + ------- + None. + + """ + for dc in self.dynamicCables(): + if not 'Tmax' in dc.loads: + dc.loads['Tmax'] = 0 + if dc.ss: + for line in dc.ss.lineList: + Tmax = max([abs(line.TA), abs(line.TB)]) + if Tmax*DAF > dc.loads['Tmax']: + dc.loads['Tmax'] = deepcopy(Tmax)*DAF + return(dc.loads['Tmax']) + + # def updateCurvature(self): + # for dc in self.dyamicCables(): + # if not dc.curvature: + # dc.curvature = np.inf + # if dc.ss: + # curv = dc.ss.calcCurvature() + # if curv > dc.curvature: + # dc.curvature = curv + # mCSF = dc.ss.getMinCurvSF() + + def updateSafetyFactors(self, key='tension', load='Tmax', prop='MBL', + info={}): + """ + Update the safety factor dictionaries stored in dynamic cable objects + + Parameters + ---------- + key : str/int, optional + key in safety factor dictionary of dynamic cables. + The default is 'tension'. + load : str, optional + Key in load dictionary of dynamic cables. The default is 'Tmax'. + prop : str, optional + Key in dynamic cable properties dictionary to compare to load. + The default is 'MBL'. + info : dict, optional + Information dictionary to add in the safety_factors dict for context + + Returns + ------- + None. + + """ + for dc in self.dynamicCables(): + dc.safety_factors[key] = dc.dd['cable_type'][prop]/dc.loads[load] + dc.safety_factors['info'] = info + + def updateSpan(self,newSpan): ''' Change the lengths of subcomponents based on the new total span diff --git a/famodel/cables/components.py b/famodel/cables/components.py index 9f108339..5844e3ce 100644 --- a/famodel/cables/components.py +++ b/famodel/cables/components.py @@ -61,6 +61,11 @@ def makeMoorPyConnector(self, ms): return(ms) +class Jtube(Node,dict): + def __init__(self,id, r=None,**kwargs): + dict.__init__(self, **kwargs) # initialize dict base class (will put kwargs into self dict) + Node.__init__(self, id) # initialize Node base class + """ class Cable(Edge, dict): '''A length of a subsea power cable product (i.e. same cross section of diff --git a/famodel/cables/dynamic_cable.py b/famodel/cables/dynamic_cable.py index 022a836f..59f6f9e3 100644 --- a/famodel/cables/dynamic_cable.py +++ b/famodel/cables/dynamic_cable.py @@ -79,6 +79,7 @@ def __init__(self, id, dd=None, subsystem=None, rA=[0,0,0], rB=[0,0,0], self.headingB = 0 elif 'headingB' in self.dd: self.headingB = self.dd['headingB'] # <<< ?? + # if there's no headingA, likely a suspended cable - headingA = headingB+180 degrees self.headingA = 0 else: @@ -109,6 +110,9 @@ def __init__(self, id, dd=None, subsystem=None, rA=[0,0,0], rB=[0,0,0], self.rho = rho self.g = g + # alternate designs to interpolate between when depth changes + self.alternate_designs = None + # Dictionaries for addition information self.loads = {} self.safety_factors = {} # calculated safety factor diff --git a/famodel/cables/static_cable.py b/famodel/cables/static_cable.py index 76a7bd47..055e2eab 100644 --- a/famodel/cables/static_cable.py +++ b/famodel/cables/static_cable.py @@ -181,13 +181,12 @@ def updateRouting(self,coords=None): ''' - if coords: + if coords is not None and len(coords)>0: self.x = [coord[0] for coord in coords] # cable route vertex global x coordinate [m] self.y = [coord[1] for coord in coords] # cable route vertex global y coordinate [m] # Check if radius available if len(coords[0]) == 3: self.r = [coord[2] for coord in coords] # cable route vertex corner radius [m] - # update static cable length self.getLength() diff --git a/famodel/famodel_base.py b/famodel/famodel_base.py index 808bbfae..c21d014c 100644 --- a/famodel/famodel_base.py +++ b/famodel/famodel_base.py @@ -1,5 +1,5 @@ import numpy as np - +from famodel.helpers import calc_midpoint ''' famodel_base contains base classes that can be used for various classes in the floating array model that relate to entities that act like either @@ -22,10 +22,53 @@ Linear assemblies of edges and nodes can be grouped into a higher-level edge object. This allows mooring line sections and connectors to be part of an overall mooring line object, which is itself an edge. These -relationships are tracked with sub_edge and sub_node lists in the +relationships are tracked with subcomponents lists in the higher-level Edge object, and part_of entries in the lower-level node or edge objects. +NEW: +Edge subcomponents can now be in any arrangement +The subcomponent(s) at the end of an edge can attach to the node +the edge is attached to, or a sub-node of the node. +If the super-edge end is attached to a node, +the sub-objects could be automatically attached to the node, or +left to be manually attached <<<<< which one?? +If a super-edge is disconnected, the sub-objects should be disconnected, right? + +Is there some overriding logic of the above?? + +How do we identify/track the sub-objects? +How do we identify/track the attacheble end sub-objets? +Dicts??? + + +The current approach is not top-down. If you connect higher-level objects, +the lower level objects aren't automatically connected. Instead, if you connect +lower objects (including specifying particular positions), the higher-level +connections are also made. + + +Nodes can attach to nodes as either subortinately or equally... + + +when attached equally, they reference each other (mirror) in self.attachments +but not in self.attached_to. The latter is used only for subordinate +connections (i.e. to a higher node, and only to one). +Mutual/equal node attachments are done with the join method, and undone with +the separate method. + + +When a node or edge of a higher edge is attached to a node of a higher node, +the following rules apply: +The higher level objects can be attached regardless of sub-object attachment. +If any sub-objects are attached, the higher level objects must be attached. +- Detaching the higher level objects must detach the sub objects in the process. +- When all the sub objects are detached, it would be convenient to detach the higher objects. + +Can an end of a higher level edge attach to multiple nodes? +(using a list, corresponding to multiple sub objects at the end) + + ''' class Node(): @@ -47,16 +90,16 @@ def __init__(self, id): self.id = id # id number or string, used as the key when attached to things - self.attachments = {} # dictionary listing attached edges + self.attachments = {} # dictionary listing attached edges or nodes # (key is the id, value is attachment object ref and other info) - self.attached_to = None # whether this object is bound to another object + self.attached_to = None # whether this object is subordinately bound to another object self.part_of = None # whether this object is part of an Edge group # position/orientation variables self.r = np.zeros(2) # position [m] - self.theta = 0 # heading [rad] + self.theta = 0 # heading [rad] CCW+ self.R = np.eye(2) # rotation matrix # installation dictionary [checks for installation status] @@ -65,40 +108,123 @@ def __init__(self, id): def isAttached(self, object, end=None): - '''Check if something is attached to this node, even if it's part of - a higher-level edge. + '''Check if something is attached to this node. This works for both + attached edges (the end can be specified), or nodes that are joined + with this node. Parameters ---------- object The Node or Edge object being attached to this one. end - If an Edge is being attached, which end of it, 'a' or 'b'. + If an Edge is being considered, which end of it, 'a' or 'b' (optional). ''' - # find top-level edge end if applicable - if isinstance(object, Node): - object2, i_end = object.getTopLevelEdge() - elif isinstance(object, Edge): - i_end = endToIndex(end, estr='when checking if an edge is attached to a node.') - object2, _ = object.getTopLevelEdge(i_end) - else: - raise Exception('Provided object is not an Edge or Node.') + if object.id in self.attachments: # See if it's attached - # See if it's attached (might be a higher-level edge) - # if object2.id in self.attachments: - if object2.id in self.attachments: - - if isinstance(object2, Node): # if it's a node, it's simple + if isinstance(object, Node): # if it's a node, it's simple return True - elif isinstance(object2, Edge): # if it's an edge, end matters - if endToIndex(self.attachments[object2.id]['end']) == i_end: - return True # the end in question is attached - else: - return False + elif isinstance(object, Edge): + if end == None: # if end not specified, it's simple + return True + else: # otherwise check which end + if endToIndex(self.attachments[object.id]['end']) == endToIndex(end): + return True # the end in question is attached + else: + return False + else: + raise Exception('Provided object is not an Edge or Node.') else: return False + + + def join(self, object): + '''Join another node ot this node, in a mutual way. + This could be multiple connectors within a higher level edge, + or one connector in a higher level edge (potentially connecting + to a connector in a higher level node). + ''' + + if not isinstance(object, Node): + raise Exception('Provided object is not a Node.') + + + # Make sure they're not already attached + if object.id in self.attachments: + raise Exception(f"Object {object.id} is already attached to {self.id}") + if self.id in object.attachments: + raise Exception(f"{self.id} is already attached to {object.id}") + + + # make sure there isn't some incompatibility in joining these nodes? + if isinstance(self.part_of, Edge) and isinstance(object.part_of, Edge): + if not self.part_of == object.part_of: + raise Exception("Cannot join two nodes that are each part of a different edge") + + # do the mutual joining + self.attachments[object.id] = dict(obj=object, id=object.id, + r_rel=np.array([0,0]), type='node') + + object.attachments[self.id] = dict(obj=self, id=self.id, + r_rel=np.array([0,0]), type='node') + + # Register the attachment in higher level objects if applicable + if isinstance(self.part_of, Edge) and isinstance(object.part_of, Edge): + raise Exception("This attachment would directly connect two higher-level edges to each other, which is not allowed.") + + elif isinstance(self.part_of, Edge) and isinstance(object.attached_to, Node): + end = self.part_of.findEnd(self) + object.attached_to.attach(self.part_of, end=end, + r_rel=object.attached_to.attachments[object.id]['r_rel']) + #self.part_of._attach_to(object.part_of, end=end) + + elif isinstance(self.attached_to, Node) and isinstance(object.part_of, Edge): + end = object.part_of.findEnd(object) + self.attached_to.attach(object.part_of, end=end, + r_rel=self.attached_to.attachments[self.id]['r_rel']) + + elif isinstance(self.attached_to, Node) and isinstance(object.attached_to, Node): + raise Exception("This would attach two higher-level nodes, which is not supported.") + + + def isJoined(self): + '''Check if this node is joined to anything else.''' + + for att in self.attachments.values(): # Look through everything attached + object = att['obj'] + if isinstance(object, Node): # Only another node could be joined + if self.id in object.attachments: + return True + + # If we've gotten this far, it's not joined with anything + return False + + + def separate(self, object): + '''Opposite of join''' + + if not isinstance(object, Node): + raise Exception('Provided object is not a Node.') + + # Make sure they're already attached + if not object.id in self.attachments: + raise Exception(f"Object {object.id} is not attached to {self.id}") + if not self.id in object.attachments: + raise Exception(f"{self.id} is not attached to {object.id}") + + # do the mutual separating + del self.attachments[object.id] + del object.attachments[self.id] + + # Register the separation in higher level objects if applicable + if isinstance(self.part_of, Edge) and isinstance(object.attached_to, Node): + end = self.part_of.findEnd(self) + object.attached_to.dettach(self.part_of, end=end) + + elif isinstance(self.attached_to, Node) and isinstance(object.part_of, Edge): + end = object.part_of.findEnd(object) + self.attached_to.detach(object.part_of, end=end) def attach(self, object, r_rel=[0,0], end=None): @@ -115,6 +241,11 @@ def attach(self, object, r_rel=[0,0], end=None): end If an Edge is being attached, which end of it, 'a' or 'b'. ''' + + #object_parent + #self_parent + + ''' # find top-level edge end if applicable if isinstance(object, Node): object2, i_end = object.getTopLevelEdge() @@ -123,45 +254,107 @@ def attach(self, object, r_rel=[0,0], end=None): object2, _ = object.getTopLevelEdge(i_end) else: raise Exception('Provided object is not an Edge or Node.') - - + ''' # Make sure it's not already attached (note this doesn't distinguish end A/B) - if object2.id in self.attachments: - raise Exception(f"Object {object.id} is already attached to {self.id}") - - - # Attach the object (might be a higher-level edge) - if isinstance(object2, Node): - self.attachments[object2.id] = dict(obj=object2, id=object2.id, + if object.id in self.attachments: + # for bridles, the mooring will already be attached to platform + # for second bridle section + # need to calculate new r_rel that is average of end points + if isinstance(object, Edge): + # pull out relative dist of each point on end to self + r_rel = self.calculate_r_rel(object,end=end) + self.attachments[object.id]['r_rel'] = r_rel + # update end position + Node.setPosition(self, r=self.r,theta=self.theta) + # don't need to attach, already attached- just return + return + else: + raise Exception(f"Object {object.id} is already attached to {self.id}") + + + # Attach the object + if isinstance(object, Node): # (object is a node) + + if object.attached_to: # object is already attached to something + raise Exception("The object being attached is already attached to a higher node - it needs to be detached first.") + + self.attachments[object.id] = dict(obj=object, id=object.id, r_rel=np.array(r_rel), type='node') - #object2._attach_to(self) # tell it it's attached to this Node - object2.attachments[self.id] = dict(obj=self,id=self.id,r_rel=np.array(r_rel),type='node') + object._attach_to(self) # tell it it's attached to this Node - elif isinstance(object2, Edge): - self.attachments[object2.id] = dict(obj=object2, id=object2.id, - r_rel=np.array(r_rel), type='edge', - end=['a', 'b'][i_end]) - object2._attach_to(self, i_end) # tell it it's attached to this Node - ''' - if end in ['a', 'A', 0]: - new_entry['end'] = 'a' - object._attach_to(self, 0) + elif isinstance(object, Edge): # (object is an edge) + i_end = endToIndex(end, estr='when attaching an edge to a node.') - elif end in ['b', 'B', 1]: - new_entry['end'] = 'b' - object._attach_to(self, 1) + if object.attached_to[i_end]: # object is already attached to something + raise Exception("The object being attached is already attached to a higher node - it needs to be detached first.") - else: - raise Exception('End A or B must be specified when attaching an edge.') - ''' + self.attachments[object.id] = dict(obj=object, id=object.id, + r_rel=np.array(r_rel), type='edge', + end=i_end) + + object._attach_to(self, i_end) # tell it it's attached to this Node + else: raise Exception('Unrecognized object type') + + # See about attaching higher-level objects (new) (note: r_rel will be neglected at higher level) + + if isinstance(object.part_of, Edge): # attached object is part of an edge + + # figure out which end of the edge object corresponds to + if object in object.part_of.subcons_A and object in object.part_of.subcons_B: + # there is only one subcomponent, keep end that was passed in + i_end = end + elif object in object.part_of.subcons_A: + end = 0 + elif object in object.part_of.subcons_B: + end = 1 + else: + end = -1 # object isn't at the end of the higher level edge so do nothing + if not self.part_of == object.part_of: + raise Exception("Cannot attach two non-end subcomponents of different edges.") + + if self.part_of == None and end > -1: # this node isn't part of an edge + if self.attached_to: # if self is part of a higher node + # attach higher edge to higher node + self.attached_to.attach(object.part_of, end=end) + else: + # attach higher edge to this node + self.attach(object.part_of, r_rel=r_rel, end=end) + + else: # if self is part of a higher level edge + raise Exception("This attachment would directly connect two higher-level edges to each other, which is not allowed.") + ''' + elif isinstance(object.attached_to, Node): # attached object is attached to a higher node + + raise Exception("The object being attached is part of a higher node - this operation is not supported.") + + if self.part_of: # self node is part of a higher level edge + + # figure out which end of the edge object corresponds to + if self in self.part_of.subcons_A: + end = 0 + elif self in self.part_of.subcons_B: + end = 1 + + # attach higher node and edge + object.attached_to.attach(self.part_of, end=end) + + # attach higher edge to this node + self.attach(object.part_of, r_rel=r_rel, end=end) + + elif if self.attached_to: # if self is part of a higher node + Exception("This attachment would directly connect two higher-level nodes to each other, which is not allowed.") + + else: # self has nothing higher, so attach the object's higher level thing to self as well + self.attach(object.attached_to, r_rel=r_rel, end=end) XXXX + ''' + def detach(self, object, end=None): '''Detach the specified object from this node. - Note that this method doesn't search for highest-level edge - attachment because it should already be attached that way. + Will also detach any attached sub-objects Parameters ---------- @@ -176,54 +369,45 @@ def detach(self, object, end=None): raise Exception(f"Object {object.id} is not attached to {self.id}") # this exception could be optionally disabled - # Remove it from the attachment registry - del self.attachments[object.id] - # Handle attachment type and the end if applicable, and record # the detachment in the subordinate object. if isinstance(object, Node): - object._detach_from() # tell the attached object to record things - + object._detach_from() # tell the attached object to record that it's detached + elif isinstance(object, Edge): - - i_end = endToIndex(end, estr='when detaching an edge from a node.') - + if end: + i_end = endToIndex(end, estr='when detaching an edge from a node.') + else: # otherwise figure out which end is attached + i_end = self.attachments[object.id]['end'] + + # Detach this edge from self + # This will also detach end subcomponents of the edge from anything. object._detach_from(i_end) - ''' - if end in ['a', 'A', 0]: - object._detach_from(0) - - elif end in ['b', 'B', 1]: - object._detach_from(1) - - else: - raise Exception('End A or B must be specified when detaching an edge.') - ''' + else: raise Exception('Provided object is not an Edge or Node.') - - + + # Remove it from the attachment registry + del self.attachments[object.id] + - def _attach_to(self, object,sub=0): + def _attach_to(self, object): '''Internal method to update the Node's attached_to registry when - requested by a node. + requested by a node. With an error check. Parameters ---------- object The Node object to attach to. - sub - Boolean to mark if this node is subordinately attached to the object ''' - if not sub: - if isinstance(object, Node): - raise Exception('Node objects can only be attached subordinately to Node objects.') + # Make sure it's not already attached to something else if self.attached_to: # True if populated, False if empty dict # self.detach() # commented out because I don't think this would work for shared anchors # could optionally have a warning or error here print(f'Warning: node {self.id} is attached to 2 objects') - + breakpoint() + print("fix up this scenario in the code somewhere...") # Add it to the attached_to registry self.attached_to = object @@ -236,10 +420,10 @@ def _detach_from(self): self.attached_to = None - - def getTopLevelEdge(self): - '''If this node is part of a higher-level edge group, and the request - corresponds to an end of that higher-level group, return the higher edge, + """ + def getTopLevelObject(self): + '''If this node is part of a higher-level object, and the request + corresponds to an end of that higher-level group, return the higher object, otherwise return this same object. Can be recursive. A similar method exists for edges.''' @@ -247,13 +431,13 @@ def getTopLevelEdge(self): supe = self.part_of # shorthand for the super edge if supe.subcomponents[0] == self: # if this node is at end A of supe return supe.getTopLevelEdge(0) # return supe, and which end - elif supe.subcomponents[-1] == self: # if this node is at end B of supe + >> elif supe.subcomponents[-1] == self: # if this node is at end B of supe return supe.getTopLevelEdge(1) else: return self, -1 # if not part of something bigger, just return self + """ - - def setPosition(self, r, theta=0): + def setPosition(self, r, theta=0, force=False): '''Set the position of the node, as well as any attached objects. Parameters @@ -262,27 +446,117 @@ def setPosition(self, r, theta=0): x and y coordinates to position the node at [m]. theta, float (optional) The heading of the object [rad]. + force : bool (optional) + When false (default) it will not allow movement of subordinate objects. ''' + # Don't allow this if this is part of another object + if self.attached_to and not force: + raise Exception("Can't setPosition of an object that's attached to a higher object unless force=True.") + # Store updated position and orientation - self.r = np.array(r) + if len(r) >= len(self.r): # default r is 2D, but can be adjusted to 3D + self.r = np.array(r) + else: # if just a portion of r is being adjusted, only change up to length of initial r + self.r[:len(r)] = r + self.theta = theta # Get rotation matrix... - self.R = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]]) + if len(self.r) == 2: + self.R = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]]) + elif len(self.r) == 3: + if np.isscalar(theta): + angles = np.array([ 0, 0, theta]) + elif len(theta)==1: + angles = np.array([0, 0, theta[0]]) + elif len(theta)==3: + angles = np.array(theta) + else: + raise Exception("theta needs to be length 1 or 3.") + self.R = rotationMatrix(*angles) + else: + raise Exception("Length of r must be 2 or 3.") # Update the position of any attach objects for att in self.attachments.values(): + if len(self.r) == len(att['r_rel']): + pass # all good + elif len(self.r) == 3 and len(att['r_rel']) == 2: # pad r_rel with z=0 if needed + att['r_rel'] = np.hstack([att['r_rel'], [0]]) + else: + raise Exception("r and attachment r_rel values have mismatched dimensions.") + # Compute the attachment's position r_att = self.r + np.matmul(self.R, att['r_rel']) - + # set position of any attached node that isn't subordinate to another node + # (prevents infinite loop of setPositioning for nodes) if isinstance(att['obj'], Node): - att['obj'].setPosition(r_att) + if not isinstance(att['obj'].attached_to, Node) or att['obj'].attached_to == self: + att['obj'].setPosition(r_att, theta=theta, force=True) elif isinstance(att['obj'], Edge): att['obj'].setEndPosition(r_att, att['end']) + + def calculate_r_rel(self,object, end=None): + '''Calculate the relative distance between node and object + based on the combined relative distances of the subordinate/ + subcomponent nodes connecting them''' + if isinstance(object,Edge): + # pull out subcomponent(s) attached to self at the correct end + end = endToIndex(end) # find end + subs = object.subcons_A if end==0 else object.subcons_B + + # go through all end subcomponents of edge at the correct end + rel_locs = [] # relative location list (in case multiple points at end) + for sub in subs: + # first check if subordinate/subcomponent joined + att = [att for att in sub.attachments.values() if att['obj'].attached_to==self] + if len(att)>0: + # find attachment of sub that is subordinately connected to self (Node) + att = att[0] # just need 1st entry + r_rel_att_self = self.attachments[att['id']]['r_rel'] + r_rel_att_sub = att['obj'].attachments[sub.id]['r_rel'] + # r_rel of sub to self is r_rel of attachment to self + r_rel of sub to attachment + if len(r_rel_att_self) < 3: # pad as needed + r_rel_att_self = np.hstack([r_rel_att_self,[0]]) + if len(r_rel_att_sub) < 3: # pad as needed + r_rel_att_sub = np.hstack([r_rel_att_sub,[0]]) + rel_locs.append(r_rel_att_self + r_rel_att_sub) + # otherwise, check if directly connected + elif self.isAttached(object): + # if no subordinate/subcomponent connection, should be + # only 1 attachment point at this end + return(self.attachments[object.id]['r_rel']) + else: + raise Exception(f'Cannot determine how {self.id} and {object.id} are connected') + return calc_midpoint(rel_locs) + elif isinstance(object, Node): + # node to node - check if 2 subordinates connected + att = [att for att in object.attachments.values() if self.isAttached(att['obj'])] + if len(att)>0: + att = att[0] # just need 1st entry + # get relative distance of subordinately attached nodes + r_rel_att_self = self.attachments[att['id']]['r_rel'] + r_rel_att_obj = object.attachments[att['id']]['r_rel'] + # r_rel of obj to self is r_rel of attachment to self + r_rel of obj to attachment + if len(r_rel_att_self) < 3: # pad as needed + r_rel_att_self = np.hstack(r_rel_att_self,[0]) + if len(r_rel_att_obj) < 3: # pad as needed + r_rel_att_sub = np.hstack(r_rel_att_sub,[0]) + return(r_rel_att_self + r_rel_att_sub) + # otherwise see if they are directly attached and return r_rel + elif self.isattached(object): + return self.attachments[object.id]['r_rel'] + else: + raise Exception(f'Cannot determine how {self.id} and {object.id} are connected') + else: + raise Exception(f'{object} is not a Node or Edge') + + + @@ -305,19 +579,22 @@ def __init__(self, id): self.id = id # id number or string, used as the key when attached to things - self.attached_to = [None, None] # whether either end [A, B] of this object is bound to another object + self.attached_to = [None, None] # object end [A, B] of this edge is attached to # End A and B locations - self.rA = [0,0] - self.rB = [0,0] + self.rA = np.zeros(2) + self.rB = np.zeros(2) # Some attributes related to super-edges used to group things self.part_of = None # whether this object is part of an Edge group self.subcomponents = [] # chain of edges and nodes that make up this edge # (e.g. sections of a mooring line, and connetors between them) - - whole = True # false if there are sub edges/nodes that aren't connected + + self.subcons_A = [] # subcomponent for end A (can be multiple) + self.subcons_B = [] # subcomponent for end B (can be multiple) + + whole = True # false if there is a disconnect among the sub edges/nodes # installation dictionary [checks for installation status] self.inst = {'mobilized': False, @@ -343,27 +620,7 @@ def isAttachedTo(self, query, end=None): return query.isAttached(self, end=end) else: raise Exception('Edges can only be attached to Nodes.') - - ''' - # old version where query can be a reference to the object itself, or the object ID. - # If query is a reference to an object, get its ID - if isinstance(query, Node) or isinstance(query, Edge): - id = query.id - else: - id = query - - if end == None: - answer = bool(id == self.attached_to[0]['id'] or id == self.attached_to[1]['id']) - else: - if end in ['a', 'A', 0]: - answer = bool(id == self.attached_to[0]['id']) - elif end in ['b', 'B', 1]: - answer = bool(id == self.attached_to[1]['id']) - else: - raise Exception("If an 'end' parameter is provided, it must be one of a,b,A,B,0,1,False,True.") - ''' - # return answer - + def attachTo(self, object, r_rel=[0,0], end=None): '''Attach an end of this edge to some node. @@ -392,35 +649,11 @@ def attachTo(self, object, r_rel=[0,0], end=None): # Tell the Node in question to initiate the attachment # (this will also call self._attach_to) object.attach(self, r_rel=r_rel, end=i_end) - - ''' - # Add it to the attachment registry - new_entry = dict(ref=object, r_rel=np.array(r_rel)) - - # Handle attachment type and the end if applicable - if type(object) == Node: - new_entry['type'] = 'Node' - - elif type(object) == Edge: - new_entry['type'] = 'Edge' - if not end: - raise Exception('End A or B must be specified when attaching an edge.') - if end.lower() in ['a', 'b']: - new_entry['end'] = end.lower() - else: - raise Exception('End A or B must be specified when attaching an edge.') - else: - raise Exception('Provided object is not an Edge or Node.') - - # Since things have worked, add it to the list - self.attachments[object['id']] = new_entry - ''' def _attach_to(self, object, end): '''Internal method to update the edge's attached_to registry when - requested by a node. In nested edges, expects to be called for the - highest-level one first, then it will recursively call any lower ones. + requested by a node. This doesn't do higher level attachments. Parameters ---------- @@ -434,33 +667,373 @@ def _attach_to(self, object, end): if not isinstance(object, Node): raise Exception('Edge objects can only be attached to Node objects.') + # Could potentially check if it's already attached to something <<< + # Add it to the attached_to registry self.attached_to[i_end] = object + ''' # Recursively attach any subcomponent at the end - if len(self.subcomponents) > 0: - subcon = self.subcomponents[-i_end] # index 0 for A, -1 for B + >>> do we really want to do this still? <<< + if len(self.subcomponents) > 0: # this edge has subcomponents + for i_sub_end in self.end_inds[i_end]: # go through each subcomponent associated with this end + subcon = self.subcomponents[i_sub_end] + if isinstance(subcon, Node): # if the end subcomponent is a node + subcon._attach_to(object) + + elif isinstance(subcon, Edge): # if it's an edge + subcon._attach_to(object, i_end) # i_end will tell it whether to use end A or B + ''' + + + + def detachFrom(self, end): + '''Detach the specified end of the edge from whatever it's attached to. + + Parameters + ---------- + end + Which end of the line is being dettached, 'a' or 'b'. + ''' + # Determine which end to detach + i_end = endToIndex(end, estr='when detaching an edge.') + + # Tell the Node the end is attached to to initiate detachment + # (this will then also call self._detach_from) + self.attached_to[i_end].detach(self, end=i_end) + + + def _detach_from(self, end): + '''Internal method to update the edge's attached_to registry when + requested by a node. In nested edges, it will recursively call any + lower objects. + + Parameters + ---------- + end + Which end of the line is being detached, a-false, b-true. + ''' + i_end = endToIndex(end) + # Delete the attachment(s) of the edge end (there should only be one) + self.attached_to[i_end] = None + + # Go deeper >..... >>> + if i_end == 0: + end_subcons = self.subcons_A + elif i_end == 1: + end_subcons = self.subcons_B + + for subcon in end_subcons: if isinstance(subcon, Node): - # will be attaching a node to a node, tell _attach_to it is attaching the subcon as a subordinate node we don't throw an error - #print(f'attaching {object.id} to {subcon.id}') - subcon._attach_to(object,sub=1) - # object._attach_to(subcon,sub=1) + if subcon.attached_to: + subcon.attached_to.detach(subcon) + # the above will then call subcon._detach_from() elif isinstance(subcon, Edge): - #print(f'attaching {object.id} to {subcon.id}') - subcon._attach_to(object, i_end) - # object._attach_to(subcon) + if subcon.attached_to[i_end]: + subcon.detachFrom(end=i_end) + # the above will eventually call subcon._detach_from(i_end) + + # could add a check that if it isn't the highest left edge and if it + # isn't called from another edge's _detach_from method, then error, + # or go up a level... + + + def addSubcomponents(self, items, iA=[0], iB=[-1]): + '''If items is a list: Adds a sequences of nodes and edges + (alternating) as subcomponents of this edge. It also connects + the sequence in the process, and saves it as a dict rather than list.?? + If items is a list: Adds them, assuming they are already assembled.?? + iA, iB : index of the end subcomponent(s) - provided as lists + ''' + + # Attach the sequency of nodes and edges to each other + assemble(items) + + # Store them as subcomponents of this edge + self.subcomponents = items # dict(enumerate(items)) + for item in items: + if isinstance(item, list): + for branch in item: + for subitem in branch: + subitem.part_of = self + else: + item.part_of = self + # Assign ends + if isinstance(items[0],list): + self.subcons_A = [it[0] for it in items[0]] # subcomponent for end A (can be multiple) + else: + self.subcons_A = list([items[0]]) # subcomponents for end A (can be multiple) + if isinstance(items[-1],list): + self.subcons_B = [it[-1] for it in items[-1]] # subcomponent for end B (can be multiple) + else: + self.subcons_B = list([items[-1]]) # subcomponent for end B (can be multiple) + + + ''' + # Make sure the subcomponents ends are connected appropriately + # to whatever this Edge might be attached to + >>> this seems like it shouldn't be done anymore! <<< + for i in [0,1]: + if self.attached_to[end_inds[i]]: + if isinstance(self.attached_to[end_inds[i], Node): + self._attach_to(self.attached_to[end_inds[i]]) + else: # it's an edge, so also tell it which end should be attached + self._attach_to(self.attached_to[end_inds[i]], end_ends[i]) + + if self.attached_to[0]: + for i in self.end_inds[0]: + + if isinstance(self.attached_to[end_inds[i], Node): + self._attach_to(self.attached_to[end_inds[i]]) + else: # it's an edge, so also tell it which end should be attached + self._attach_to(self.attached_to[end_inds[i]], end_ends[i]) + #self._attach_to(self.attached_to[0], 0) + if self.attached_to[1]: + self._attach_to(self.attached_to[1], 1) + ''' + """ + def getTopLevelObject(self, end): + '''If this edge is part of a higher-level object, and the request + corresponds to an end of that higher-level group, return the higher object, + otherwise return this same object. Can be recursive. + A similar method exists for nodes.''' + + i_end = endToIndex(end) + + if self.part_of: # if part of something higher + supe = self.part_of # shorthand for the super edge + if supe.subcomponents[-i_end] == self: # if we're at an end of supe + return supe.getTopLevelEdge(i_end), i_end # return supe, and which end + + else: + return self, i_end # if not part of something bigger, just return self + """ + + def findEnd(self, object): + '''Checks if object is a subcomponent of self and which end it's at.''' + + if not object in self.subcomponents: + obj_in = False + for sub in self.subcomponents: + if isinstance(sub,list): + for subsub in sub: + if object in subsub: + obj_in = True + break + if not obj_in: + raise Exception("This object is not a subcomponent of this edge!") + + if any([object is con for con in self.subcons_A]): + end = 0 + elif any([object is con for con in self.subcons_B]): + end = 1 + else: + end = -1 # object isn't at the end of the higher level edge so do nothing + + return end + + + def getSubcomponent(self, index): + '''Returns the subcomponent of the edge corresponding to the provided + index. An index with multiple entries can be used to refer to parallel + subcomponents. + + Parameters + ---------- + index: list + The index of the subcomponent requested to be returned. Examples: + [2]: return the third subcomponent in the series (assuming there + are no parallel subcomponents). + [2,1,0]: Return the first (or only) object along the second + parallel string at the third serial position. Same as [2,1]. + [1,0,2]: Return the third object along the first paralle string + at the first serial position. + ''' + + if np.isscalar(index): + index = [index] # put into a common list format if not already + + if len(index) == 2: # assume length 2 is for a parallel branch with + index.append(0) # with just one edge object, so add that 0 index. + + # Only one index means the simple case without a parallel string here + if len(index) == 1: + if isinstance(self.subcomponents[index[0]], list): + raise Exception('There is a parallel string at the requested index.') + + object = self.subcomponents[index[0]] + + # Three indices means an object along a parallel string + elif len(index) == 3: + if not isinstance(self.subcomponents[index[0]], list): + raise Exception('There is not a parallel string at the requested index.') + if len(self.subcomponents[index[0]]) < index[1]+1: + raise Exception('The number of parallel strings is less than the requested index.') + if len(self.subcomponents[index[0]][index[1]]) < index[2]+1: + raise Exception('The number of objects along the parallel string is less than the requested index.') + object = self.subcomponents[index[0]][index[1]][index[2]] + + else: # other options are not yet supported + raise Exception('Index must be length 1 or 3.') + + return object + + + def setEndPosition(self, r, end): + '''Set the position of an end of the edge. This method should only be + called by a Node's setPosition method if the edge end is attached to + the node. + + Parameters + ---------- + r : list + x and y coordinates to set the end at [m]. + end + Which end of the edge is being positioned, 'a' or 'b'. ''' - if end: - self.sub_edges[-1]._attach_to(object, end) + + if end in ['a', 'A', 0]: + self.rA = np.array(r) + elif end in ['b', 'B', 1]: + self.rB = np.array(r) else: - self.sub_edges[0]._attach_to(object, end) + raise Exception('End A or B must be specified with either the letter, 0/1, or False/True.') + + + def delete(self): + '''Detach the point from anything it's attached to, then delete the + object (if such a thing is possible?).''' + + self.detachFrom(0) # detach end A + self.detachFrom(1) # detach end B + + # next step would just be to separately remove any other references + # to this object... + + + +class Poly(): + '''A base class for objects that run between two OR MORE Node-type objects. + + It has attached_to dictionaries for ends 0-N, with entries formatted as: + id : { 'ref' : object ) # simply a reference to the object it's attached to + + With a Poly, end is no longer A/B (0/1) but instead 0:N where N is the + number of exposed ends of the poly. + + >>>> + For grouped/multilevel edges, connections are stored at the highest level + in the nodes they are attached to. But each edge object will store what it's + attached to in its attached_to dict, even though that's repeated info between + the levels. + In general, edge methods will worry about their internal subcomponents, but won't + "look up" to see if they are part of something bigger. Except getTopLevelEdge(). <<<< revise + ''' + + def __init__(self, id): + + self.id = id # id number or string, used as the key when attached to things + + self.attached_to = [None, None] # whether either end [A, B] of this object is bound to another object + + # End locations + self.r = [[0,0]] + + # Some attributes related to super-polies used to group things + self.part_of = None # whether this object is part of a Poly group + + self.subcomponents = [] # collection of edges and nodes that make up this Poly + + self.sub_end_indices = [] # subcomponent index of each attachable end of this edge + self.sub_end_ends = [] # if the subcomponent is an edge, which end corresponds to self Edge's end + + whole = True # false if there are sub edges/nodes that aren't connected + + + def isAttachedTo(self, query, end=None): + '''Checks if this poly is attached to the Node object 'query'. + It uses the node's isAttached method since that will also check + if this poly in question is part of a higher-level poly that + might be what is stored in the attachments list. + + Parameters + ---------- + query + The Node we might be attached to. + end + Which end of the poly being asked about, 0:N. + ''' + + if isinstance(query, Node): # call the node's method; it's easy + return query.isAttached(self, end=end) + else: + raise Exception('Polies can only be attached to Nodes.') + + + def attachTo(self, object, r_rel=[0,0], end=None): + '''Attach an end of this poly to some node. + + Parameters + ---------- + object + The Node object to attach to. + r_rel : list + x and y coordinates of the attachment point [m]. + end + Which end of the line is being attached, 0:N. ''' + # Determine which end to attach + if end == None: + raise Exception("Poly end must be given...") + else: + i_end = int(end) # <<>> endToIndex(end, estr='when attaching an edge to something.') + + # Make sure this end isn't already attached to something + if self.attached_to[i_end]: + self.detachFrom(end=i_end) + + # Tell the Node in question to initiate the attachment + # (this will also call self._attach_to) + object.attach(self, r_rel=r_rel, end=i_end) + + + def _attach_to(self, object, end): + '''Internal method to update the poly's attached_to registry when + requested by a node. In nested polies, expects to be called for the + highest-level one first, then it will recursively call any lower ones. + + Parameters + ---------- + object + The Node object to attach to. + end + Which end of the Poly is being attached, 0:N. + ''' + i_end = endToIndex(end) # <<< (all these are redundant for polies?) <<< + + if not isinstance(object, Node): + raise Exception('Poly objects can only be attached to Node objects.') + + # Add it to the attached_to registry + self.attached_to[i_end] = object + + # Recursively attach any subcomponent at the end + if len(self.subcomponents) > 0: + subcon = self.subcomponents[-i_end] + + if isinstance(subcon, Node): # if the end subcomponent is a node + subcon._attach_to(object) + + elif isinstance(subcon, Edge): # if it's an edge + subcon._attach_to(object, i_end) + + def detachFrom(self, end): - '''Detach the specified end of the edge from whatever it's attached to. + '''Detach the specified end of the poly from whatever it's attached to. Parameters ---------- @@ -468,7 +1041,7 @@ def detachFrom(self, end): Which end of the line is being dettached, 'a' or 'b'. ''' # Determine which end to detach - i_end = endToIndex(end, estr='when detaching an edge.') + #i_end = endToIndex(end, estr='when detaching a poly.') # Tell the Node the end is attached to to initiate detachment # (this will then also call self._detach_from) @@ -476,9 +1049,9 @@ def detachFrom(self, end): def _detach_from(self, end): - '''Internal method to update the edge's attached_to registry when - requested by a node. In nested edges, expects to be called for the - highest-level on first, then it will recursively call any lower ones. + '''Internal method to update the poly's attached_to registry when + requested by a node. In nested polies, expects to be called for the + highest-level one first, then it will recursively call any lower ones. Parameters ---------- @@ -490,6 +1063,9 @@ def _detach_from(self, end): self.attached_to[i_end] = None # Recursively detach the ends of any sub-edges + + #>>> need to figure out which subcomponent would correspond to the requested end of the poly <<< + if len(self.subcomponents) > 0: subcon = self.subcomponents[-i_end] # index 0 for A, -1 for B @@ -512,29 +1088,33 @@ def _detach_from(self, end): def addSubcomponents(self, items): - '''Adds a sequences of nodes and edges (alternating) as subcomponents - of this edge. It also connects the sequence in the process.''' + '''Adds a collection of nodes and edges as subcomponents of this Poly. + These subcomponents should already be attached to each other.''' + # >>> check if the items are already assembled? <<< - assemble(items) + # Store and register them as subcomponents of this Poly self.subcomponents = items for item in items: item.part_of = self + # Make sure the subcomponents ends are connected appropriately # to whatever this Edge might be attached to - if self.attached_to[0]: - self._attach_to(self.attached_to[0], 0) - if self.attached_to[1]: - self._attach_to(self.attached_to[1], 1) - - + for i in range(len(end_inds)): + if self.attached_to[end_inds[i]]: + if isinstance(self.attached_to[end_inds[i]], Node): + self._attach_to(self.attached_to[end_inds[i]]) + else: # it's an edge, so also tell it which end should be attached + self._attach_to(self.attached_to[end_inds[i]], end_ends[i]) + + """ def getTopLevelEdge(self, end): '''If this edge is part of a higher-level edge group, and the request corresponds to an end of that higher-level group, return the higher edge, otherwise return this same object. Can be recursive. A similar method exists for nodes.''' - + >>> i_end = endToIndex(end) if self.part_of: # if part of something higher @@ -567,7 +1147,7 @@ def setEndPosition(self, r, end): end Which end of the edge is being positioned, 'a' or 'b'. ''' - + >>> if end in ['a', 'A', 0]: self.rA = np.array(r) elif end in ['b', 'B', 1]: @@ -579,17 +1159,18 @@ def setEndPosition(self, r, end): def delete(self): '''Detach the point from anything it's attached to, then delete the object (if such a thing is possible?).''' - + >>>> self.detachFrom(0) # detach end A self.detachFrom(1) # detach end B # next step would just be to separately remove any other references # to this object... + """ # general functions -def are_attached(object1, object2): +def areAttached(object1, object2): '''Check if two objects are attached to each other. If both are nodes, checks if one is in the attached list of the other. If both are edges, checks if both are attached to the same node. @@ -606,7 +1187,7 @@ def detach(object1, object2): def attach(self, object1, object2, r_rel=[0,0], end=None):#end1=None, end2=None): '''Attached 1 to object2, as long as the object types are compatible. - Ends can to be specified if either object is an end type, otherwise + Ends can to be specified if either object is an edge type, otherwise if not specified then an available end will be connected. ''' @@ -626,25 +1207,71 @@ def attach(self, object1, object2, r_rel=[0,0], end=None):#end1=None, end2=None) # lower-level utility functions -def endToIndex(end, estr=''): +def endToIndex(end, estr='', n=2): '''Converts an end specifier (a, b, A, B, 0, 1) to just 0, 1 for use in the attached_to indexing of an Edge-type object.''' - - if end in ['a', 'A', 0]: - return 0 - elif end in ['b', 'B', 1]: - return 1 - else: - raise Exception('End A/B must be specified (with a/b or 0/1) '+estr) + + if type(end) == str: + if len(end) == 1: + end = ord(end.lower())-97 # convert letter to integer (A=0, b=1, etc) + else: + raise Exception("When providing 'end' as a string, it must be a single letter.") + + if not type(end) == int: + raise Exception('End must be provided as a character or integer.') + + if end < 0: + raise Exception('End must be positive.') + elif end > n-1: + if n==2: + raise Exception('End A/B must be specified (with a/b or 0/1) '+estr) + else: + raise Exception(f'The specified end value exceeds the limit of {n} values from 0/A' +estr) + + return end def assemble(items): '''Strings together a sequence of nodes and edges''' - n = len(items) + # >>> TODO: adjust this so it can connect parallel elements .eg. for bridles <<< + ''' + # If the provided items isn't a list, there's nothing to assemble so return + if not isinstance(items[i], List): + print('Not a list - returning!') + return + ''' + n = len(items) for i in range(n-1): - if isinstance(items[i], Node) and isinstance(items[i+1], Edge): + if isinstance(items[i], list): + for subitem in items[i]: # go through each parallel subitem + if isinstance(subitem, list): # if it's a concatenation of multiple things + + assemble(subitem) # make sure that any sublist is assembled + + # attach the end objects of the subitem to the nodes before and after + if i > 0 and isinstance(items[i-1], Node): # attach to previous node + items[i-1].attach(subitem[0], end='a') + if i < n-1 and isinstance(items[i+1], Node): # attach to next node + items[i+1].attach(subitem[-1], end='b') + # note: this requires the end objects to be edges + + elif isinstance(subitem, Edge): # if the subitem is just one edge + print("THIS CASE SHOULDN'T HAPPEN - the list should be nested more") + breakpoint() + if i > 0 and isinstance(items[i-1], Node): # attach to previous node + items[i-1].attach(subitem, end='a') + if i < n-1 and isinstance(items[i+1], Node): # attach to next node + items[i+1].attach(subitem, end='b') + else: + raise Exception("Unsupported situation ... parallel subitems must be edges or concatenations") + + elif isinstance(items[i], Node) and isinstance(items[i+1], list): + pass # this node connects to a bridle or doubled section, + # so it will be hooked up in the next step + + elif isinstance(items[i], Node) and isinstance(items[i+1], Edge): items[i].attach(items[i+1], end='a') elif isinstance(items[i], Edge) and isinstance(items[i+1], Node): @@ -652,9 +1279,63 @@ def assemble(items): else: raise Exception('sequences is not alternating between nodes and edges') + # check if last item in items is a list (if length of items>1) + # if it is a list, it won't have been attached/assembled previously, so + # attach and assemble now + if n-1>0: + if isinstance(items[i+1], list): + for subitem in items[i+1]: # go through each parallel subitem + if isinstance(subitem, list): # if it's a concatenation of multiple things + assemble(subitem) # make sure that any sublist is assembled + + # attach the end objects of the subitem to the nodes before and after + if i > 0 and isinstance(items[i], Node): # attach to previous node + items[i].attach(subitem[0], end='a') + if i < n-1 and isinstance(items[i+1], Node): # attach to next node + items[i+1].attach(subitem[-1], end='b') + # note: this requires the end objects to be edges + + elif isinstance(subitem, Edge): # if the subitem is just one edge + print("THIS CASE SHOULDN'T HAPPEN - the list should be nested more") + breakpoint() + if i > 0 and isinstance(items[i], Node): # attach to previous node + items[i].attach(subitem, end='a') + if i < n-1 and isinstance(items[i+1], Node): # attach to next node + items[i+1].attach(subitem, end='b') + else: + raise Exception("Unsupported situation ... parallel subitems must be edges or concatenations") + +def rotationMatrix(x3,x2,x1): + '''Calculates a rotation matrix based on order-z,y,x instrinsic (tait-bryan?) angles, meaning + they are about the ROTATED axes. (rotation about z-axis would be (0,0,theta) ) + (Copied from MoorPy) + Parameters + ---------- + x3, x2, x1: floats + The angles that the rotated axes are from the nonrotated axes. Normally roll,pitch,yaw respectively. [rad] + + Returns + ------- + R : matrix + The rotation matrix + ''' + # initialize the sines and cosines + s1 = np.sin(x1) + c1 = np.cos(x1) + s2 = np.sin(x2) + c2 = np.cos(x2) + s3 = np.sin(x3) + c3 = np.cos(x3) + # create the rotation matrix + R = np.array([[ c1*c2, c1*s2*s3-c3*s1, s1*s3+c1*c3*s2], + [ c2*s1, c1*c3+s1*s2*s3, c3*s1*s2-c1*s3], + [ -s2, c2*s3, c2*c3]]) + return R + + # test script if __name__ == '__main__': @@ -679,7 +1360,7 @@ def assemble(items): edge1.attachTo(node2, end='a') - # ----- make a test for super edges... ----- + # ----- a test for super edges... ----- e0 = Edge(id='e0') e1 = Edge(id='e1') @@ -696,6 +1377,126 @@ def assemble(items): assemble([A, E, B]) - E.addSubcomponents([e0,n0,e1,n1,e2]) + E.addSubcomponents([e0,n0,e1,n1,e2]) + + + # ----- a test for bridles etc ----- + + e0 = Edge(id='e0') + n0 = Node(id='n0') + e1 = Edge(id='e1') + n1 = Node(id='n1') + e2a1 = Edge(id='e2a') + e2a2 = Node(id='e2a') + e2a3 = Edge(id='e2a') + e2b = Edge(id='e2b') + + + thelist = [e0, n0, e1, n1, [[e2a1, e2a2, e2a3], [e2b]]] + E = Edge(id='big edge') + + E.addSubcomponents(thelist) + + s = E.getSubcomponent([4,0,2]) + + # ----- try joining two nodes ----- + """ + A = Node(id='Node A') + B = Node(id='Node B') + A.join(B) + + + # ----- tests connecting multi-level node and edge objects ---- + + # --- Test 1 --- + # platform and fairlead + n1 = Node(id='n1') + n2 = Node(id='n2') + n1.attach(n2, r_rel=[20,0,-10]) + # mooring and contents + e1 = Edge(id='e1') + e1_e1 = Edge(id='e1_e1') + e1_n2 = Node(id='e1_n2') + e1_e3 = Edge(id='e1_e3') + e1.addSubcomponents([e1_e1, e1_n2, e1_e3]) + # attach mooring to platfrom (by lower objects, then upper will be automatic) + #n2.attach(e1_e1, end='A') + n2.attach(e1_e3, end='B') + + # --- Test 2 --- + # platform and fairlead + n1 = Node(id='n1') + n2 = Node(id='n2') + n1.attach(n2, r_rel=[20,0,-10]) + # mooring and contents + e1 = Edge(id='e1') + e1_n1 = Node(id='e1_n1') + e1_e2 = Edge(id='e1_e2') + e1_n3 = Node(id='e1_n3') + e1.addSubcomponents([e1_n1, e1_e2, e1_n3]) + # attach mooring to platfrom (by lower objects, then upper will be automatic) + n2.attach(e1_n1) + #n2.attach(e1_n3) + #n2.join(e1_n1) + #n2.join(e1_n3) + + # --- Test 3 --- + # platform and fairlead + n1 = Node(id='n1') + # mooring and contents + e1 = Edge(id='e1') + e1_e1 = Edge(id='e1_e1') + e1_n2 = Node(id='e1_n2') + e1_e3 = Edge(id='e1_e3') + e1.addSubcomponents([e1_e1, e1_n2, e1_e3]) + # attach mooring to platfrom (by lower objects, then upper will be automatic) + #n1.attach(e1_e1, r_rel=[20,0,-10], end='A') + n1.attach(e1_e3, r_rel=[20,0,-10], end='B') + + # --- Test 4 --- + # platform and fairlead + n1 = Node(id='n1') + # mooring and contents + e1 = Edge(id='e1') + e1_n1 = Node(id='e1_n1') + e1_e2 = Edge(id='e1_e2') + e1_n3 = Node(id='e1_n3') + e1.addSubcomponents([e1_n1, e1_e2, e1_n3]) + # attach mooring to platfrom (by lower objects, then upper will be automatic) + #n1.attach(e1_n1, r_rel=[20,0,-10]) + n1.attach(e1_n3, r_rel=[20,0,-10]) + + # --- Test 5 --- + # platform and fairlead + n1 = Node(id='n1') + n2 = Node(id='n2') + n1.attach(n2, r_rel=[20,0,-10]) + # mooring and contents + e1_e1 = Edge(id='e1_e1') + e1_n2 = Node(id='e1_n2') + e1_n2.attach(e1_e1, end='B') + # attach mooring to platfrom (by lower objects, then upper will be automatic) + n2.attach(e1_n2) + + # --- Test 6 --- + # platform and fairlead + n1 = Node(id='n1') + n2 = Node(id='n2') + n1.attach(n2, r_rel=[20,0,-10]) + # mooring and contents + e1_n1 = Node(id='e1_n1') + e1_e2 = Edge(id='e1_e2') + e1_n1.attach(e1_e2, end='A') + # attach mooring to platfrom (by lower objects, then upper will be automatic) + n2.attach(e1_e2, end='B') + # --- done tests --- + n1.setPosition(r=[0,0,0], theta=0) + #print(n1.attachments) + #print(e1.attached_to) + """ + + + + \ No newline at end of file diff --git a/famodel/helpers.py b/famodel/helpers.py index cfaf1f18..55f9b243 100644 --- a/famodel/helpers.py +++ b/famodel/helpers.py @@ -232,12 +232,16 @@ def head_adjust(att,heading,rad_buff=np.radians(30),endA_dir=1, adj_dir=1): att : list list of objects to attach to. 1 object if only concerned about the attached object associated with that side heading : float - Cable heading at attachment to att in radians + Cable compass heading at attachment to att in radians rad_buff : float Buffer angle in radians endA_dir : float, optional Either 1 or -1, controls sign of new heading for end B. Only altered to -1 if dynamic cable from end A will get close to end B moorings. Default is 1. + adj_dir : float, optional + Either 1 or -1, default is 1. If -1, adjusts direction heading is altered + to avoid mooring lines, can be used if that heading direction is more natural. + This is a manual input to the main function adjusting cables. Returns ------- @@ -249,7 +253,7 @@ def head_adjust(att,heading,rad_buff=np.radians(30),endA_dir=1, adj_dir=1): headnew = np.pi*2 + heading else: headnew = heading - attheadings = [] + attheadings = [] # complete list of mooring headings to avoid, from all platforms flipheads = False # whether to flip headings ( for if you are looking at mooring headings of platform on the other end) for at in att: mhs = np.radians([m.heading for m in at.getMoorings().values()]) @@ -260,11 +264,11 @@ def head_adjust(att,heading,rad_buff=np.radians(30),endA_dir=1, adj_dir=1): if a>2*np.pi: atmh[j] = a-2*np.pi else: - atmh = np.array(mhs) #at.mooring_headings + at.phi + atmh = np.array(mhs) #attached platform mooring headings array #attheadings.extend(atmh) - attheadings.extend(np.pi/2 - atmh) # convert to 0 rad at East going CCW + attheadings.extend(atmh) # keep in compass heading flipheads = True - + interfere_h = check_headings(attheadings,headnew,rad_buff) # if the headings interfere, adjust them by angle buffer for mhead in interfere_h: @@ -286,6 +290,71 @@ def head_adjust(att,heading,rad_buff=np.radians(30),endA_dir=1, adj_dir=1): return(headnew) +def cableDesignInterpolation(dd, cables, depth): + '''Interpolates between dynamic cable designs for different depths to produce + a design for the given depth + + Parameters + ---------- + dd : dict + Design dictionary of cable object before interpolation + cables : list + List of dictionaries of cable designs to interpolate between + depth : float + Depth (abs val) of cable to interpolate design for + ''' + # grab list of values for all cables + + n_bs = len(dd['buoyancy_sections']) + cabdesign = {'n_buoys':[[] for _ in range(n_bs)], + 'spacings':[[] for _ in range(n_bs)], + 'L_mids':[[] for _ in range(n_bs)], + 'span': [], + 'L': []} + depths = [] + for cab in cables: + if len(cab['buoyancy_sections'])==n_bs: + for ii in range(n_bs): + cabdesign['n_buoys'][ii].append( + cab['buoyancy_sections'][ii]['N_modules']) + cabdesign['spacings'][ii].append( + cab['buoyancy_sections'][ii]['spacing']) + cabdesign['L_mids'][ii].append( + cab['buoyancy_sections'][ii]['L_mid']) + + cabdesign['L'].append(cab['L']) + depths.append(cab['depth']) + cabdesign['span'].append(cab['span']) + + # sort and interp all lists by increasing depths + sorted_indices = np.argsort(depths) + depths_sorted = [depths[i] for i in sorted_indices] + newdd = deepcopy(dd) + if depth > depths_sorted[-1]: + # depth outside range, can't interpolate - just adjust length + newdd['L'] = cabdesign['L'][sorted_indices[-1]] + depth-depths_sorted[-1] + elif depth < depths_sorted[0]: + # depth outside range, can't interpolate - just adjust length + newdd['L'] = cabdesign['L'][sorted_indices[0]] - depth-depths_sorted[0] + else: + # interpolate designs + newdd['span'] = np.interp(depth,depths_sorted, + [cabdesign['span'][i] for i in sorted_indices]) + for i,bs in enumerate(newdd['buoyancy_sections']): + bs['N_modules'] = np.interp(depth, depths_sorted, + [cabdesign['n_buoys'][i][j] for j in sorted_indices]) + bs['spacing'] = np.interp(depth,depths_sorted, + [cabdesign['spacings'][i][j] for j in sorted_indices]) + bs['L_mid'] = np.interp(depth,depths_sorted, + [cabdesign['L_mids'][i][j] for j in sorted_indices]) + newdd['L'] = np.interp(depth,depths_sorted, + [cabdesign['L'][j] for j in sorted_indices]) + newdd['depth'] = depth + newdd['z_anch'] = -depth + + + return(newdd) + def getCableDD(dd,selected_cable,cableConfig,cableType_def,connVal): ''' get cable design dictionary from a cableConfig yaml. Primarily used for project.addCablesConnections() @@ -317,11 +386,11 @@ def getCableDD(dd,selected_cable,cableConfig,cableType_def,connVal): # get connector and joint costs if they were given dd['connector_cost'] = getFromDict(selected_cable,'connector_cost',default=0) joint_cost = getFromDict(selected_cable,'joint_cost',default=0) - + depth = cableConfig['cableTypes'][selected_cable['sections'][0]]['depth'] for j in range(len(selected_cable['sections'])): dd['cables'].append(deepcopy(cableConfig['cableTypes'][selected_cable['sections'][j]])) cd = dd['cables'][j] - cd['z_anch'] = -selected_cable['depth'] + cd['z_anch'] = -depth # cd['cable_type'] = cableConfig['cableTypes'][selected_cable['sections'][j]] # assign info in selected cable section dict to cd cd['A'] = selected_cable['A'] cd['voltage'] = cableType_def[-2:] @@ -354,73 +423,140 @@ def getCableDD(dd,selected_cable,cableConfig,cableType_def,connVal): return(dd) +def getCandidateCableDesigns(cable_reqs, cable_configs): + ''' + Returns list of cable designs that meet requirements + + Parameters + ---------- + cable_reqs : TYPE + DESCRIPTION. + cable_configs : TYPE + DESCRIPTION. + + Returns + ------- + None. + + ''' + candidate_cables = [] + for config in cable_configs: + if np.allclose( + np.array([cable_reqs[key] for key in cable_reqs.keys()]), + np.array([config[key] for key in cable_reqs.keys()]) + ): + candidate_cables.append(config) + + return(candidate_cables) + def getCableDesign(connVal, cableType_def, cableConfig, configType, depth=None): # go through each index in the list and create a cable, connect to platforms dd = {} dd['cables'] = [] # collect design dictionary info on cable - - # create reference cables (these are not saved into the cableList, just used for reference) + if connVal['cable_id']>100: + # connected to substation, overwrite cable type + ctype = 0 + else: + ctype=configType + + cable_reqs = {'A': connVal['conductor_area'], + 'type': ctype } - # find associated cable in cableConfig dict - cableAs = [] - cableDs = [] - cable_selection = [] - for cabC in cableConfig['configs']: - if connVal['conductor_area'] == cabC['A']: - cableAs.append(cabC) - if not cableAs: - raise Exception('Cable configs provided do not match required conductor area') - elif len(cableAs) == 1: - cable_selection = cableAs - else: - for cabA in cableAs: - # only check distance if the cable is NOT connected to substation - if 'dist' in cabA and connVal['cable_id']<100: - if abs(connVal['2Dlength'] - cabA['dist']) < 0.1: - cableDs.append(cabA) + if ctype>0: + cable_reqs['dist'] = connVal['2Dlength'] - #if there's no matching distance, assume the nonsuspended cables - if cableDs == []: - for cabA in cableAs: - if cabA['type'] == 0: - cableDs.append(cabA) + cable_candidates = getCandidateCableDesigns(cable_reqs, + cableConfig['configs']) + if not cable_candidates: + # change type to dynamic-static-dynamic and try again + cable_reqs['type']=0 + cable_candidates = getCandidateCableDesigns(cable_reqs, + cableConfig['configs']) + + if len(cable_candidates)> 1: + # downselect by depth + depthdiff = np.array([x['depth']-depth for x in cable_candidates]) + selected_cable = cable_candidates[np.argmin(depthdiff)] + elif len(cable_candidates) == 1: + # found the correct cable + selected_cable = cable_candidates[0] + else: + raise Exception(f"No cable matching the selection criteria found for cable {connVal['cable_id']}") + + # # create reference cables (these are not saved into the cableList, just used for reference) + + # # find associated cable in cableConfig dict + # cableAs = [] + # cableDs = [] + # cable_selection = [] + # for cabC in cableConfig['configs']: + # if connVal['conductor_area'] == cabC['A']: + # cableAs.append(cabC) + # if not cableAs: + # raise Exception('Cable configs provided do not match required conductor area') + # elif len(cableAs) == 1: + # cable_selection = cableAs + # cableDs = cableAs # needed for interpolation procedure + # else: + # for cabA in cableAs: + # # only check distance if the cable is NOT connected to substation + # if 'dist' in cabA and connVal['cable_id']<100: + # if abs(connVal['2Dlength'] - cabA['dist']) < 0.1: + # cableDs.append(cabA) + + # #if there's no matching distance, assume the nonsuspended cables + # if cableDs == []: + # for cabA in cableAs: + # if cabA['type'] == 0: + # cableDs.append(cabA) - for cabD in cableDs: - if connVal['cable_id']>=100 and cabD['type']==0: - # connected to a substation, use a dynamic-static-dynamic configuration - cable_selection.append(cabD) + # for cabD in cableDs: + # if connVal['cable_id']>=100 and cabD['type']==0: + # # connected to a substation, use a dynamic-static-dynamic configuration + # cable_selection.append(cabD) - elif connVal['cable_id']<100 and cabD['type']==configType: - # not connected to substation, use default config type - cable_selection.append(cabD) + # elif connVal['cable_id']<100 and cabD['type']==configType: + # # not connected to substation, use default config type + # cable_selection.append(cabD) - # if no cables are found to match, override the configType + # # if no cables are found to match, override the configType - if cable_selection == []: - for cabD in cableDs: - if connVal['cable_id']<100: - cable_selection.append(cabD) + # if cable_selection == []: + # for cabD in cableDs: + # if connVal['cable_id']<100: + # cable_selection.append(cabD) - if len(cable_selection)> 1: - # downselect by depth - depthdiff = np.array([x['depth']-depth for x in cable_selection]) - selected_cable = cable_selection[np.argmin(depthdiff)] - # else: - # raise Exception(f"Multiple cables match selection criteria for cable {connDict[i]['cable_id']}") - elif len(cable_selection) == 1: - # found the correct cable - selected_cable = cable_selection[0] + # if len(cable_selection)> 1: + # # downselect by depth + # depthdiff = np.array([x['depth']-depth for x in cable_selection]) + # selected_cable = cable_selection[np.argmin(depthdiff)] + # # else: + # # raise Exception(f"Multiple cables match selection criteria for cable {connDict[i]['cable_id']}") + # elif len(cable_selection) == 1: + # # found the correct cable + # selected_cable = cable_selection[0] - else: - raise Exception(f"No cable matching the selection criteria found for cable {connVal['cable_id']}") - - dd = getCableDD(dd,selected_cable,cableConfig,cableType_def,connVal) + # else: + # raise Exception(f"No cable matching the selection criteria found for cable {connVal['cable_id']}") + dd = getCableDD(dd,selected_cable,cableConfig,cableType_def,connVal) + i_dc = [i for i,sec in enumerate(dd['cables']) if sec['type']=='dynamic'] dd['name'] = cableType_def + dc_cands = [] + # pull out the dc definitions of candidate cables + for cand in cable_candidates: + cand = dict(cand) + for sec in cand['sections']: + typedef = cableConfig['cableTypes'][sec] + if typedef['type']=='dynamic' and typedef not in dc_cands: + dc_cands.append(cableConfig['cableTypes'][sec]) + for i in i_dc: + dd['cables'][i] = cableDesignInterpolation( + dd['cables'][i], dc_cands, depth) - return(selected_cable,deepcopy(dd)) + return(selected_cable,deepcopy(dd), cable_candidates) def getDynamicCables(cable_config, cable_types, cable_appendages, depth, rho_water=1025, g=9.81): @@ -659,6 +795,7 @@ def MooringProps(mCon, lineTypes, rho_water, g, checkType=1): # else: # d_vol = dd['d'] dd['w'] = (dd['m']-np.pi/4*d_vol**2*rho_water)*g + dd['MBL'] = float(dd['MBL']) if 'mooringFamily' in mCon: raise Exception('type and moorFamily listed in yaml - use type to reference a mooring type in the mooring_line_types section of the yaml and mooringFamily to obtain mooring properties from MoorProps_default.yaml') elif 'mooringFamily' in mCon: @@ -670,6 +807,7 @@ def MooringProps(mCon, lineTypes, rho_water, g, checkType=1): dd = mProps dd['name'] = mCon['mooringFamily'] dd['d_nom'] = mCon['d_nom'] + dd['MBL'] = float(dd['MBL']) elif 'type' in mCon and not mCon['type'] in lineTypes: raise Exception(f'Type {mCon["type"]} provided in mooring_line_config {mCon} is not found in mooring_line_types section. Check for errors.') @@ -693,20 +831,20 @@ def getMoorings(lcID, lineConfigs, connectorTypes, pfID, proj): Returns ------- - m_config : dict - mooring configuration dictionary - c_config : dict - connector configuration dictionary + dd : dict + mooring design dictionary ''' # set up dictionary of information on the mooring configurations - m_config = {'sections':[],'anchor':{},'span':{},'zAnchor':{}}#,'EndPositions':{}} + dd = {'span':{},'zAnchor':{}}#,'EndPositions':{}} # set up connector dictionary c_config = [] + config = [] # mooring and connector combined configuation list lineLast = 1 # boolean whether item with index k-1 is a line. Set to 1 for first run through of for loop ct = 0 # counter for number of line types - for k in range(0,len(lineConfigs[lcID]['sections'])): # loop through each section in the line + nsec = len(lineConfigs[lcID]['sections']) # number of sections + for k in range(0,nsec): # loop through each section in the line lc = lineConfigs[lcID]['sections'][k] # set location for code clarity later # determine if it's a line type or a connector listed @@ -714,19 +852,20 @@ def getMoorings(lcID, lineConfigs, connectorTypes, pfID, proj): # this is a line if lineLast: # previous item in list was a line (or this is the first item in a list) # no connector was specified for before this line - add an empty connector + config.append({}) c_config.append({}) # set line information lt = MooringProps(lc, proj.lineTypes, proj.rho_water, proj.g) # lt = self.lineTypes[lc['type']] # set location for code clarity and brevity later # set up sub-dictionaries that will contain info on the line type - m_config['sections'].append({'type':lt})# {'name':str(ct)+'_'+lc['type'],'d_nom':lt['d_nom'],'material':lt['material'],'d_vol':lt['d_vol'],'m':lt['m'],'EA':float(lt['EA'])}}) - m_config['sections'][ct]['type']['name'] = str(ct)+'_'+str(lt['name']) + config.append({'type':lt})# {'name':str(ct)+'_'+lc['type'],'d_nom':lt['d_nom'],'material':lt['material'],'d_vol':lt['d_vol'],'m':lt['m'],'EA':float(lt['EA'])}}) + config[-1]['type']['name'] = str(ct)+'_'+str(lt['name']) # make EA a float not a string - m_config['sections'][ct]['type']['EA'] = float(lt['EA']) + config[-1]['type']['EA'] = float(lt['EA']) + config[-1]['type']['MBL'] = float(lt['MBL']) # set line length - m_config['sections'][ct]['L'] = lc['length'] - # update counter for line types - ct = ct + 1 + config[-1]['L'] = lc['length'] + # update line last boolean lineLast = 1 @@ -740,56 +879,107 @@ def getMoorings(lcID, lineConfigs, connectorTypes, pfID, proj): # last item in list was a line if cID in connectorTypes: - c_config.append(connectorTypes[cID]) # add connector to list - c_config[-1]['type'] = cID + config.append(connectorTypes[cID]) # add connector to list + config[-1]['type'] = cID else: # try pointProps try: props = loadPointProps(None) design = {f"num_c_{cID}":1} - c_config.append(getPointProps(design, Props=props)) + config.append(getPointProps(design, Props=props)) + except Exception as e: raise Exception(f"Connector type {cID} not found in connector_types dictionary, and getPointProps raised the following exception:",e) # update lineLast boolean lineLast = 0 + elif 'subsections' in lc: + # TODO: LHS: ERROR CHECKING FOR ORDER OF COMPONENTS PROVIDED WITHIN SUBSECTIONS, ADD IN NEEDED CONNECTORS!! + + if lineLast and k != 0: + # if this is not the first section AND last section was a line, add a empty connector first + config.append({}) + lineLast = 0 + config.append([]) + sublineLast = [lineLast]*len(lc['subsections']) # to check if there was a connector provided before this + for ii,sub in enumerate(lc['subsections']): + config[-1].append([]) + for jj,subsub in enumerate(sub): + if 'connectorType' in subsub and sublineLast[ii]: + cID = subsub['connectorType'] + if cID in connectorTypes: + cID = subsub['connectorType'] + config[-1][-1].append(connectorTypes[cID]) + else: + # try pointProps + try: + props = loadPointProps(None) + design = {f"num_c_{cID}":1} + config[-1][-1].append(getPointProps(design, Props=props)) + except Exception as e: + raise Exception(f"Connector type {cID} not found in connector_types dictionary, and getPointProps raised the following exception:",e) + sublineLast[ii] = 0 + elif 'connectorType' in subsub and not sublineLast[ii]: + raise Exception('Previous section had a connector, two connectors cannot be listed in a row') + elif 'type' or 'mooringFamily' in subsub: + if sublineLast[ii]: + # add empty connector + config[-1][-1].append({}) + lt = MooringProps(subsub,proj.lineTypes, proj.rho_water, proj.g) + config[-1][-1].append({'type':lt, + 'L': subsub['length']}) + # make EA a float not a string + config[-1][-1][-1]['type']['EA'] = float(lt['EA']) + config[-1][-1][-1]['type']['MBL'] = float(lt['MBL']) + sublineLast[ii] = 1 + else: + raise Exception(f"keys in subsection line definitions must either be 'type', 'mooringFamily', or 'connectorType'") + # if this is the last section and the last part of the subsection in the section, it needs to end on a connector + # so, add a connector if last part of subsection was a line! + if sublineLast[ii] and k==nsec-1 and jj==len(sub)-1: + # end bridle needs connectors added + config[-1][-1].append({}) + sublineLast[ii] = 0 + + lineLast = sublineLast[-1] # TODO: LHS: think how to handle this situation for error checking... else: # not a connector or a line - raise Exception(f"Please make sure that all section entries for line configuration '{lcID}' are either line sections (which must have a 'type' key) or connectors (which must have a 'connectorType' key") + raise Exception(f"Please make sure that all section entries for line configuration '{lcID}' are either line sections (which must have a 'type' key), connectors (which must have a 'connectorType' key, or subsections") # check if line is a shared symmetrical configuration if 'symmetric' in lineConfigs[lcID] and lineConfigs[lcID]['symmetric']: if not lineLast: # check if last item in line config list was a connector - for ii in range(0,ct): + for ii in range(len()): # set mooring configuration - m_config['sections'].append(m_config['sections'][-1-2*ii]) + config.append(config[-1-2*ii]) # set connector (since it's mirrored, connector B becomes connector A) - c_config.append(c_config[-2-2*ii]) + config.append(config[-2-2*ii]) else: # double the length of the end line - m_config['sections'][-1]['L'] = m_config['sections'][-1]['L']*2 + config[-1]['L'] =config[-1]['L']*2 # set connector B for line same as previous listed connector - c_config.append(c_config[-1]) + config.append(config[-1]) for ii in range(0,ct-1): # go through every line config except the last (since it was doubled already) # set mooring configuration - m_config['sections'].append(m_config['sections'][-2-2*ii]) + config.append(config[-2-2*ii]) # set connector - c_config.append(c_config[-3-2*ii]) + config.append(config[-3-2*ii]) else: # if not a symmetric line, check if last item was a line (if so need to add another empty connector) if lineLast: # add an empty connector object - c_config.append({}) + config.append({}) # set general information on the whole line (not just a section/line type) # set to general depth first (will adjust to depth at anchor location after repositioning finds new anchor location) - m_config['zAnchor'] = -proj.depth - m_config['span'] = lineConfigs[lcID]['span'] - m_config['name'] = lcID + dd['subcomponents'] = config + dd['zAnchor'] = -proj.depth + dd['span'] = lineConfigs[lcID]['span'] + dd['name'] = lcID # add fairlead radius and depth to dictionary - m_config['rad_fair'] = proj.platformList[pfID].rFair - m_config['z_fair'] = proj.platformList[pfID].zFair + dd['rad_fair'] = proj.platformList[pfID].rFair + dd['z_fair'] = proj.platformList[pfID].zFair - m_config['connectors'] = c_config # add connectors section to the mooring dict - return(m_config) #, c_config) + return(dd) #, c_config) + def getConnectors(c_config, mName, proj): @@ -850,8 +1040,96 @@ def getAnchors(lineAnch, arrayAnchor, proj): return(ad, mass) -def route_around_anchors(proj, anchor=True, cable=True, padding=50): +def attachFairleads(moor, end, platform, fair_ID_start=None, fair_ID=None, fair_inds=None): + ''' + helper function for loading, attaches fairleads to mooring objects + and runs some error checks + + Parameters + ---------- + fair_inds : int/list + Fairlead index/indices to attach to mooring line + moor : Mooring class instance + Mooring that will attach to fairlead(s) + end : int or str + must be in [0,a,A] for end A or [1,b,B] for end B + platform : Platform class instance + Platform that is associated with the fairlead + fair_ID_start : str, optional + start of fairlead ID, the index will be appended to this. Not needed if fair_ID provided + fair_ID : list, optional + fairlead ID list for each fairlead. If fair_ID_start is not provided, fair_ID must be provided + + + Returns + ------- + None. + + ''' + # convert to list if needed + if fair_inds is not None : + if not isinstance(fair_inds,list): + fair_inds = list([fair_inds]) + # check lengths are the same + if not len(moor.subcons_B)==len(fair_inds): + raise Exception(f'Number of fairleads must equal number of parallel sections at end {end}') + elif fair_ID is not None: + if not isinstance(fair_ID, list): + fair_ID = list([fair_ID]) + # check lengths are the same + if not len(moor.subcons_B)==len(fair_ID): + raise Exception(f'Number of fairleads must equal number of parallel sections at end {end}') + else: + raise Exception('Either fairlead indices or fairlead IDs must be provided') + # grab correct end + end_subcons = moor.subcons_B if end in [1,'b','B'] else moor.subcons_A + + # put together fairlead ids as needed + if fair_ID_start != None and fair_inds != None: + fair_ID = [] + for i in fair_inds: + fair_ID.append(fair_ID_start+str(i)) + # attach each fairlead to the end subcomponent + fairs = [] + for ii,con in enumerate(end_subcons): + fairs.append(platform.attachments[fair_ID[ii]]['obj']) + end_subcons[ii].join(fairs[-1]) + + return(fairs) + +def calc_heading(pointA, pointB): + '''calculate a compass heading from points, if pointA or pointB is a list of points, + the average of those points will be used for that end''' + # calculate the midpoint of the point(s) on each end first + pointAmid = calc_midpoint(pointA) + pointBmid = calc_midpoint(pointB) + dists = np.array(pointAmid) - np.array(pointBmid) + headingB = np.pi/2 - np.arctan2(dists[1], dists[0]) + + return(headingB) + +def calc_midpoint(point): + '''Calculates the midpoint of a list of points''' + if isinstance(point[0],list) or isinstance(point[0],np.ndarray): + pointx = sum([x[0] for x in point])/len(point) + pointy = sum([x[1] for x in point])/len(point) + # add z component if needed + if len(point[0])==3: + pointz = sum([x[2] for x in point])/len(point) + return([pointx,pointy,pointz]) + else: + pointx = point[0] + pointy = point[1] + # add z component if needed + if len(point)==3: + pointz = point[2] + return([pointx,pointy,pointz]) + + return([pointx,pointy]) + +def route_around_anchors(proj, anchor=True, cable=True, padding=50): + '''check if static cables hit anchor buffer, if so reroute cables around anchors''' # make anchor buffers with 50m radius if anchor: anchor_buffs = [] @@ -873,15 +1151,16 @@ def angle(pt): for anch in anchor_buffs: if cab.intersects(anch): # Get the start and end of the detour (the two closest points to the buffer) - segments = [] + new_points = [] # make additional points on the line on either side of anchor dist_to_anch = cab.line_locate_point(anch.centroid) if dist_to_anch > 100: - segments.append(cab.interpolate(dist_to_anch - 100)) + new_points.append(cab.interpolate(dist_to_anch - 100)) if cab.length - dist_to_anch > 100: - segments.append(cab.interpolate(dist_to_anch + 100)) + new_points.append(cab.interpolate(dist_to_anch + 100)) - start = np.array(segments[0].coords[-1]) + # pull out the coordinates of the first new point + start = np.array(new_points[0].coords[-1]) # Get buffer center and radius center = np.array(anch.centroid.coords[0]) @@ -893,8 +1172,20 @@ def angle(pt): # Generate point along the arc (detour) arc_point = [center[0] + radius * np.cos(angle_start+np.pi/2), center[1] + radius * np.sin(angle_start+np.pi/2)] + # determine relative positions of new routing points among other routing points + rel_dist = [] + orig_coords = [] + for i,x in enumerate(proj.cableList[name].subcomponents[2].x): + y = proj.cableList[name].subcomponents[2].y[i] + rel_dist.append(cab.line_locate_point(sh.Point([x,y]))) + orig_coords.append([x,y]) + all_dists = np.hstack((rel_dist,dist_to_anch-100, dist_to_anch+100, dist_to_anch)) + all_points = np.vstack((orig_coords,[coord.coords[0] for coord in new_points],arc_point)) + sorted_idxs = np.argsort(all_dists) + final_points = all_points[sorted_idxs] + # add new routing point in cable object - proj.cableList[name].subcomponents[2].updateRouting([list(segments[0].coords[1:]) + arc_point + list(segments[1].coords[:-1])]) + proj.cableList[name].subcomponents[2].updateRouting(final_points) @@ -1057,7 +1348,8 @@ def eval_func(X, args): Xmin=[1], Xmax=[1.1*np.linalg.norm(ss.rB-ss.rA)], dX_last=[1], tol=[0.01], maxIter=50, stepfac=4) ss.lineList[i_line].L = L_final[0] - mooring.dd['sections'][i_line]['L'] = L_final[0] + sec = mooring.getSubcomponent(i_line) + sec['L'] = L_final[0] mooring.dd['span'] = span mooring.span = span @@ -1083,6 +1375,8 @@ def func_TH_L(X, args): args=dict(direction='horizontal'), Xmin=[10], Xmax=[2000], dX_last=[10], maxIter=50, stepfac=4) + # update design dictionary L + mooring.setSectionLength(ss.lineList[i_line].L,i_line) else: print('Invalid method. Must be either pretension or horizontal') @@ -1178,6 +1472,28 @@ def gothroughlist(dat): # return cleaned dictionary return(info) + +def createRAFTDict(project): + from famodel.turbine.turbine import Turbine + # Create a RAFT dictionary from a project class to create RAFT model + rd = {'array':{'keys':['ID', 'turbineID', 'platformID', 'mooringID', 'x_location', 'y_location', 'heading_adjust'], + 'data':[]}} + turb = 0 + for pf in project.platformList.values(): + for att in pf.attachments.values(): + if isinstance(att['obj'],Turbine): + turb = att['obj'].dd['type'] + break + rd['array']['data'].append([pf.id, turb, pf.dd['type'], 0, pf.r[0], pf.r[1],np.degrees(pf.phi)]) + rd['site'] = {'water_depth':project.depth,'rho_water':project.rho_water,'rho_air':project.rho_air,'mu_air':project.mu_air} + rd['site']['shearExp'] = .12 + + rd['turbines'] = project.turbineTypes + rd['platforms'] = project.platformTypes + + return rd + + def getFromDict(dict, key, shape=0, dtype=float, default=None, index=None): ''' Function to streamline getting values from design dictionary from YAML file, including error checking. diff --git a/famodel/images/fairleads_and_jtubes.png b/famodel/images/fairleads_and_jtubes.png new file mode 100644 index 00000000..a9289fbf Binary files /dev/null and b/famodel/images/fairleads_and_jtubes.png differ diff --git a/famodel/images/parallel_sections.png b/famodel/images/parallel_sections.png new file mode 100644 index 00000000..332ddaa1 Binary files /dev/null and b/famodel/images/parallel_sections.png differ diff --git a/famodel/mooring/README.md b/famodel/mooring/README.md index 1e0b38fa..6e06022a 100644 --- a/famodel/mooring/README.md +++ b/famodel/mooring/README.md @@ -22,18 +22,23 @@ includes a design dictionary with the following details: - rad_fair : Fairlead radius - z_fair : Fairlead depth - span : 2D distance from fairlead to anchor (or fairlead to fairlead) -- Sections: List of line segment detail dictionaries, becomes list of section objects - - type : Line section type dictionary - - d_nom, d_vol : diameter (nominal and volume-equivalent) [m] - - material - - cost [USD] - - m : linear mass [g/m] - - w : weight [N/m] - - MBL : minimum breaking load [N] - - EA : stiffness coefficient [N] - - L : Line section length [m] +- Subcomponents: List of line sections and connectors in order from end A to end B + The values in each subcomponent type vary depending on if it is a section or connector. For sections: + - type : material property dictionary + - d_nom, d_vol : diameter (nominal and volume-equivalent) [m] + - material + - cost [USD] + - m : linear mass [g/m] + - w : weight [N/m] + - MBL : minimum breaking load [N] + - EA : stiffness coefficient [N] + - L : Line section length [m] + For connectors: + - m : mass [kg] + - v : volume [kg/m^3] + - CdA -The Mooring object contains subcomponent objects that represent each component of the full mooring line. Line segments are Section objects, while connectors between segments and at the ends of the lines are Connector objects. These segments alternate. +The Mooring object contains subcomponent objects that represent each component of the full mooring line. Line segments are Section objects, while connectors between segments and at the ends of the lines are Connector objects. These segments alternate, and are listed in the subcomponents section of the design dictionary in order from end A to end B. If there are parallel sections, such as in the case of a bridle, the parallel sections are described with nested lists. ## Mooring Properties - dd @@ -55,13 +60,15 @@ The Mooring object contains subcomponent objects that represent each component o - rA : end A absolute coordinates - rB : end B absolute coordinates - heading : compass heading from B to A +- ss : MoorPy subsystem representation of this Mooring, pristine +- ss_mod : modified MoorPy subsystem of thie Mooring, could have marine growth etc +- span : 2D (x-y) distance from fairlead to anchor or fairlead to fairlead. If bridles, the distance is calculated from the midpoint of all bridle fairlead points - adjuster : custom function that can adjust mooring - shared : int for anchored line (0), shared line (1) or half of a shared line (2) - symmetric : boolean for if the mooring line is symmetric shared line - rho : water density - g : acceleration due to gravity - envelopes : 2D motion envelopes, buffers, etc. -- loads : dictionary of loads on the mooring line - reliability : dictionary of reliability information on the line - cost : dictionary of line costs - failure_probability : dictionary of failure probabilities @@ -88,7 +95,7 @@ Set the position of an end of the mooring Finds the cost based on the MoorPy subsystem cost estimates ### updateTensions -Gets tensions from subsystem and updates the max tensions dictionary if it is larger than a previous tension +Gets tensions from subsystem and updates the max tensions dictionaries of each Section object if it is larger than a previous tension ### createSubsystem @@ -123,7 +130,7 @@ the following details: - volume - CdA -The connector class also contains an xyz location of the connector, and a connector object in MoorPy. +The connector class also contains an xyz location of the connector, and a connector object in MoorPy (mpConn). ## Connector methods @@ -135,7 +142,11 @@ Create a MoorPy connector object in a MoorPy system. Mass, volume, and CdA are a The Section class provides a data structure for the mooring line section material and length. The Section class inherits from dict and Edge. -The line material properties (linear mass, material, MBL, Cd, etc) are stored in the type dictionary of the Section class. +The line material properties (linear mass, material, MBL, Cd, etc) are stored in the type dictionary of the Section class. If a moorpy system is developed, the the line object representing this section is listed in the mpLine parameter. Loads are stored in the loads dictionary, and safety factors are stored in the safety_factors dictionary property. + +### Section methods +- makeMoorPyLine +Create a moorpy line object in a moorpy system [Back to Top](#moorings-sections-and-connectors) diff --git a/famodel/mooring/connector.py b/famodel/mooring/connector.py index 72620e98..5bf5eec6 100644 --- a/famodel/mooring/connector.py +++ b/famodel/mooring/connector.py @@ -38,14 +38,19 @@ def __init__(self,id, r=[0,0,0], **kwargs): # MoorPy Point Object for Connector self.mpConn = None + # dictionary of loads + self.loads = {} + # dictionary of failure probabilities self.failure_probability = {} # cost dictionary self.cost = {} - self.getProps() + self.getProps() + + def makeMoorPyConnector(self, ms): '''Create a MoorPy connector object in a MoorPy system Parameters @@ -57,10 +62,15 @@ def makeMoorPyConnector(self, ms): ------- ms : class instance MoorPy system - ''' + + if self.isJoined(): # if the connector is joined to something + pointType = 1 # make it a fixed point + else: + pointType = 0 # otherwise a free point + # create connector as a point in MoorPy system - ms.addPoint(0,self.r) + ms.addPoint(pointType, self.r) # assign this point as mpConn in the anchor class instance self.mpConn = ms.pointList[-1] @@ -71,11 +81,10 @@ def makeMoorPyConnector(self, ms): # set point type in ms self.getProps() - - return(ms) + def getProps(self): ''' Wrapper function to get moorpy point props dictionary @@ -94,8 +103,11 @@ def getProps(self): if self['CdA']>0: details['CdA'] = self['CdA'] if self.mpConn: + # add point type to system and assign to point entity if mp connector point exists pt = self.mpConn.sys.setPointType(design,**details) + self.mpConn.entity = pt else: + # otherwise, jsut get the point properties dict and return props = loadPointProps(None) pt = getPointProps(design, Props=props, **details) self.required_safety_factor = pt['FOS'] @@ -104,17 +116,26 @@ def getProps(self): return(pt) + def getCost(self,update=True, fx=0.0, fz=0.0, peak_tension=None, MBL=None): '''Get cost of the connector from MoorPy pointProps. Wrapper for moorpy's getCost_and_MBL helper function''' if update: - if self.mpConn: - # use pointProps to update cost - try: - self.getProps() - self.cost['materials'], MBL, info = self.mpConn.getCost_and_MBL(fx=fx, fz=fz, peak_tension=peak_tension) - except: - print('Warning: unable to find cost from MoorPy pointProps, cost dictionary not updated') + # use pointProps to update cost + try: + # get point properties from wrapper function + ptype = self.getProps() + if self.mpConn: + point = self.mpConn + else: + from moorpy import System + ms = System() # create a blank moorpy system + point = ms.addPoint(0, r=[0,0]) # add a dummy point + point.entity = ptype # get the point properties and assign to point entity + # calculate cost with any given forces/tensions + self.cost['materials'], MBL, info = point.getCost_and_MBL(fx=fx, fz=fz, peak_tension=peak_tension) + except: + print('Warning: unable to find cost from MoorPy pointProps, cost dictionary not updated') # if update == False, just return existing costs return sum(self.cost.values()) @@ -139,4 +160,40 @@ def __init__(self,id, **kwargs): # if the type dict wasn't provided, set as none to start with if not 'type' in self: - self['type'] = None \ No newline at end of file + self['type'] = None + + # MoorPy Line object for the section + self.mpLine = None + + # dictionary of loads on section + self.loads = {} + + # dictionary of safety factors + self.safety_factors = {} + + + def makeMoorPyLine(self, ms): + '''Create a MoorPy Line object in a MoorPy system. + If this section is attached to connectors that already have associated + MoorPy point objects, then those attachments will also be made in + MoorPy. + + Parameters + ---------- + ms : MoorPy System object + The MoorPy system to create the Line object in. + ''' + + # See if this section is attached to any already-created MoorPy Points + pointA = 0 + if self.attached_to[0]: # if an end A attachment + if self.attached_to[0].mpConn: # if it has a MoorPy point object + pointA = self.attached_to[0].mpConn.number # get its number + pointB = 0 + if self.attached_to[1]: + if self.attached_to[1].mpConn: + pointB = self.attached_to[1].mpConn.number + + # Create a Line for the section in MoorPy system + self.mpLine = ms.addLine(self['L'], self['type'], pointA=pointA, pointB=pointB) + diff --git a/famodel/mooring/mooring.py b/famodel/mooring/mooring.py index 7692d9bc..e90860f6 100644 --- a/famodel/mooring/mooring.py +++ b/famodel/mooring/mooring.py @@ -5,7 +5,8 @@ from moorpy.subsystem import Subsystem from moorpy import helpers from famodel.mooring.connector import Connector, Section -from famodel.famodel_base import Edge +from famodel.famodel_base import Edge, Node +from famodel.helpers import calc_midpoint class Mooring(Edge): ''' @@ -23,15 +24,19 @@ def __init__(self, dd=None, subsystem=None, anchor=None, dd: dictionary Design dictionary that contains all information on a mooring line needed to create a MoorPy subsystem Layout: { - sections: + subcomponents: # always starts and ends with connectors even if connector dict is blank { - 0 - { + 0 + { # connector + type: {m, v, CdA} + } + 1 + { # section type: { name, d_nom, material, d_vol, m, EA, EAd, EAd_Lm, MBL, cost, weight } - L + L # length in [m] } } connectors: @@ -42,17 +47,7 @@ def __init__(self, dd=None, subsystem=None, anchor=None, zAnchor z_fair rad_fair - EndPositions: - { - endA, endB - } } - Initialize an empty object for a mooring line. - Eventually this will fully set one up from ontology inputs. - - >>> This init method is a placeholder that currently may need - some additional manual setup of the mooring object after it is - called. <<< ''' Edge.__init__(self, id) # initialize Edge base class @@ -74,35 +69,80 @@ def __init__(self, dd=None, subsystem=None, anchor=None, if not 'z_fair' in self.dd: self.dd['z_fair'] = self.ss.z_fair - self.dd['sections'] = [] - self.dd['connectors'] = [] - for ls in self.ss.lineList: - self.dd['sections'].append({'type':ls.type,'L':ls.L}) + self.dd['subcomponents'] = [] + # find the starting point index for lp in self.ss.pointList: - self.dd['connectors'].append({'CdA':lp.CdA, 'm':lp.m, 'v':lp.v}) + if not any(lp.attachedEndB): + # this is the starting point at end A - add this point first + self.dd['subcomponents'].append({'CdA':lp.CdA, 'm':lp.m, 'v':lp.v}) + break + # now iterate through and add the line and point B + for s in range(len(self.ss.lineList)): + # find what entry in the attached list is for a line attached at end A + endA = [lp.attachedEndB[i] for i in lp.attachedEndB if i==0][0] + # save the line number attached to the point at its end A + line_num = lp.attached[endA] + # pull out the line section and save it to subcomponents + ls = self.ss.lineList[line_num-1] + self.dd['subcomponents'].append({'type':ls.type, 'L':ls.L}) + # go through the point list again and pull out the point attached to end B of the line + for lb in self.ss.pointList: + if line_num in lb.attached and lb != lp: + lp = lb + # save end B point to subcomponents + self.dd['subcomponents'].append({'CdA':lp.CdA, 'm':lp.m, 'v':lp.v}) + + + self.parallels = False # True if there are any parallel sections in the mooring # let's turn the dd into something that holds subdict objects of connectors and sections if self.dd: # >>> list of sections ? And optional of section types (e,g, chian, poly) # and dict of scaling props (would be used by linedesign) ? - self.n_sec = len(self.dd['sections']) - + # self.n_sec = len(self.dd['sections']) + self.i_con = [] self.i_sec = [] - # Turn what's in dd and turn it into Sections and Connectors - for i, con in enumerate(self.dd['connectors']): - if con and 'type' in con: - Cid = con['type']+str(i) - else: - Cid = None - self.addConnector(con, i, id=Cid, insert=False) + + # # Turn what's in dd and turn it into Sections and Connectors + # con_i = 0 + # for i, con in enumerate(self.dd['connectors']): + # if isinstance(self.dd['connectors'][i],list): + # for j,subcon in enumerate(self.dd['connectors'][i]): + # if isinstance(self.dd['connectors'][i][j],list): + # if con and 'type' in con: + # Cid = con['type']+str(con_i) + # else: + # Cid = None + # self.addConnector(con, con_i, id=Cid, insert=False) + # con_i += 1 + # else: + # if con and 'type' in con: + # Cid = con['type']+str(con_i) + # else: + # Cid = None + # self.addConnector(con, con_i, id=Cid, insert=False) + # con_i += 1 + # sub_i = 0 + # for i, sec in enumerate(self.dd['sections']): + # if isinstance(self.dd['sections'][i],list): + # for j,subcon in enumerate(self.dd['sections'][i]): + # if isinstance(self.dd['sections'][i][j],list): + # self.addSection(sec['L'],sec['type'],sub_i, insert=False) + # sub_i += 1 + # else: + # self.addSection(sec['L'],sec['type'],sub_i, insert=False) + # sub_i += 1 + + # convert subcomponents list into actual objects + self.convertSubcomponents(self.dd['subcomponents']) - for i, sec in enumerate(self.dd['sections']): - self.addSection(sec['L'],sec['type'],i, insert=False) + # connect subcomponents + self.addSubcomponents(self.dd['subcomponents']) - # connect subcomponents and update i_sec and i_conn lists - self.connectSubcomponents() + # point dd['subcomponents'] list to self.subcomponents + self.dd['subcomponents'] = self.subcomponents @@ -148,9 +188,13 @@ def update(self, dd=None): if not dd == None: # if dd passed in self.dd.update(dd) # move contents of dd into Mooring.dd - + self.convertSubcomponents(dd['subcomponents']) + self.addSubcomponents(self.dd['subcomponents']) + # Update section lengths and types - for i, sec in enumerate(dd['sections']): + for i in range(len(self.i_sec)): + sec = self.getSubcomponent(self.i_sec[i]) + if self.ss: self.ss.lineList[i].setL(sec[i]['L']) self.ss.lineTypes[i] = sec[i]['type'] @@ -161,8 +205,8 @@ def update(self, dd=None): def setSectionLength(self, L, i): '''Sets length of section, including in the subdsystem if there is one.''' - - self.dd['sections'][i]['L'] = L # set length in dd (which is also Section/subcomponent) + sec = self.getSubcomponent(self.i_sec[i]) + sec['L'] = L # set length in dd (which is also Section/subcomponent) if self.ss: # is Subsystem exists, adjust length there too self.ss.lineList[i].setL(L) @@ -179,9 +223,9 @@ def setSectionDiameter(self, d, i): def setSectionType(self, lineType, i): '''Sets lineType of section, including in the subdsystem if there is one.''' - + sec = self.getSubcomponent(self.i_sec[i]) # set type dict in dd (which is also Section/subcomponent) - self.dd['sections'][i]['type'] = lineType + sec['type'] = lineType if self.ss: # is Subsystem exists, adjust length there too self.ss.lineTypes[i] = lineType @@ -239,18 +283,22 @@ def reposition(self, r_center=None, heading=None, project=None, r_centerA = self.attached_to[0].r r_centerB = self.attached_to[1].r - # create fairlead radius list for end A and end B if needed - if not rad_fair: - rad_fair = [self.attached_to[x].rFair if (hasattr(self.attached_to[x],'rFair') and self.attached_to[x].rFair) else 0 for x in range(2)] - # create fairlead depth list for end A and end B if needed - if not z_fair: - z_fair = [self.attached_to[x].zFair if (hasattr(self.attached_to[x],'zFair') and self.attached_to[x].zFair) else 0 for x in range(2)] + # check if there are fairlead objects attached to end connectors + fairs = True if len(self.subcons_B[0].attachments)>1 else False + # if there is no fairlead object, use traditional method to determine new fairlead location and set it, otherwise end B should be set already + if not fairs: + # create fairlead radius list for end A and end B if needed + if not rad_fair: + rad_fair = [self.attached_to[x].rFair if (hasattr(self.attached_to[x],'rFair') and self.attached_to[x].rFair) else 0 for x in range(2)] + # create fairlead depth list for end A and end B if needed + if not z_fair: + z_fair = [self.attached_to[x].zFair if (hasattr(self.attached_to[x],'zFair') and self.attached_to[x].zFair) else 0 for x in range(2)] + + # Set the updated end B location + self.setEndPosition(np.hstack([r_centerB[:2] + rad_fair[1]*u, z_fair[1] + r_centerB[2]]), 'b') - # Set the updated end B location - self.setEndPosition(np.hstack([r_centerB[:2] + rad_fair[1]*u, z_fair[1] + r_centerB[2]]), 'b') # Run custom function to update the mooring design (and anchor position) - # this would also szie the anchor maybe? if self.adjuster and adjust: #if i_line is not defined, assumed segment 0 will be adjusted @@ -264,27 +312,31 @@ def reposition(self, r_center=None, heading=None, project=None, else: #move anchor based on set spacing then adjust line length - xy_loc = r_centerB[:2] + (self.span + rad_fair[1])*u + xy_loc = self.rB[:2] + self.span*u #r_centerB[:2] + (self.span + rad_fair[1])*u if project: self.dd['zAnchor'] = -project.getDepthAtLocation(xy_loc[0],xy_loc[1]) self.z_anch = self.dd['zAnchor'] else: print('Warning: depth of mooring line, anchor, and subsystem must be updated manually.') - self.setEndPosition(np.hstack([r_centerB[:2] + (self.span + rad_fair[1])*u, self.z_anch]), 'a', sink=True) + self.setEndPosition(np.hstack([self.rB[:2] + self.span*u, self.z_anch]), 'a', sink=True) self.adjuster(self, method = 'horizontal', r=r_centerB, project=project, target = self.target, i_line = self.i_line) - elif self.shared == 1: # set position of end A at platform end A - self.setEndPosition(np.hstack([r_centerA[:2] - rad_fair[0]*u, z_fair[0] + r_centerA[2]]),'a') + elif self.shared == 1: # set position of end A at platform end A if no fairlead objects + if not len(self.subcons_A[0].attachments) > 1: + self.setEndPosition(np.hstack([r_centerA[:2] - rad_fair[0]*u, z_fair[0] + r_centerA[2]]),'a') else: # otherwise just set the anchor position based on a set spacing (NEED TO UPDATE THE ANCHOR DEPTH AFTER!) - xy_loc = r_centerB[:2] + (self.span + rad_fair[1])*u + if not fairs: + xy_loc = self.rB[:2] + self.span*u #r_centerB[:2] + (self.span + rad_fair[1])*u + else: + xy_loc = calc_midpoint([sub.r[:2] for sub in self.subcons_B]) + self.span*u if project: self.dd['zAnchor'] = -project.getDepthAtLocation(xy_loc[0],xy_loc[1]) self.z_anch = self.dd['zAnchor'] else: print('Warning: depth of mooring line, anchor, and subsystem must be updated manually.') - self.setEndPosition(np.hstack([r_centerB[:2] + (self.span + rad_fair[1])*u, self.z_anch]), 'a', sink=True) + self.setEndPosition(np.hstack([xy_loc, self.z_anch]), 'a', sink=True) # Update the mooring profile given the repositioned ends if self.ss: @@ -300,6 +352,9 @@ def reposition(self, r_center=None, heading=None, project=None, att.r = iend if att.mpAnchor: att.mpAnchor.r = att.r + + # reposition the subcomponents + self.positionSubcomponents() @@ -391,24 +446,66 @@ def getCost(self,from_ss=True): # sum up the costs in the dictionary and return return sum(self.cost.values()) - def updateTensions(self): + def updateTensions(self, DAF=1): ''' Gets tensions from subsystem and updates the max tensions dictionary if it is larger than a previous tension ''' - if not 'TAmax' in self.loads: - self.loads['TAmax'] = 0 - if not 'TBmax' in self.loads: - self.loads['TBmax'] = 0 - # get anchor tensions - if abs(self.ss.TA) > self.loads['TAmax']: - self.loads['TAmax'] = deepcopy(self.ss.TA) - # get TB tensions - if abs(self.ss.TB) > self.loads['TBmax']: - self.loads['TBmax'] = deepcopy(self.ss.TB) + Ts = [] + Tc = [] + # get tensions for each section + for sec in self.sections(): + if not 'Tmax' in sec.loads: + sec.loads['Tmax'] = 0 + Tmax = max([abs(sec.mpLine.TA), abs(sec.mpLine.TB)]) + if Tmax*DAF > sec.loads['Tmax']: + sec.loads['Tmax'] = deepcopy(Tmax)*DAF + Ts.append(sec.loads['Tmax']) + for conn in self.connectors(): + if not 'Tmax' in conn.loads: + conn.loads['Tmax'] = 0 + Tmax = np.linalg.norm(conn.mpConn.getForces()) + if Tmax*DAF > conn.loads['Tmax']: + conn.loads['Tmax'] = deepcopy(Tmax)*DAF + Tc.append(conn.loads['Tmax']) - return(self.loads['TAmax'],self.loads['TBmax']) + return max(Ts) + def updateSafetyFactors(self,key='tension',load='Tmax', prop='MBL', + sections=True, connectors=True, info={}): + """Update safety factors for desired factor type, load type, and property + + Parameters + --------- + key: str/int, optional + safety_factor dictionary key. Default is 'tension'. + load: str, optional + key in loads dictionary. Default is 'Tmax' + prop: str, optional + key in line type dictionary. Default is 'MBL' + info: str, optional + information string to add to safety_factors dictionary + + Returns + ------- + Minimum safety factor for the given key across all sections in the mooring line + """ + + # get safety factors for each section + if sections: + for sec in self.sections(): + if prop in sec['type']: + sec.safety_factors[key] = sec['type'][prop]/sec.loads[load] + sec.safety_factors['info'] = info + if connectors: + for con in self.connectors(): + if 'type' in con and prop in con['type']: + con.safety_factors[key] = con['type'][prop]/con.loads[load] + sec.safety_factors['info'] = info + + + - def createSubsystem(self, case=0,pristine=True,dd=None, mooringSys=None): + + def createSubsystem(self, case=0, pristine=True, dd=None, ms=None): ''' Create a subsystem for a line configuration from the design dictionary Parameters @@ -422,67 +519,194 @@ def createSubsystem(self, case=0,pristine=True,dd=None, mooringSys=None): - 2: the assembly is suspended and assumed symmetric, end A is the midpoint dd : dict, optional Dictionary describing the design - mooringSys : MoorPy System, optional - MoorPy system this subsystem is a part of + ms : MoorPy System, optional + MoorPy system this subsystem is a part of. Necessary if ''' + # set design dictionary as self.dd if none given, same with connectorList if not dd: dd = self.dd - - ss=Subsystem(mooringSys=mooringSys, depth=-dd['zAnchor'], rho=self.rho, g=self.g, + + # get list of sections and connectors, send in dd in case it is not from self.dd + secs = self.sections(dd) + conns = self.connectors(dd) + + if self.parallels: # make parts of a MoorPy system + + if not ms: + raise Exception('A MoorPy system (ms) must be provided for a Mooring with parallel/bridle parts.') + # Make Points + for con in conns: + # >>> leah had some checks here that I didn't understand <<< + con.makeMoorPyConnector(ms) + + # Make Lines + for sec in secs: + sec.makeMoorPyLine(ms) # this also will connect the Lines to Points + + else: + ss=Subsystem(mooringSys=ms, depth=-dd['zAnchor'], rho=self.rho, g=self.g, span=dd['span'], rad_fair=self.rad_fair, z_fair=self.z_fair)#, bathymetry=dict(x=project.grid_x, y=project.grid_y, depth=project.grid_depth)) # don't necessarily need to import anymore - #ss.setSSBathymetry(project.grid_x, project.grid_y, project.grid_depth) - - - - - - lengths = [] - types = [] - # run through each line section and collect the length and type - for i, sec in enumerate(dd['sections']): - lengths.append(sec['L']) - # points to existing type dict in self.dd for now - types.append(sec['type']) # list of type names - #types.append(sec['type']['name']) # list of type names - #self.ss.lineTypes[i] = sec['type'] + + lengths = [] + types = [] + # run through each line section and collect the length and type + for sec in secs: + lengths.append(sec['L']) + types.append(sec['type']) # list of type names + + + # make the lines and set the points + ss.makeGeneric(lengths, types, + connectors=[conns[ic+1] for ic in range(len(conns)-2)], + suspended=case) + ss.setEndPosition(self.rA,endB=0) + ss.setEndPosition(self.rB,endB=1) + + for i,sec in enumerate(self.sections(dd)): + sec.mpLine = ss.lineList[i] + for i,con in enumerate(self.connectors(dd)): + con.mpConn = ss.pointList[i] + + # add in connector info to subsystem points + if case == 0: # has an anchor - need to ignore connection for first point because anchor is a point itself so can't have a point attached to a point + startNum = 1 + else: # no anchor - need to include all connections + startNum = 0 + for i in range(startNum,len(ss.pointList)): + conn = conns[i] + conn.mpConn = ss.pointList[i] + conn.mpConn.CdA = conns[i]['CdA'] + conn.getProps() + + # solve the system + ss.initialize() + ss.staticSolve() + + + # add to the parent mooring system if applicable + if ms: + ms.lineList.append(ss) + ss.number = len(ms.lineList) + + # save ss to the correct Mooring variable + if pristine: + # save to ss + self.ss = ss + return(self.ss) + else: + # save to modified ss (may have marine growth, corrosion, etc) + self.ss_mod = ss + return(self.ss_mod) + + + def positionSubcomponents(self): + '''Puts any subcomponent connectors/nodes along the mooring in + approximate positions relative to the endpoints based on the + section lengths.''' - # make the lines and set the points - ss.makeGeneric(lengths, types, - connectors=[dd['connectors'][ic+1] for ic in range(len(dd['connectors'])-2)], - suspended=case) - ss.setEndPosition(self.rA,endB=0) - ss.setEndPosition(self.rB,endB=1) - # note: next bit has similar code/function as Connector.makeMoorPyConnector <<< + # Tabulate the section lengths + L = [] + n_serial_nodes = 0 # number of serial nodes, including first and last + + # ----- First pass, going through each section in series ----- + + # Figure out lengths + for item in self.subcomponents: + + if isinstance(item, list): # indicates there are parallel sections here + pLtot = [] # total length of each parallel string + for j, parallel in enumerate(item): # go through each parallel string + if isinstance(parallel, list): # if it's a concatenation of multiple things + pLtot.append(0) + # go through each item along the parallel path + for subitem in parallel: + if isinstance(subitem, Edge): + pLtot[j] += subitem['L'] # add the L of each edge + else: + raise Exception("Unsupported situation ... parallel subitems must be lists") + + L.append(min(pLtot)) # save minimum parallel string length + + elif isinstance(item, Node): + n_serial_nodes += 1 + + elif isinstance(item, Edge): + L.append(item['L']) # save length of section - # add in connector info to subsystem points - if case == 0: # has an anchor - need to ignore connection for first point because anchor is a point itself so can't have a point attached to a point - startNum = 1 - else: # no anchor - need to include all connections - startNum = 0 + + # Position nodes along main serial string between rA and rB + Lsum = np.cumsum(np.array(L)) + j = 0 # index of node along serial string (at A is 0) - for i in range(startNum,len(ss.pointList)): - dd['connectors'][i].mpConn = ss.pointList[i] - dd['connectors'][i].mpConn.CdA = dd['connectors'][i]['CdA'] - dd['connectors'][i].getProps() + for i, item in enumerate(self.subcomponents): + if isinstance(item, list) or isinstance(item, Edge): + j = j+1 # note that we're moving a certain length along the string - # solve the system - ss.initialize() - ss.staticSolve() - - # save ss to the correct Mooring variable - if pristine: - # save to ss - self.ss = ss - return(self.ss) - else: - # save to modified ss (may have marine growth, corrosion, etc) - self.ss_mod = ss - return(self.ss_mod) + # if it's a node, but not the first or last one + elif isinstance(item, Node) and i > 0 and i < len(self.subcomponents)-1: + r = self.rA + (self.rB-self.rA)*Lsum[j-1]/Lsum[-1] + item.setPosition(r) + + + # ----- Second pass, to position any nodes that are along parallel sections ----- + for i, item in enumerate(self.subcomponents): + + if isinstance(item, list): # indicates there are parallel sections here + for j, parallel in enumerate(item): # go through each parallel string + + # --- go through each item along the parallel path --- + # Note: this part repeats some logic, could be replaced with recursive fn + L = [] + n_serial_nodes = 0 + + for subitem in parallel: + if isinstance(item, Node): + n_serial_nodes += 1 + + elif isinstance(subitem, Edge): + L.append(subitem['L']) # save length of section + + # --- Figure out the end points of this paralle string --- + + # if this parallel is on the first section, then it's a bridle at A + if i == 0: + if isinstance(parallel[0], Edge): # if first object is an Edge + rA = parallel[0].rA + else: + rA = parallel[0].r + else: + rA = self.subcomponents[i-1].r + + # if this parallel is on the last section, then it's a bridle at B + if i == len(self.subcomponents)-1: + if isinstance(parallel[-1], Edge): # if last object is an Edge + rB = parallel[-1].rB + else: + rB = parallel[-1].r + else: + rB = self.subcomponents[i+1].r + + # --- Do the positioning --- + Lsum = np.cumsum(np.array(L)) + + for subitem in parallel: + if isinstance(subitem, Edge): + j = j+1 # note that we're moving a certain length along the string + + # if it's a node, but not the first or last one + elif isinstance(item, Node): + if j > 0 and j < n_serial_nodes-1: + r = rA + (rB-rA)*Lsum[j]/Lsum[-1] + item.setPosition(r) + else: + print('end of parallel') + breakpoint() + def mirror(self,create_subsystem=True): ''' Mirrors a half design dictionary. Useful for symmetrical shared mooring lines where only half of the line is provided @@ -494,9 +718,11 @@ def mirror(self,create_subsystem=True): Default is True ''' # disconnect all sections and connectors - for i, sec in enumerate(self.dd['sections']): - self.dd['sections'][i].detachFrom(end='a') - self.dd['sections'][i].detachFrom(end='b') + # TODO: update to work with dd['subcomponents'] + for i in self.i_sec: + sec = self.getSubcomponent(i) + sec.detachFrom(end='a') + sec.detachFrom(end='b') # find out if the connector at end A (center of line) is empty if not self.dd['connectors'][0] or self.dd['connectors'][0]['m']==0: # do not double the middle section length @@ -626,7 +852,8 @@ def addMarineGrowth(self, mgDict, project=None, idx=None): # oldLine = project.mooringListPristine[idx] # else: # use current mooring object # oldLine = self - + if self.parallels: + raise Exception('addMarineGrowth not set up to work with parallels at this time') # create a reference subsystem if it doesn't already exist if not self.ss: self.createSubsystem() @@ -643,7 +870,7 @@ def addMarineGrowth(self, mgDict, project=None, idx=None): changeDepths = [] # index of list that has the corresponding changeDepth # set first connector - connList.append(self.dd['connectors'][0]) + connList.append(self.connectors()[0]) # go through each line section for i in range(0,len(oldLine.lineList)): slthick = [] # mg thicknesses for the section (if rA is above rB, needs to be flipped before being added to full subsystem list LThick) @@ -749,7 +976,7 @@ def addMarineGrowth(self, mgDict, project=None, idx=None): Lengths.append(ssLine.L) # add connector at end of section to list - connList.append(self.dd['connectors'][i+1]) + connList.append(self.connectors()[i+1]) # Set up list variables for pristine line info EA = [] @@ -871,8 +1098,11 @@ def addMarineGrowth(self, mgDict, project=None, idx=None): # fill out rest of new design dictionary nd1 = deepcopy(self.dd) - nd1['sections'] = nd - nd1['connectors'] = connList + nd1['subcomponents'] = [None]*(len(nd)*2+1) + for i in range(len(nd)): + nd1['subcomponents'][2*i] = Connector('C'+str(i),**connList[i]) + nd1['subcomponents'][2*i+1] = Section('S'+str(i),**nd[i]) + nd1['subcomponents'][2*i+2] = Connector('C'+str(i),**connList[i+1]) # call createSubsystem() to make moorpy subsystem with marine growth if self.shared: @@ -897,7 +1127,8 @@ def addCorrosion(self,corrosion_mm=10): None. ''' - for i,sec in enumerate(self.dd['sections']): + for i in self.i_sec: + sec = self.getSubcomponent[i] if sec['type']['material']=='chain': MBL_cor = sec['type']['MBL']*( (sec['type']['d_nom']-(corrosion_mm/1000))/sec['type']['d_nom'] )**2 # corroded MBL else: @@ -971,27 +1202,53 @@ def addSection(self, section_length, section_type, index, id=None, insert=True): Length of new section in [m] section_type : dict Dictionary of section properties - index : int + index : list New index of section in the mooring design dictionary sections list + List of length 1 or 3 depending on if part of a subsection or not id : str/int, optional Id of section ''' if not id: if insert: - for i,sec in enumerate(self.dd['sections']): - # update ids of sections - if i>=index and isinstance(sec, Section): - sec.id = 'Section'+str(i+1) - id='Section'+str(index) + for i in self.i_sec: + # update ids of subcomponents after this in series + # first check if the i_sec index i is the same level as index to add in + # and the final entry in i is greater than the index to add in + if len(i)==len(index) and i[-1]>index[-1]: + # check if all indices within the index list are less + # than or equal to the i_con index, i, list + if np.all([i[j]>=index[j] for j in range(len(i))]) and i[-1]>index[-1]: + sec = self.getSubcomponent(i) + sec.id = '_'.join(['S',*[str(j) for j in i]]) + # make the id start with S and add each component of the index separated by _ + id='_'.join(['S',*[str(j) for j in index]]) newsection_dd = {'type':section_type,'L':section_length} - newsection = Section(id,**newsection_dd) + # create section object + newsec = Section(id,**newsection_dd) if insert: - self.dd['sections'].insert(index, newsection) + if len(index)==1: + self.dd['subcomponents'].insert(index[0], + newsec) + elif len(index)==2: + self.dd['subcomponents'][index[0]][index[1]].insert(0, + newsec) + elif len(index)==3: + self.dd['subcomponents'][index[0]][index[1]].insert(index[2], + newsec) + else: + raise Exception('Length of index must be 1 or 3') else: - self.dd['sections'][index] = newsection + if len(index)==1: + self.dd['subcomponents'][index[0]] = newsec + elif len(index)==2: + self.dd['subcomponents'][index[0]][index[1]][0] = newsec + elif len(index)==3: + self.dd['subcomponents'][index[0]][index[1]][index[2]] = newsec + else: + raise Exception('Length of index must be 1 or 3') - return(newsection) + return(newsec) def addConnector(self, conn_dd, index, id=None, insert=True): ''' @@ -1002,7 +1259,7 @@ def addConnector(self, conn_dd, index, id=None, insert=True): conn_dd : dict Connector design dictionary index : int - New index of connector in the mooring design dictionary connectors list + New index of connector in the mooring design dictionary subcomponents list id : str or int, optional ID of new connector insert : bool, optional @@ -1016,47 +1273,179 @@ def addConnector(self, conn_dd, index, id=None, insert=True): ''' if not id: if insert: - for i,conn in enumerate(self.dd['connectors']): - # update ids of connectors - if i>=index and isinstance(conn, Connector): - conn.id = 'Conn'+str(i+1) - id = 'Conn'+str(index) + for i in self.i_con: + # update ids of subcomponents after this in series + # first check if the i_con index i is the same level as index to add in + # and the final entry in i is greater than the index to add in + if len(i)==len(index) and i[-1]>index[-1]: + # check if all indices within the index list are less + # than or equal to the i_con index, i, list + if np.all([i[j]>=index[j] for j in range(len(i))]): + conn = self.getSubcomponent(i) + conn.id = '_'.join(['C',*[str(j) for j in i]]) + # make the id start with C and add each component of the index separated by _ + id = '_'.join(['C',*[str(j) for j in index]]) + # create connector object newconn = Connector(id, **conn_dd) + # insert it in self.dd['subcompoents'] list or replace entry in list as needed if insert: - self.dd['connectors'].insert(index, newconn) + if len(index)==1: + self.dd['subcomponents'].insert(index[0], + newconn) + elif len(index)==2: + self.dd['subcomponents'][index[0]][index[1]][0].insert(0, + newconn) + elif len(index)==3: + self.dd['subcomponents'][index[0]][index[1]].insert(index[2], + newconn) + else: + raise Exception('Length of index must be 1 or 3') else: - self.dd['connectors'][index] = newconn + if len(index)==1: + self.dd['subcomponents'][index[0]] = newconn + elif len(index)==2: + self.dd['subcomponents'][index[0]][index[1]][0] = newconn + elif len(index)==3: + self.dd['subcomponents'][index[0]][index[1]][index[2]] = newconn + else: + raise Exception('Length of index must be 1 or 3') return(newconn) - def connectSubcomponents(self): + # def connectSubcomponents(self, subcons=None): - # first disconnect any current subcomponents - for ii in self.i_sec: - self.subcomponents[ii].detachFrom('A') - self.subcomponents[ii].detachFrom('B') + # # first disconnect any current subcomponents + # for ii in self.i_sec: + # self.subcomponents[ii].detachFrom('A') + # self.subcomponents[ii].detachFrom('B') - # detach end connectors from platforms/anchors just in case - if len(self.subcomponents)>0: - endattsA = [att['obj'] for att in self.subcomponents[0].attachments.values()] - endattsB = [att['obj'] for att in self.subcomponents[-1].attachments.values()] - for att in endattsA: - self.subcomponents[0].detach(att) - for att in endattsB: - self.subcomponents[-1].detach(att) + # # # detach end connectors from platforms/anchors just in case + # # if len(self.subcomponents)>0: + # # endattsA = [att['obj'] for att in self.subcomponents[0].attachments.values()] + # # endattsB = [att['obj'] for att in self.subcomponents[-1].attachments.values()] + # # for att in endattsA: + # # self.subcomponents[0].detach(att) + # # for att in endattsB: + # # self.subcomponents[-1].detach(att) - # Now connect the new set of subcomponents and store them in self(Edge).subcomponents! - subcons = [] # temporary list of node-edge-node... to pass to the function - for i in range(self.n_sec): - subcons.append(self.dd['connectors'][i]) - subcons.append(self.dd['sections'][i]) - subcons.append(self.dd['connectors'][-1]) - self.addSubcomponents(subcons) # Edge method to connect and store em - - # Indices of connectors and sections in self.subcomponents list - self.i_con = list(range(0, 2*self.n_sec+1, 2)) - self.i_sec = list(range(1, 2*self.n_sec+1, 2)) + # # Now connect the new set of subcomponents and store them in self(Edge).subcomponents! + # if subcons is None: + # subcons = [] # temporary list of node-edge-node... to pass to the function + # for i in range(self.n_sec): + # subcons.append(self.dd['connectors'][i]) + # subcons.append(self.dd['sections'][i]) + # subcons.append(self.dd['connectors'][-1]) + # self.addSubcomponents(subcons) # Edge method to connect and store em + + def convertSubcomponents(self, subs_list): + '''Create section and connector objects from the subcomponents dicts. + ''' + # go through each entry in subcomponents list + for i,sub in enumerate(subs_list): + # if this entry is a list, go through each entry in that + if isinstance(sub,list): + self.parallels = True # flag there is at least one parallel section + for j,subsub in enumerate(sub): + # if this is a list (3rd level), make sections and connectors from entries + if isinstance(subsub, list): + for k, subsubsub in enumerate(subsub): + if 'L' in subsubsub: + id = '_'.join(['S',*[str(l) for l in [i,j,k]]]) + # this is a section + subs_list[i][j][k] = self.addSection(subsubsub['L'], + subsubsub['type'], + [i,j,k], + id=id, + insert=False) + self.i_sec.append([i, j, k]) + else: + # this should be a connector (no length provided) + id = '_'.join(['C',*[str(l) for l in [i,j,k]]]) + subs_list[i][j][k] = self.addConnector(subsubsub, + [i,j,k], + id=id, + insert=False) + self.i_con.append([i, j, k]) + else: + raise Exception('subcomponent list entry must be length 1 or 3') + elif 'L' in sub: + # this is a section + id = 'S'+str(i) + subs_list[i] = self.addSection(sub['L'], + sub['type'], + [i], + id=id, + insert=False) + self.i_sec.append([i]) + else: + # this is a connector + id = 'C'+str(i) + subs_list[i] = self.addConnector(sub, [i], id=id, insert=False) + self.i_con.append([i]) + + def sections(self, dd=None): + ''' + returns list of sections in the mooring + ''' + secs = [] + # allow option to input dict of subcomponents and pull sections from that + if dd: + for sub in dd['subcomponents']: + if 'L' in sub: + secs.append(sub) + elif isinstance(sub, list): + for subsub in sub: + if isinstance(subsub, list): + for sss in subsub: + if 'L' in sss: + secs.append(sss) + elif 'L' in sss: + secs.append(subsub) + else: + for i in self.i_sec: + secs.append(self.getSubcomponent(i)) + + return secs + + def connectors(self, dd=None): + ''' + returns list of connectors in the mooring + ''' + conns = [] + # allow option to input dict of subcomponents and pull sections from that + if dd: + for sub in dd['subcomponents']: + if not 'L' in sub and isinstance(sub, dict): + conns.append(sub) + elif isinstance(sub, list): + for subsub in sub: + if isinstance(subsub, list): + for sss in subsub: + if not 'L' in sss and isinstance(sss, dict): + conns.append(sss) + elif not 'L' in sss and isinstance(sss, dict): + conns.append(subsub) + else: + for i in self.i_con: + conns.append(self.getSubcomponent(i)) + + return conns + # def convertSubcomponents(self,subs_list, level=0, index=[0]): + # ind = index + # for i,sub in enumerate(subs_list): + # if isinstance(sub, list): + # lvl = level+1 + # if len + # ind.append(0) + # self.convertSubcomponents(sub,level=lvl, index=ind) + # elif 'L' in sub: + # # this is a section + # id = '_'.join([str(j) for j in ind]) + # self.addSection(sub['L'], sub['type'], ind, id=id) + # ind[level] += 1 + # else: + # self.addConnector(sub, ind) + # ind[level] += 1 - \ No newline at end of file diff --git a/famodel/ontology/README.md b/famodel/ontology/README.md index 33de9515..e06b70a4 100644 --- a/famodel/ontology/README.md +++ b/famodel/ontology/README.md @@ -168,7 +168,6 @@ csv filename. TI: [ , , ] Shear: [ , , ] CS: [ , , ] - 8.5 9.8, 10.4, 11.8, 12.4, 13.7, 16.8, 18.1, 18.6, 19.8, 20.3, 21.4 WS_2_4 : # conditional values from wind speed range of 2 - 4 m/s @@ -307,7 +306,10 @@ Additionally, a list of mooring lines can be input in the line_data table with s If there is an anchor connected to this line, it must be listed in end A, not end B. All anchors listed in line_data end A must have a matching ID in the anchor_data table, and all FOWTs listed in line_data end A or end B must have a matching ID in the array_data table. The anchor and fowt IDs must all be unique. The mooring lines each have a mooring configuration ID which links to the [Mooring Line Configs](#mooring-line-configurations) section. -There is also an option to adjust the length of the line, depending on the spacing. + +The fairleadA and fairleadB keys are optional; if not provided, the moorings will attach to the platform at the platform's fairlead radius and depth based on the angle of the mooring line. If none is provided, the fairlead radius and depth of the platform(s) connected to the mooring line is used to determine the fairlead point. If you choose to specify fairlead points, they are represented with an integer value or list of integers in fairleadA and fairleadB respectively. This integer value maps to an index (starting at 1) in the list of fairleads within the platform definition of the associated platform type in [Platforms](#platforms). For lines with shared anchors, the fairleadA is listed as None. + +If a mooring line has multiple connection points to a platform, such as in the case of a bridle, a list of integers (starting at 1) is used to specify the fairlead connection points of each section. The order that the fairlead points are listed here corresponds to the order of parallel sections provided in the mooring configuration from the [Mooring Line Configs](#mooring-line-configurations) section. ```yaml array_mooring: @@ -318,11 +320,11 @@ array_mooring: - [ anch2, suction1, , , ] line_keys : - [MooringConfigID , end A, end B, lengthAdjust] + [MooringConfigID , endA, endB, fairleadA, fairleadB] line_data : - - [ semitaut-poly_1 , anch1, fowt1, 0] - - [ semitaut-poly_1 , anch2, fowt1, 0] - - [ semitaut-poly_2 , fowt1, f2, 0] + - [ semitaut-poly_1 , anch1, fowt1, None, 1] + - [ semitaut-poly_1 , anch2, fowt3, None, 2] + - [ semitaut-poly_bridle , fowt1, f2, 2, [3,4]] ``` ### Array Cables @@ -330,7 +332,7 @@ There are two locations to list cables, either or both may be used. Cables liste This section provides a straightforward and compact way to define the power cables in the array. The CableID refers to an entry in the [Top Level Cables](#top-level-cables) section. For each end (A and B) of the cable, it specifies the -platform (matching an ID in the [array table](#array-layout)) it is attached to, the dynamic cable attached at either end (matching an ID in the [Dynamic Cable Configurations](#dynamic-cable-configurations) section), and the heading of the cable at the attachment of each end, using headings relative to the heading of the platform or substation it is connected to, running clockwise. +platform (matching an ID in the [array table](#array-layout)) it is attached to, the dynamic cable attached at either end (matching an ID in the [Dynamic Cable Configurations](#dynamic-cable-configurations) section), the heading of the dynamic cable at the attachment of each end, using headings relative to the heading of the platform or substation it is connected to, running clockwise, and the index in the platform J-tube list (starting at 1) each end is attached to. The JtubeA and JtubeB keys in the table are optional to implement; if not provided it is assumed that the cable attaches to the platform at a j-tube radius specified in the dynamic cable configuration (if provided) or at the fairlead radius of the platform. The static cable type is listed under 'cableType', referencing either an entry in the [Cable Cross Sectional Properties](#cable-cross-sectional-properties) section or a cable type name in the FAModel CableProps yaml. Length adjustment information is also included. @@ -339,10 +341,10 @@ If a cable does not have a feature (for example, a suspended cable would not hav ```yaml array_cables: - keys: [ AttachA, AttachB, DynCableA, DynCableB, headingA, headingB, cableType, lengthAdjust] + keys: [ AttachA, AttachB, DynCableA, DynCableB, headingA, headingB, JtubeA, JtubeB, cableType ] data: - - [ f2, substation1, lazy_wave1, lazy_wave2, 270, 270, static_cable_66, 0 ] - - [ fowt1, f2, suspended_1, None, 90, 270, None, 0 ] + - [ f2, substation1, lazy_wave1, lazy_wave2, 270, 270, 1, 1, static_cable_66 ] + - [ fowt1, f2, suspended_1, None, 90, 270, 2, 3, None ] ``` ## Topside(s) @@ -389,8 +391,15 @@ by [WEIS](https://weis.readthedocs.io). This section defines the floating support structures used in the design. As in the previous section, it can contain a single platform or a list of platforms. By default, the format here follows that used by -[RAFT](https://openraft.readthedocs.io) input files, with the addition of 'rFair', 'zFair', and 'type' entries to the -dictionary for each platform in the first level of each platform listed. In this case, rFair is the fairlead radius, zFair is the fairlead depth with respect to the platform depth, and type describes the kind of platform (i.e. FOWT for a floating wind turbine, Substation, WEC). An optional input is z_location, which describes the nominal depth of the platform. If the z_location is not provided here or as a column in the array table, it is assumed to be 0. +[RAFT](https://openraft.readthedocs.io) input files, with the addition of 'type', and the optional entries of 'fairleads', 'Jtubes', 'rFair' and 'zFair' to the +dictionary for each platform in the first level of each platform listed. In this case, type describes the kind of platform (i.e. FOWT for a floating wind turbine, Substation, WEC). + +Optional entries include: + - *fairleads* : a list of dictionaries providing information on the relative fairlead locations of the platform. The exact relative positions can be listed for each fairlead, or a relative position and headings (relative to 0 platform heading) can be provided. If a list of fairlead headings is provided in one fairlead entry, the heading list indices are added to the list of fairlead indices referenced in mooring_systems or array_mooring. For the case below, the 30 degree heading would be index 1, 150 degree heading would be index 2, 270 degree heading would be index 3, and the 'fairleads2' entry (with relative position [-57.779,-5.055, -14]) would be index 4) + - *Jtubes*: a list of dictionaries providing information on the relative J-tube locations on the platform. Like fairleads, the exact relative position can be provided or a relative position and list of headings (relative to 0 platform heading) can also be provided. If a list of j-tube headings is provided in one j-tube entry, the heading list indices are added to the list of j-tube indices referenced in cables or array_cables (just like fairleads). +- *rFair*: fairlead radius. MUST be provided if fairleads and Jtubes not provided. +- *zFair*: fairlead depth with respect to the platform depth. MUST be provided if fairleads and Jtubes not provided. +- *z_location*: nominal depth of the platform. If the z_location is not provided here or as a column in the array table, it is assumed to be 0. However, support will be added for also linking to platform descriptions that follow the [WindIO](https://windio.readthedocs.io) ontology format, which is also used @@ -401,6 +410,17 @@ platform: potModMaster : 1 # [int] master switch for potMod variables; 0=keeps all member potMod vars the same, 1=turns all potMod vars to False (no HAMS), 2=turns all potMod vars to True (no strip) dlsMax : 5.0 # maximum node splitting section amount for platform members; can't be 0 + fairleads : + # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [58, 0, -14] # relative coordinates of fairlead to platform center + headings: [30, 150, 270] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + - name: fairleads2 + r_rel: [-57.779,-5.055, -14] + Jtubes : # list of Jtube coordinates for the platform relative to platform coordinate and 0-degree heading + - name: Jtube1 + r_rel: [5, 0, -20] + headings: [90, 210, 330] # headings in degrees for the Jtube (if multiple headings, the Jtube will be repeated for each heading) rFair : 58 zFair : -15 type : FOWT # floating wind turbine platform @@ -441,29 +461,30 @@ anchor characteristics. ### Mooring Systems This section describes the mooring systems that could be used for individual turbines and repeated throughout the array. Each mooring system contains a -list of mooring lines, which contains the mooring configuration ID, the heading, the anchor type, and a possible length adjustment. The +list of mooring lines, which contains the mooring configuration ID, the heading, the anchor type, and optionally, fairlead connection index for end B. The mooring configuration ID links to the details about the segments lengths and types in the [mooring line configurations](#mooring-line-configurations) section. The heading refers to the angle of the mooring line and it rotates clockwise from North, relative to the heading of the platform. The anchor type links to details about the anchor -size and dimensions in the [anchor types section](#anchor-types). The length adjustment -is an optional parameter that can adjust the mooring line length for a shallower or deeper depth, for example. +size and dimensions in the [anchor types section](#anchor-types). The fairlead index +is an optional parameter that can specify the relative fairlead position point on a platform. +This index (which starts at 1) refers to the list of fairlead relative positions specified in the [Platforms](#platforms) section. If a list of indices is provided for a mooring line entry, this mooring line has a bridle and each entry in the list refers to a fairlead index in the platform definition. ```yaml mooring_systems: ms1: - name: 3-line taut polyester mooring system + name: 3-line taut polyester mooring system with 3rd line bridle - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ semitaut-poly_1, 30 , suction 1, 0 ] - - [ semitaut-poly_1, 150 , suction 1, 0 ] - - [ semitaut-poly_1, 270 , suction 1, 0 ] + - [ semitaut-poly_1, 30 , suction 1, 1 ] + - [ semitaut-poly_1, 150 , suction 1, 2 ] + - [ semitaut-poly_1, 270 , suction 1, [4,5] ] ``` ### Mooring Line Configurations The mooring line configurations lists the segment lengths and line types that make up each mooring line. Each line has a name that can then be specified as the MooringConfigID in the [mooring systems](#mooring-systems) section. The span is specified for each configuration, which represents the distance in the x-y plane between -the two connection points of the line - i.e. between fairlead and anchor, or for shared lines, fairlead and fairlead. +the two connection points of the line - i.e. between fairlead and anchor, or for shared lines, fairlead and fairlead. If there is a bridle, the fairlead position used to calculate span is the midpoint of the bridle fairlead locations. Fairlead radius and fairlead depth are specified in the [Platform](#platforms) section. Each line contains a list of sections that details the line section type and length. The line type name @@ -482,6 +503,9 @@ the middle line (last line given in the list) is doubled in length in the mirror For example, the 'rope_shared' config in the yaml below would produce a symmetric shared line with sections in the following order a 150 m section of rope, a clump weight, a 1172 m section of rope (note the doubled length), a clump weight, and finally a 150 m section of rope. +A section of line that has multiple parallel sections, such as a bridle or double chain section bounded by triplates, is specified with the 'subsections' key. Each list within the subsections key describes the sections of line connected in series for that individual line within the parallel. The following image visualizes the mooring configuration described in 'taut_bridle_double_chain': +![Mooring configuration with a bridle and double chain section](../images/parallel_sections.png) + ```yaml @@ -512,9 +536,40 @@ a 150 m section of rope, a clump weight, a 1172 m section of rope (note the doub - type: polyester_182mm # ID of a mooring line section type length: 199.8 # [m] length (unstretched) + taut_bridle_double_chain: # mooring line configuration identifier + + name: rope configuration 1 with a bridle # descriptive name + + span: 1131.37 + + + sections: #in order from anchor to fairlead + - type: chain_155mm + length: 20 + - type: rope # ID of a mooring line section type + length: 500 # [m] usntretched length of line section + - connectorType: triplate + - subsections: # double chain section + - - mooringFamily: chain + d_nom: 0.1 + length: 120 + - - mooringFamily: chain + d_nom: 0.1 + length: 120 + - connectorType: triplate + - type: rope # ID of a mooring line section type + length: 500 # [m] usntretched length of line section + - subsections: # bridle sections for end B + - - type: rope + length: 50 + - connectorType: shackle + - - type: rope + length: 50 + - connectorType: shackle + rope_shared: - name: shared rope + name: shared rope line shown symmetrically symmetric: True span: 1484 @@ -663,6 +718,8 @@ section, including joints, buoyancy module layout and other appendages. A top-level cable is defined as the full assembly of electrical connection equipment between two turbines or a turbine and a substation. 'type' links to the static cable property description, either in the [Cable Cross-Sectional Properties](#cable-cross-sectional-properties) section or in the cableProps_default yaml. Each cable end (A and B) is defined by the attached FOWT/substation/junction ID linking to the associated ID in the array table (attachID), the heading of the cable, and the dynamic cable configuration ID linking to a key in the [Dynamic Cable Configurations](#cable-configurations) section (dynamicID). +Jtube is an optional parameter defined for both endA and endB. If used, it refers to an index (starting at 1) in the Jtube list of the platform definition, which defines relative Jtube positions on a platform. If not provided, either the rJtube (optional) listed in the dynamic cable configuration, or the fairlead radius will be used to determine the platform connection point. + Routing can be added as an option, described in a list of coordinates for x,y, and radius values. Burial can also be included, with the station describing the normalized length along the cable, and depth describing the cable burial depth below the mudline. ```yaml @@ -674,11 +731,13 @@ Routing can be added as an option, described in a list of coordinates for x,y, a attachID: fowt1 # FOWT/substation/junction ID heading: 270 # [deg] heading of attachment at end A dynamicID: lazy_wave1 # ID of dynamic cable configuration at this end + Jtube: 1 endB: attachID: f2 # FOWT/substation/junction ID heading: 270 # [deg] heading of attachment at end B dynamicID: lazy_wave1 # ID of dynamic cable configuration at this end + Jtube: 2 routing_x_y_r: # optional vertex points along the cable route. Nonzero radius wraps around a point at that radius. - [-900, -1450, 20] @@ -700,6 +759,8 @@ and the volume of a single buoyancy module. The volume is only needed if the buo from the cableProps_defaul yaml. As with the cable properties, the 'type' in the sections list must refer to an entry in either the [Cable Appendages](#cable-appendages) section or in the FAModel cableProps_default.yaml. +rJtube is an optional parameter; if provided, and a Jtube relative position is not provided, it defines the radial distance of the connection point for the cable to the platform. + Similar to mooring lines, the span refers to the end to end distance of the line in the x-y plane. ```yaml @@ -713,6 +774,7 @@ dynamic_cable_configs: A: 300 cable_type: dynamic_cable_66_1 # ID of a cable section type length: 353.505 # [m] length (unstretched) + rJtube: 5 #[m] radial distance from platform center of J-tube sections: - type: buoyancy_module_1 diff --git a/famodel/platform/fairlead.py b/famodel/platform/fairlead.py new file mode 100644 index 00000000..2861a500 --- /dev/null +++ b/famodel/platform/fairlead.py @@ -0,0 +1,12 @@ +from famodel.famodel_base import Node + +class Fairlead(Node): + + def __init__(self,id): + + # Initialize as a node + Node.__init__(self,id) + + + + \ No newline at end of file diff --git a/famodel/platform/platform.py b/famodel/platform/platform.py index 157c9679..c4ae0739 100644 --- a/famodel/platform/platform.py +++ b/famodel/platform/platform.py @@ -9,6 +9,7 @@ from famodel.cables.cable import Cable from famodel.anchors.anchor import Anchor from famodel.cables.cable import DynamicCable +from famodel.famodel_base import Node, Edge class Platform(Node): ''' @@ -61,6 +62,7 @@ def __init__(self, id, r=[0,0,0], heading=0, mooring_headings=[60,180,300],rFair self.failure_probability = {} self.raftResults = {} + def setPosition(self, r, heading=None, degrees=False,project=None): ''' Set the position/orientation of the platform as well as the associated @@ -74,31 +76,20 @@ def setPosition(self, r, heading=None, degrees=False,project=None): x and y coordinates to position the node at [m]. heading, float (optional) The heading of the platform [deg or rad] depending on - degrees parameter (True or False). + degrees parameter (True or False) in compass direction ''' - ''' - # Future approach could be + # first call the Node method to take care of the platform and what's directly attached - Node.setPosition(self, r, heading=heading) - # then also adjust the anchor points - ''' - - # Store updated position and orientation - for i,ri in enumerate(r): - self.r[i] = np.array(ri) - - if not heading == None: - if degrees: + if heading: # save compass heading in radians + if degrees == True: self.phi = np.radians(heading) else: self.phi = heading - - # correction for xy coords - corr = np.radians(90) - - # Get 2D rotation matrix - self.R = np.array([[np.cos(corr-self.phi), -np.sin(corr-self.phi)],[np.sin(corr-self.phi), np.cos(corr-self.phi)]]) + # send in cartesian heading to node.setPosition (+ rotations CCW here) + Node.setPosition(self, r, theta=-self.phi) + # then also adjust the anchor points + # Update the position of any Moorings count = 0 # mooring counter (there are some attachments that aren't moorings) @@ -117,11 +108,12 @@ def setPosition(self, r, heading=None, degrees=False,project=None): cab = self.attachments[att]['obj'] - # update headings stored in subcomponents - headings = [cab.subcomponents[0].headingA + self.phi, cab.subcomponents[-1].headingB + self.phi] + # update heading stored in subcomponent for attached end + # pf_phis = [cab.attached_to[0].phi, cab.attached_to[1].phi] + # headings = [cab.subcomponents[0].headingA + pf_phis[0], cab.subcomponents[-1].headingB + pf_phis[1]] # reposition the cable - cab.reposition(headings=headings,project=project) + cab.reposition(project=project) def mooringSystem(self,rotateBool=0,mList=None,bodyInfo=None, project=None): @@ -147,23 +139,20 @@ def mooringSystem(self,rotateBool=0,mList=None,bodyInfo=None, project=None): # check if the subsystems were passed in from the function call if not mList: - mList = [] - for i in self.attachments: - if isinstance(self.attachments[i]['obj'],Mooring): - mList.append(self.attachments[i]['obj']) + mList = [moor for moor in self.getMoorings().values()] if project and len(project.grid_depth) > 1: - # calculate the maximum anchor spacing - anchor_spacings = [np.linalg.norm(mooring.rA[0:2] - self.r[:2]) for mooring in mList] - # get the bathymetry range that is related to this platform - margin = 1.2 - small_grid_x = np.linspace((self.r[0] - np.max(anchor_spacings)*margin), (self.r[0] + np.max(anchor_spacings)*margin), 10) - small_grid_y = np.linspace((self.r[1] - np.max(anchor_spacings)*margin), (self.r[1] + np.max(anchor_spacings)*margin), 10) - # interpolate the global bathymetry - small_grid_depths = np.zeros([len(small_grid_y), len(small_grid_x)]) - for i,x in enumerate(small_grid_x): - for j,y in enumerate(small_grid_y): - small_grid_depths[j,i] = project.getDepthAtLocation(x, y) + # # calculate the maximum anchor spacing + # anchor_spacings = [np.linalg.norm(mooring.rA[0:2] - self.r[:2]) for mooring in mList] + # # get the bathymetry range that is related to this platform + # margin = 1.2 + # small_grid_x = np.linspace((self.r[0] - np.max(anchor_spacings)*margin), (self.r[0] + np.max(anchor_spacings)*margin), 10) + # small_grid_y = np.linspace((self.r[1] - np.max(anchor_spacings)*margin), (self.r[1] + np.max(anchor_spacings)*margin), 10) + # # interpolate the global bathymetry + # small_grid_depths = np.zeros([len(small_grid_y), len(small_grid_x)]) + # for i,x in enumerate(small_grid_x): + # for j,y in enumerate(small_grid_y): + # small_grid_depths[j,i] = project.getDepthAtLocation(x, y) #self.ms = mp.System(bathymetry=dict(x=small_grid_x, y=small_grid_y, depth=small_grid_depths)) self.ms = mp.System(bathymetry=dict(x=project.grid_x, y=project.grid_y, depth=project.grid_depth)) @@ -176,47 +165,113 @@ def mooringSystem(self,rotateBool=0,mList=None,bodyInfo=None, project=None): r6 = [self.r[0],self.r[1],self.r[2],0,0,0] # create body if bodyInfo: - self.ms.addBody(0,r6,m=bodyInfo['m'],v=bodyInfo['v'],rCG=np.array(bodyInfo['rCG']),rM=np.array(bodyInfo['rM']),AWP=bodyInfo['AWP']) + body = self.ms.addBody(0,r6,m=bodyInfo['m'],v=bodyInfo['v'],rCG=np.array(bodyInfo['rCG']),rM=np.array(bodyInfo['rM']),AWP=bodyInfo['AWP']) else: - self.ms.addBody(0,r6,m=19911423.956678286,rCG=np.array([ 1.49820657e-15, 1.49820657e-15, -2.54122031e+00]),v=19480.104108645974,rM=np.array([2.24104273e-15, 1.49402849e-15, 1.19971829e+01]),AWP=446.69520543229874) + body = self.ms.addBody(0,r6,m=19911423.956678286,rCG=np.array([ 1.49820657e-15, 1.49820657e-15, -2.54122031e+00]),v=19480.104108645974,rM=np.array([2.24104273e-15, 1.49402849e-15, 1.19971829e+01]),AWP=446.69520543229874) if rotateBool: # rotation self.setPosition(self.r) # make mooring system from subsystems - for i,attID in enumerate(self.attachments): - - # only process moorings that have subsystems for now + for i,mooring in enumerate(mList): - if type(self.attachments[attID]['obj']) == Mooring: - mooring = self.attachments[attID]['obj'] - if mooring.ss: - ssloc = mooring.ss - else: - ssloc = mooring.createSubsystem() + if mooring.ss and not mooring.parallels: + ssloc = mooring.ss + self.ms.lineList.append(ssloc) + else: + ssloc = mooring.createSubsystem(ms=self.ms) + + if ssloc: # only proceed it's not None + ''' + # add subsystem as a line to the linelist + self.ms.lineList.append(ssloc) + ssloc.number = i+1 + ''' + for j,att in enumerate(mooring.attached_to): + if isinstance(att,Anchor): + # check whether a moorpy anchor object exists for this mooring line + # if not att.mpAnchor: + # create anchor moorpy object + att.makeMoorPyAnchor(self.ms) + if mooring.parallels: + subcom = mooring.subcomponents[j] # check what's on the end of the mooring + + if isinstance(subcom, list): # bridle case + print('This case not implemented yet') + breakpoint() + elif isinstance(subcom, Node): + # TODO: get rel dist from connector to anchor + # for now, just assume 0 rel dist until anchor lug objects introduced + r_rel = [0,0,0] + # attach anchor body to subcom connector point + subcom.mpConn.type = 1 + att.mpAnchor.attachPoint(subcom.mpConn.number,r_rel) + else: + # need to create "dummy" point to connect to anchor body + point = self.ms.addPoint(1,att.r) + # attach dummy point to anchor body + att.mpAnchor.attachPoint(point.number,[0,0,0]) + # now attach dummy point to line + point.attachLine(ssloc.number, j) + + elif isinstance(att,Platform): + # attach rB point to platform + if mooring.parallels: # case with paralles/bridles + + # Look at end B object(s) + subcom = mooring.subcomponents[-1] + + if isinstance(subcom, list): # bridle case + for parallel in subcom: + subcom2 = parallel[-1] # end subcomponent of the parallel path + + # Code repetition for the moment: + if isinstance(subcom2, Edge): + r = subcom2.attached_to[1].r # approximate end point...? + point = self.ms.addPoint(1, r) + body.attachPoint(point.number, r-att.r) + point.attachLine(subcom2.mpLine.number, 1) # attach the subcomponent's line object end B + + elif isinstance(subcom2, Node): + r = subcom2.r # approximate end point...? + subcom2.mpConn.type = 1 + pnum = subcom2.mpConn.number + body.attachPoint(pnum, r-att.r) + + elif isinstance(subcom, Edge): + r = subcom.attached_to[1].r # approximate end point...? + point = self.ms.addPoint(1, r) + body.attachPoint(point.number, r-att.r) + point.attachLine(subcom.mpLine.number, 1) # attach the subcomponent's line object end B + + elif isinstance(subcom, Node): + r = subcom.r # approximate end point...? + subcom.mpConn.type = 1 + pnum = subcom.mpConn.number + body.attachPoint(pnum, r-att.r) + # (the section line object(s) should already be attached to this point) + + + else: # normal serial/subsystem case + # add fairlead point + point = self.ms.addPoint(1,ssloc.rB) + # add connector info for fairlead point + # >>> MH: these next few lines might result in double counting <<< + point.m = ssloc.pointList[-1].m + point.v = ssloc.pointList[-1].v + point.CdA = ssloc.pointList[-1].CdA + # attach the line to point + point.attachLine(ssloc.number,j) + body.attachPoint(point.number, ssloc.rB-att.r) # attach to fairlead (need to subtract out location of platform from point for subsystem integration to work correctly) - if ssloc: # only proceed it's not None - # add subsystem as a line to the linelist - self.ms.lineList.append(ssloc) - ssloc.number = i+1 - for att in mooring.attached_to: - if isinstance(att,Anchor): - # check whether a moorpy anchor object exists for this mooring line - # if not att.mpAnchor: - # create anchor moorpy object - att.makeMoorPyAnchor(self.ms) - # else: - # # add anchor point from anchor class and fairlead point adjusted to include location offsets, attach subsystem - # self.ms.pointList.append(att.mpAnchor) # anchor - # attach subsystem line to the anchor point - self.ms.pointList[-1].attachLine(i,0) - # add fairlead point as a coupled point - self.ms.addPoint(1,ssloc.rB) - # attach subsystem line to the fairlead point - self.ms.pointList[-1].attachLine(i,1) - # attach fairlead point to body - self.ms.bodyList[0].attachPoint(len(self.ms.pointList),self.ms.pointList[-1].r-np.append(self.r[:2], [0])) + + # # add fairlead point as a coupled point + # self.ms.addPoint(1,ssloc.rB) + # # attach subsystem line to the fairlead point + # self.ms.pointList[-1].attachLine(i,1) + # # attach fairlead point to body + # self.ms.bodyList[0].attachPoint(len(self.ms.pointList),self.ms.pointList[-1].r-np.append(self.r[:2], [0])) # initialize and plot self.ms.initialize() self.ms.solveEquilibrium() @@ -226,7 +281,8 @@ def mooringSystem(self,rotateBool=0,mList=None,bodyInfo=None, project=None): def getWatchCircle(self, plot=0, ang_spacing=45, RNAheight=150, - shapes=True,Fth=None,SFs=True,ms=None): + shapes=True,Fth=None,SFs=True,ms=None, DAF=1, + moor_seabed_disturbance=False): ''' Compute watch circle of platform, as well as mooring and cable tension safety factors and cable sag safety factors based on rated thrust. @@ -269,6 +325,7 @@ def getWatchCircle(self, plot=0, ang_spacing=45, RNAheight=150, moorings = [] # list of mooring lines attached cables = [] # list of cables attached dcs = [] + lBots = [0]*len(self.mooring_headings) # find turbines, cables, and mooorings attached to platform moorings = self.getMoorings().values() @@ -320,27 +377,36 @@ def getWatchCircle(self, plot=0, ang_spacing=45, RNAheight=150, ms.solveEquilibrium3(DOFtype='both') # equilibrate (specify 'both' to enable both free and coupled DOFs) if SFs: + # get loads on anchors (may be shared) + for j,anch in enumerate(anchors): + F2 = anch.mpAnchor.getForces()*DAF # add up all forces on anchor body + H = np.hypot(F2[0],F2[1]) # horizontal force + T = np.sqrt(F2[0]**2+F2[1]**2+F2[2]**2) # total tension force + if F[j] is None or T>np.sqrt(F[j][0]**2+F[j][1]**2+F[j][2]**2): + F[j] = F2 # max load on anchor + # save anchor load information + anch.loads['Hm'] = H + anch.loads['Vm'] = F[j][2] + anch.loads['thetam'] = np.degrees(np.arctan(anch.loads['Vm']/anch.loads['Hm'])) #[deg] + anch.loads['mudline_load_type'] = 'max' + anch.loads['info'] = f'determined from arrayWatchCircle() with DAF of {DAF}' # get tensions on mooring line for j,moor in enumerate(moorings): - MBLA = float(moor.ss.lineList[0].type['MBL']) - MBLB = float(moor.ss.lineList[-1].type['MBL']) - # print(MBLA,MBLB,moor.ss.TA,moor.ss.TB,MBLA/moor.ss.TA,MBLB/moor.ss.TB,abs(MBLA/moor.ss.TA),abs(MBLB/moor.ss.TB)) - MTSF = min([abs(MBLA/moor.ss.TA),abs(MBLB/moor.ss.TB)]) - # atenMax[j], btenMax[j] = moor.updateTensions() - if not minTenSF[j] or minTenSF[j]>MTSF: - minTenSF[j] = deepcopy(MTSF) - if not moor.shared: - if self.attachments[moor.id]['end'] == 'a': - # anchor attached to end B - F[j] = moor.ss.fB - else: - F[j] = moor.ss.fA + lBot = 0 + moor.updateTensions(DAF=DAF) + info = {'analysisType': 'quasi-static (MoorPy)', + 'info': f'determined from platform.getWatchCircle() with DAF of {DAF}'} + moor.updateSafetyFactors(info=info) + if moor_seabed_disturbance: + for sec in moor.sections(): + lBot += sec.mpLine.LBot + lBots[j] = max(lBots[j], lBot) # get tensions, sag, and curvature on cable for j,cab in enumerate(dcs): MBLA = cab.ss.lineList[0].type['MBL'] MBLB = cab.ss.lineList[-1].type['MBL'] - CMTSF = min([abs(MBLA/cab.ss.TA),abs(MBLB/cab.ss.TB)]) + CMTSF = min([abs(MBLA/(cab.ss.TA*DAF)),abs(MBLB/(cab.ss.TB*DAF))]) if not CminTenSF[j] or CminTenSF[j]>CMTSF: CminTenSF[j] = deepcopy(CMTSF) # CatenMax[j], CbtenMax[j] = cab.updateTensions() @@ -380,16 +446,7 @@ def getWatchCircle(self, plot=0, ang_spacing=45, RNAheight=150, ms.solveEquilibrium3(DOFtype='both') - if SFs: - # save anchor loads - for j,moor in enumerate(moorings): - for att3 in moor.attached_to: - if isinstance(att3,Anchor): - att3.loads['Hm'] = np.sqrt(F[j][0]**2+F[j][1]**2) - att3.loads['Vm'] = F[j][2] - att3.loads['thetam'] = np.degrees(np.arctan(att3.loads['Vm']/att3.loads['Hm'])) #[deg] - att3.loads['mudline_load_type'] = 'max' - + if SFs: maxVals = {'minTenSF':minTenSF,'minTenSF_cable':CminTenSF,'minCurvSF':minCurvSF,'minSag':minSag,'maxF':F}# np.vstack((minTenSF,CminTenSF,minCurvSF,minSag)) return(x,y,maxVals) else: diff --git a/famodel/project.py b/famodel/project.py index f73a5e1c..ad6b1dc7 100644 --- a/famodel/project.py +++ b/famodel/project.py @@ -21,21 +21,23 @@ from famodel.mooring.mooring import Mooring from famodel.platform.platform import Platform from famodel.anchors.anchor import Anchor -from famodel.mooring.connector import Connector +from famodel.mooring.connector import Connector, Section from famodel.substation.substation import Substation from famodel.cables.cable import Cable from famodel.cables.dynamic_cable import DynamicCable from famodel.cables.static_cable import StaticCable from famodel.cables.cable_properties import getCableProps, getBuoyProps, loadCableProps,loadBuoyProps -from famodel.cables.components import Joint +from famodel.cables.components import Joint, Jtube +from famodel.platform.fairlead import Fairlead from famodel.turbine.turbine import Turbine -from famodel.famodel_base import Node +from famodel.famodel_base import Node, Edge, rotationMatrix # Import select required helper functions from famodel.helpers import (check_headings, head_adjust, getCableDD, getDynamicCables, getMoorings, getAnchors, getFromDict, cleanDataTypes, getStaticCables, getCableDesign, m2nm, loadYAML, - configureAdjuster, route_around_anchors) + configureAdjuster, route_around_anchors, attachFairleads, + calc_heading, calc_midpoint) class Project(): @@ -105,8 +107,8 @@ def __init__(self, lon=0, lat=0, file=None, depth=202,raft=1): # Seabed grid self.grid_x = np.array([0]) # coordinates of x grid lines [m] self.grid_y = np.array([0]) # coordinates of y grid lines [m] - self.grid_depth = np.array([[depth]]) # depth at each grid point [iy, ix] self.depth = depth + self.grid_depth = np.array([[self.depth]]) # depth at each grid point [iy, ix] self.seabed_type = 'clay' # switch of which soil property set to use ('clay', 'sand', or 'rock') @@ -152,13 +154,18 @@ def load(self, info, raft=True): # if not project: # raise Exception(f'File {file} does not exist or cannot be read. Please check filename.') project = loadYAML(info) + + # save directory of main yaml for use when reading linked files + dir = os.path.dirname(os.path.abspath(info)) + else: project = info + dir = '' # look for site section # call load site method if 'site' in project: - self.loadSite(project['site']) + self.loadSite(project['site'], dir=dir) # look for design section # call load design method @@ -377,11 +384,23 @@ def loadDesign(self, d, raft=True): self.turbineTypes = turbines # ----- set up dictionary for each individual mooring line, create anchor, mooring, and platform classes ---- - # make platforms first if they exist, as there may be no moorings called out + + # check that all necessary sections of design dictionary exist if arrayInfo: - for i in range(len(arrayInfo)): + + mct = 0 # counter for number of mooring lines + import string + alph = list(string.ascii_lowercase) + jtube_by_platform = {} + fairlead_by_platform = {} # dict of platform ids as keys and fairlead objects list as values + + + for i in range(0, len(arrayInfo)): # loop through each platform in array + + + # get index of platform from array table pfID = int(arrayInfo[i]['platformID']-1) - # create platform instance (even if it only has shared moorings / anchors), store under name of ID for that row + # - - - create platform instance (even if it only has shared moorings / anchors), store under name of ID for that row if 'z_location' in arrayInfo[i]: r = [arrayInfo[i]['x_location'],arrayInfo[i]['y_location'],arrayInfo[i]['z_location']] @@ -396,25 +415,66 @@ def loadDesign(self, d, raft=True): hydrostatics = {} # add platform - self.addPlatform(r=r, id=arrayInfo[i]['ID'], phi=arrayInfo[i]['heading_adjust'], + platform = self.addPlatform(r=r, id=arrayInfo[i]['ID'], phi=arrayInfo[i]['heading_adjust'], entity=platforms[pfID]['type'], rFair=platforms[pfID].get('rFair',0), zFair=platforms[pfID].get('zFair',0),platform_type=pfID, hydrostatics=hydrostatics) - - # check that all necessary sections of design dictionary exist - if arrayInfo and lineConfigs: - - mct = 0 # counter for number of mooring lines - # set up a list of the alphabet for assigning names purposes - import string - alph = list(string.ascii_lowercase) - - for i in range(0, len(arrayInfo)): # loop through each platform in array - - # get index of platform from array table - pfID = int(arrayInfo[i]['platformID']-1) - # get platform object - platform = self.platformList[arrayInfo[i]['ID']] + + # add fairleads + pf_fairs = [] + fct = 1 # start at 1 because using indices starting at 1 in ontology + if 'fairleads' in platforms[pfID]: + for fl in platforms[pfID]['fairleads']: + # if headings provided, adjust r_rel with headings + if 'headings' in fl: + + + for head in fl['headings']: + # get rotation matrix of heading + R = rotationMatrix(0,0,np.radians(90-head)) + # apply to unrotated r_rel + r_rel = np.matmul(R, fl['r_rel']) + # r_rel = [fl['r_rel'][0]*np.cos(np.radians(90-head)), + # fl['r_rel'][1]*np.sin(np.radians(90-head)), + # fl['r_rel'][2]] + pf_fairs.append(self.addFairlead(id=platform.id+'_F'+str(fct), + platform=platform, + r_rel=r_rel)) + fct += 1 + # otherwise, just use r_rel as-is + elif 'r_rel' in fl: + pf_fairs.append(self.addFairlead(id=platform.id+'_F'+str(fct), + platform=platform, + r_rel=fl['r_rel'])) + fct += 1 + + fairlead_by_platform[platform.id] = pf_fairs + + # add J-tubes + pf_jtubes = [] + jct = 1 + if 'Jtubes' in platforms[pfID]: + for jt in platforms[pfID]['Jtubes']: + if 'headings' in jt: + for head in jt['headings']: + # get rotation matrix of heading + R = rotationMatrix(0,0,np.radians(90-head)) + # apply to unrotated r_rel + r_rel = np.matmul(R, jt['r_rel']) + pf_jtubes.append(self.addJtube(id=platform.id+'_J'+str(jct), + platform=platform, + r_rel=r_rel)) + jct += 1 + elif 'r_rel' in jt: + pf_jtubes.append(self.addJtube(id=platform.id+'_J'+str(jct), + platform=platform, + r_rel=jt['r_rel'])) + jct += 1 + jtube_by_platform[platform.id] = pf_jtubes + + + # # get platform object + # platform = self.platformList[arrayInfo[i]['ID']] # remove pre-set headings (need to append to this list so list should start off empty) platform.mooring_headings = [] @@ -445,7 +505,7 @@ def loadDesign(self, d, raft=True): node.dd = topside_dd platform.attach(node) - if mSystems and not arrayInfo[i]['mooringID'] == 0: #if not fully shared mooring on this platform + if lineConfigs and mSystems and not arrayInfo[i]['mooringID'] == 0: #if not fully shared mooring on this platform m_s = arrayInfo[i]['mooringID'] # get mooring system ID # # sort the mooring lines in the mooring system by heading from 0 (North) mySys = [dict(zip(d['mooring_systems'][m_s]['keys'], row)) for row in d['mooring_systems'][m_s]['data']] @@ -472,27 +532,43 @@ def loadDesign(self, d, raft=True): lineconfig = mySys[j]['MooringConfigID'] # create mooring and connector dictionary - m_config = getMoorings(lineconfig, lineConfigs, connectorTypes, arrayInfo[i]['ID'], self) + mdd = getMoorings(lineconfig, lineConfigs, connectorTypes, arrayInfo[i]['ID'], self) # create mooring object, attach ends, reposition moor = self.addMooring(id=name, heading=headings[j]+platform.phi, - dd=m_config, reposition=False) + dd=mdd, + reposition=False) anch = self.addAnchor(id=name, dd=ad, mass=mass) - + # attach ends moor.attachTo(anch, end='A') - moor.attachTo(platform, end='B') + if 'fairlead' in mySys[j]: + attachFairleads(moor, + 1, + platform, + fair_ID_start=platform.id+'_F', + fair_inds=mySys[j]['fairlead']) + + elif pf_fairs: + attachFairleads(moor, + 1, + platform, + fair_ID = pf_fairs[j].id) + + else: + moor.attachTo(platform, r_rel=[platform.rFair,0,platform.zFair], end='b') - # reposition mooring - moor.reposition(r_center=platform.r, heading=headings[j]+platform.phi, project=self) - # update anchor depth and soils - self.updateAnchor(anch=anch) - + # Position the subcomponents along the Mooring + moor.positionSubcomponents() + # update counter mct += 1 + + # update position of platform, moorings, anchors + platform.setPosition(r=platform.r, project=self) # ----- set up dictionary for each shared mooring line or shared anchor, create mooring and anchor classes ---- @@ -502,63 +578,81 @@ def loadDesign(self, d, raft=True): # get mooring line info for all lines for j in range(0, len(arrayMooring)): # run through each line - PFNum = [] # platform ID(s) connected to the mooring line + PF = [] # platforms connected to the mooring line # Error check for putting an anchor (or something else) at end B if not any(ids['ID'] == arrayMooring[j]['endB'] for ids in arrayInfo): raise Exception("Input for end B must match an ID from the array table.") if any(ids['ID'] == arrayMooring[j]['endB'] for ids in arrayAnchor): raise Exception(f"input for end B of line_data table row '{j}' in array_mooring must be an ID for a FOWT from the array table. Any anchors should be listed as end A.") + # Make sure no anchor IDs in arrayAnchor table are the same as IDs in array table for k in range(0,len(arrayInfo)): if any(ids['ID'] == arrayInfo[k] for ids in arrayAnchor): raise Exception(f"ID for array table row {k} must be different from any ID in anchor_data table in array_mooring section") + # determine if end A is an anchor or a platform if any(ids['ID'] == arrayMooring[j]['endA'] for ids in arrayInfo): # shared mooring line (no anchor) # get ID of platforms connected to line - PFNum.append(arrayMooring[j]['endB']) - PFNum.append(arrayMooring[j]['endA']) + PF.append(self.platformList[arrayMooring[j]['endB']]) + PF.append(self.platformList[arrayMooring[j]['endA']]) # find row in array table associated with these platform IDs and set locations for k in range(0, len(arrayInfo)): - if arrayInfo[k]['ID'] == PFNum[0]: + if arrayInfo[k]['ID'] == PF[0]: rowB = arrayInfo[k] - elif arrayInfo[k]['ID'] == PFNum[1]: + elif arrayInfo[k]['ID'] == PF[1]: rowA = arrayInfo[k] - # get headings (mooring heading combined with platform heading) - headingB = np.radians(arrayMooring[j]['headingB']) + self.platformList[PFNum[0]].phi + # # get headings (mooring heading combined with platform heading) + # headingB = np.radians(arrayMooring[j]['headingB']) + self.platformList[PFNum[0]].phi # get configuration for the line lineconfig = arrayMooring[j]['MooringConfigID'] # create mooring and connector dictionary for that line - m_config = getMoorings(lineconfig, lineConfigs, connectorTypes, self.platformList[PFNum[0]].id, self) + mdd = getMoorings(lineconfig, lineConfigs, connectorTypes, PF[0].id, self) # create mooring class instance - moor = self.addMooring(id=str(PFNum[1])+'-'+str(PFNum[0]), - heading=headingB, dd=m_config, shared=1) + moor = self.addMooring(id=str(PF[1].id)+'-'+str(PF[0].id), + dd=mdd, + shared=1) # attach ends - moor.attachTo(self.platformList[PFNum[1]],end='A') - moor.attachTo(self.platformList[PFNum[0]],end='B') + fairsB = attachFairleads(moor, + 1, + PF[0], + fair_ID_start=PF[0].id+'_F', + fair_inds=arrayMooring[j]['fairleadB']) + fairsA = attachFairleads(moor, + 0, + PF[1], + fair_ID_start=PF[1].id+'_F', + fair_inds=arrayMooring[j]['fairleadA']) + - # reposition - moor.reposition(r_center=[self.platformList[PFNum[1]].r, - self.platformList[PFNum[0]].r], + # determine heading + points = [[f.r[:2] for f in fairsA], + [f.r[:2] for f in fairsB]] + headingB = calc_heading(points[0], points[1]) + moor.reposition(r_center=[PF[1].r, + PF[0].r], heading=headingB, project=self) + + # Position the subcomponents along the Mooring + moor.positionSubcomponents() elif any(ids['ID'] == arrayMooring[j]['endA'] for ids in arrayAnchor): # end A is an anchor # get ID of platform connected to line - PFNum.append(arrayMooring[j]['endB']) + PF.append(self.platformList[arrayMooring[j]['endB']]) # get configuration for that line lineconfig = arrayMooring[j]['MooringConfigID'] # create mooring and connector dictionary for that line - m_config = getMoorings(lineconfig, lineConfigs, connectorTypes, self.platformList[PFNum[0]].id, self) + mdd = getMoorings(lineconfig, lineConfigs, connectorTypes, PF[0].id, self) # get letter number for mooring line - ind = len(self.platformList[PFNum[0]].getMoorings()) - # create mooring class instance, attach to end A and end B objects, reposition - moor = self.addMooring(id=str(PFNum[0])+alph[ind], - heading=np.radians(arrayMooring[j]['headingB'])+self.platformList[PFNum[0]].phi, - dd=m_config) + ind = len(PF[0].getMoorings()) + + # create mooring class instance + moor = self.addMooring(id=str(PF[0].id)+alph[ind], + dd=mdd) # check if anchor instance already exists if any(tt == arrayMooring[j]['endA'] for tt in self.anchorList): # anchor name exists already in list @@ -582,37 +676,58 @@ def loadDesign(self, d, raft=True): # attach anchor moor.attachTo(anchor,end='A') # attach platform - moor.attachTo(self.platformList[PFNum[0]],end='B') + fairsB = attachFairleads(moor, + 1, + PF[0], + fair_ID_start=PF[0].id+'_F', + fair_inds=arrayMooring[j]['fairleadB']) + + # determine heading + headingB = calc_heading(anchor.r[:2],[f.r[:2] for f in fairsB]) + + # re-determine span as needed from anchor loc and end B midpoint + # this is to ensure the anchor location does not change from that specified in the ontology + moor.span = np.linalg.norm(anchor.r[:2]- + np.array(calc_midpoint([f.r[:2] for f in fairsB]))) + # reposition mooring - moor.reposition(r_center=self.platformList[PFNum[0]].r, heading=np.radians(arrayMooring[j]['headingB'])+self.platformList[PFNum[0]].phi, project=self) + moor.reposition(r_center=PF[0].r, heading=headingB, project=self) # update depths zAnew, nAngle = self.getDepthAtLocation(aloc[0],aloc[1], return_n=True) moor.dd['zAnchor'] = -zAnew moor.z_anch = -zAnew - moor.rA = [aloc[0],aloc[1],-zAnew] + moor.setEndPosition([aloc[0],aloc[1],-zAnew], 0) + + # Position the subcomponents along the Mooring + moor.positionSubcomponents() - # update anchor depth and soils - self.updateAnchor(anchor, update_loc=False) + # # update anchor depth and soils + # self.updateAnchor(anchor, update_loc=False) else: # error in input raise Exception(f"end A input in array_mooring line_data table line '{j}' must be either an ID from the anchor_data table (to specify an anchor) or an ID from the array table (to specify a FOWT).") - # add heading - self.platformList[PFNum[0]].mooring_headings.append(np.radians(arrayMooring[j]['headingB'])) - if len(PFNum)>1: # if shared line - self.platformList[PFNum[1]].mooring_headings.append(np.radians(arrayMooring[j]['headingA'])) # add heading + # add heading to platform headings list + PF[0].mooring_headings.append(headingB-PF[0].phi)#np.radians(arrayMooring[j]['headingB'])) + PF[0].setPosition(r=PF[0].r, project=self) + if len(PF)>1: # if shared line + headingA = headingB - np.pi + PF[1].mooring_headings.append(headingA-PF[1].phi) # add heading + PF[1].setPosition(r=PF[1].r, project=self) # increment counter mct += 1 + # update all anchors + self.updateAnchor() + # ===== load Cables ====== # load in array cables from table (no routing, assume dynamic-static-dynamic or dynamic suspended setup) if arrayCableInfo: for i,cab in enumerate(arrayCableInfo): A=None - rJTubeA = None; rJTubeB = None # create design dictionary for subsea cable dd = {'cables':[],'joints':[]} @@ -620,22 +735,31 @@ def loadDesign(self, d, raft=True): dyn_cabA = cab['DynCableA'] if not 'NONE' in cab['DynCableA'].upper() else None dyn_cabB = cab['DynCableB'] if not 'NONE' in cab['DynCableB'].upper() else None stat_cab = cab['cableType'] if not 'NONE' in cab['cableType'].upper() else None + JtubeA = cab['JtubeA'] if ('JtubeA' in cab) else None + JtubeB = cab['JtubeB'] if ('JtubeB' in cab) else None + rJTubeA = None # if Jtube rel position not provided, this is the radial Jtube position + rJTubeB = None + + A_phi = self.platformList[cab['AttachA']].phi # end A platform phi + B_phi = self.platformList[cab['AttachB']].phi # end B platform phi if dyn_cabA: dyn_cab = cab['DynCableA'] Acondd, jAcondd = getDynamicCables(dyn_cable_configs[dyn_cab], cable_types, cable_appendages, self.depth, rho_water=self.rho_water, g=self.g) - Acondd['headingA'] = np.radians(90-cab['headingA']) + # only add a joint if there's a cable section after this if stat_cab or dyn_cabB: dd['joints'].append(jAcondd) + Acondd['headingA'] = np.radians(cab['headingA']) + A_phi # heading only if not suspended else: # this is a suspended cable - add headingB - Acondd['headingB'] = np.radians(90-cab['headingB']) + Acondd['headingB'] = np.radians(cab['headingB']) + B_phi - rJTubeA = dyn_cable_configs[dyn_cabA]['rJTube'] - Acondd['rJTube'] = rJTubeA + if 'rJTube' in dyn_cable_configs[dyn_cabA]: + rJTubeA = dyn_cable_configs[dyn_cabA]['rJTube'] + Acondd['rJTube'] = rJTubeA dd['cables'].append(Acondd) # get conductor area to send in for static cable A = Acondd['A'] @@ -653,11 +777,11 @@ def loadDesign(self, d, raft=True): cable_types, cable_appendages, self.depth, rho_water=self.rho_water, g=self.g) - - rJTubeB = dyn_cable_configs[dyn_cabB]['rJTube'] - Bcondd['rJTube'] = rJTubeB + if 'rJTube' in dyn_cable_configs[dyn_cabB]: + rJTubeB = dyn_cable_configs[dyn_cabB]['rJTube'] + Bcondd['rJTube'] = rJTubeB # add heading for end A to this cable - Bcondd['headingB'] = np.radians(90-arrayCableInfo[i]['headingB']) + Bcondd['headingB'] = np.radians(arrayCableInfo[i]['headingB']) + B_phi dd['cables'].append(Bcondd) # add joint (even if empty) dd['joints'].append(jBcondd) @@ -669,15 +793,22 @@ def loadDesign(self, d, raft=True): self.cableList[cableID] = Cable(cableID,d=dd) # attach ends if cab['AttachA'] in self.platformList.keys(): - # connect to platform - self.cableList[cableID].attachTo(self.platformList[cab['AttachA']],end='A') + # attach cable subcomponent to Jtube if it exists (higher level objs will automatically connect) + if jtube_by_platform[cab['AttachA']] and JtubeA: + self.cableList[cableID].subcomponents[0].attachTo(jtube_by_platform[cab['AttachA']][JtubeA-1], end='A') + else: + self.cableList[cableID].attachTo(self.platformList[cab['AttachA']],end='A') elif cab['AttachA'] in cable_appendages: pass else: raise Exception(f'AttachA {arrayCableInfo[i]["AttachA"]} for array cable {i} does not match any platforms or appendages.') if cab['AttachB'] in self.platformList.keys(): - # connect to platform - self.cableList[cableID].attachTo(self.platformList[cab['AttachB']],end='B') + # attach cable subcomponent to Jtube if it exists (higher level objs will automatically connect) + if jtube_by_platform[cab['AttachB']] and JtubeB: + self.cableList[cableID].subcomponents[-1].attachTo(jtube_by_platform[cab['AttachB']][JtubeB-1], end='B') + else: + self.cableList[cableID].attachTo(self.platformList[cab['AttachB']],end='B') + elif cab['AttachB'] in cable_appendages: pass else: @@ -692,6 +823,8 @@ def loadDesign(self, d, raft=True): for cab in cableInfo: rJTubeA = None; rJTubeB = None + JtubeA = cab['endA']['Jtube'] if ('Jtube' in cab['endA']) else None + JtubeB = cab['endB']['Jtube'] if ('Jtube' in cab['endB']) else None # create design dictionary for subsea cable dd = {'cables':[],'joints':[]} @@ -699,7 +832,10 @@ def loadDesign(self, d, raft=True): # pull out cable sections (some may be 'NONE') dyn_cabA = cab['endA']['dynamicID'] if not 'NONE' in cab['endA']['dynamicID'].upper() else None dyn_cabB = cab['endB']['dynamicID'] if not 'NONE' in cab['endB']['dynamicID'].upper() else None - stat_cab = cab['type'] if not 'NONE' in cab['type'].upper() else None + stat_cab = cab['type'] if not 'NONE' in cab['type'].upper() else None + + A_phi = self.platformList[cab['endA']['attachID']].phi # end A platform phi + B_phi = self.platformList[cab['endB']['attachID']].phi # end B platform phi # load in end A cable section type if dyn_cabA: @@ -707,17 +843,20 @@ def loadDesign(self, d, raft=True): cable_types, cable_appendages, self.depth, rho_water=self.rho_water, g=self.g) + # only add a joint if there's a cable section after this if stat_cab or dyn_cabB: dd['joints'].append(jAcondd) else: # this is a suspended cable - add headingB - Acondd['headingB'] = np.radians(90-cab['endB']['heading']) + Acondd['headingB'] = np.radians(cab['endB']['heading']) + B_phi + # add headingA - Acondd['headingA'] = np.radians(90-cab['endA']['heading']) - rJTubeA = dyn_cable_configs[dyn_cabA]['rJTube'] - Acondd['rJTube'] = rJTubeA + Acondd['headingA'] = np.radians(cab['endA']['heading']) + A_phi + if 'rJTube' in dyn_cable_configs[dyn_cabA]: + rJTubeA = dyn_cable_configs[dyn_cabA]['rJTube'] + Acondd['rJTube'] = rJTubeA # append to cables list dd['cables'].append(Acondd) @@ -745,10 +884,10 @@ def loadDesign(self, d, raft=True): self.depth, rho_water=self.rho_water, g=self.g) # add headingB - Bcondd['headingB'] = np.radians(90-cab['endB']['heading']) - - rJTubeB = dyn_cable_configs[dyn_cabB]['rJTube'] - Bcondd['rJTube'] = rJTubeB + Bcondd['headingB'] = np.radians(cab['endB']['heading']) + B_phi + if 'rJTube' in dyn_cable_configs[dyn_cabB]: + rJTubeB = dyn_cable_configs[dyn_cabB]['rJTube'] + Bcondd['rJTube'] = rJTubeB # append to cables list dd['cables'].append(Bcondd) # append to joints list @@ -762,16 +901,22 @@ def loadDesign(self, d, raft=True): # attach end A if cab['endA']['attachID'] in self.platformList.keys(): - # connect to platform - self.cableList[cableID].attachTo(self.platformList[cab['endA']['attachID']],end='A') + if jtube_by_platform[cab['endA']['attachID']] and JtubeA: + self.cableList[cableID].subcomponents[0].attachTo(jtube_by_platform[cab['endA']['attachID']][JtubeA], end='A') + else: + # connect to platform + self.cableList[cableID].attachTo(self.platformList[cab['endA']['attachID']],end='A') elif cab['endA']['attachID'] in cable_appendages: pass else: raise Exception(f"AttachA {cab['endA']['attachID']} for cable {cab['name']} does not match any platforms or appendages.") # attach end B if cab['endB']['attachID'] in self.platformList.keys(): - # connect to platform - self.cableList[cableID].attachTo(self.platformList[cab['endB']['attachID']],end='B') + if jtube_by_platform[cab['endB']['attachID']] and JtubeB: + self.cableList[cableID].subcomponents[-1].attachTo(jtube_by_platform[cab['endB']['attachID']][JtubeB], end='B') + else: + # connect to platform + self.cableList[cableID].attachTo(self.platformList[cab['endB']['attachID']],end='B') elif cab['endB']['attachID'] in cable_appendages: pass else: @@ -780,7 +925,8 @@ def loadDesign(self, d, raft=True): # reposition the cable self.cableList[cableID].reposition(project=self, rad_fair=[rJTubeA,rJTubeB]) - + for pf in self.platformList.values(): + pf.setPosition(pf.r, project=self) # ===== load RAFT model parts ===== # load info into RAFT dictionary and create RAFT model if raft: @@ -825,10 +971,13 @@ def loadDesign(self, d, raft=True): # ----- Site conditions processing functions ----- - def loadSite(self, site): + def loadSite(self, site, dir=''): '''Load site information from a dictionary or YAML file (specified by input). This should be the site portion of - the floating wind array ontology.''' + the floating wind array ontology. + + site : portion of project dict + dir : optional directory of main yaml file''' # standard function to load dict if input is yaml # load general information @@ -840,7 +989,13 @@ def loadSite(self, site): # load bathymetry information, if provided if 'bathymetry' in site and site['bathymetry']: if 'file' in site['bathymetry'] and site['bathymetry']['file']: # make sure there was a file provided even if the key is there - self.loadBathymetry(site['bathymetry']['file']) + filename = site['bathymetry']['file'] + + # if it's a relative file location, specify the root directory + if not os.path.isabs(filename): + filename = os.path.join(dir, filename) + self.loadBathymetry(filename) + elif 'x' in site['bathymetry'] and 'y' in site['bathymetry']: self.grid_x = np.array(site['bathymetry']['x']) self.grid_y = np.array(site['bathymetry']['y']) @@ -1494,11 +1649,40 @@ def addPlatform(self,r=[0,0,0], id=None, phi=0, entity='', platform.dd = dd self.platformList[id] = platform # also save in RAFT, in its MoorPy System(s) + return(platform) + + def addFairlead(self, id=None, platform=None, r_rel=[0,0,0], + mooring=None, end='b'): + ''' + Function to create a Fairlead object and attach it to a platform''' + # create an id if needed + if id == None: + if platform != None: + id = platform.id + str(len(platform.attachments)) + + # create fairlead object + fl = Fairlead(id=id) + + # attach subordinately to platform and provide relative location + if platform: + platform.attach(fl, r_rel=r_rel) + + # attach equally to mooring end connector + if mooring: + if end in ['a','A',0]: + mooring.subcomponents[0].join(fl) + elif end in ['b','B',1]: + mooring.subcomponents[-1].join(fl) + + # return fairlead object + return(fl) + + def addMooring(self, id=None, endA=None, endB=None, heading=0, dd={}, section_types=[], section_lengths=[], connectors=[], span=0, shared=0, reposition=False, subsystem=None, - **adjuster_settings): + subcons=None, **adjuster_settings): # adjuster=None, # method = 'horizontal', target = None, i_line = 0, ''' @@ -1696,36 +1880,32 @@ def addSubstation(self, id=None, platform=None, dd={}): self.substationList[id] = Substation(dd, id) if platform != None: platform.attach(self.substationList[id]) - - - def cableDesignInterpolation(self,depth,cables): - '''Interpolates between dynamic cable designs for different depths to produce - a design for the given depth + + def addJtube(self, id=None, platform=None, r_rel=[0,0,0], + cable=None, end='b'): ''' - # grab list of values for all cables - cabdesign = {} - cabdesign['span'] = [x.dd['span'] for x in cables] - depths = [-x.z_anch for x in cables] - cabdesign['n_buoys'] = [x.dd['buoyancy_sections']['N_modules'] for x in cables] - cabdesign['spacings'] = [x.dd['buoyancy_sections']['spacing'] for x in cables] - cabdesign['L_mids'] = [x.dd['buoyancy_sections']['L_mid'] for x in cables] - cabdesign['L'] = [x.dd['L'] for x in cables] - - # sort and interp all lists by increasing depths - sorted_indices = np.argsort(depths) - depths_sorted = [depths[i] for i in sorted_indices] - newdd = deepcopy(cable[0].dd) - newdd['span'] = np.interp(depth,depths_sorted,[cabdesign['span'][i] for i in sorted_indices]) - newdd['buoyancy_sections']['N_modules'] = np.interp(depth, depths_sorted, - [cabdesign['n_buoys'][i] for i in sorted_indices]) - newdd['buoyancy_sections']['spacing'] = np.interp(depth,depths_sorted, - [cabdesign['spacings'][i] for i in sorted_indices]) - newdd['buoyancy_sections']['L_mids'] = np.interp(depth,depths_sorted, - [cabdesign['L_mids'][i] for i in sorted_indices]) - newdd['L'] = np.interp(depth,depths,[cabdesign['L'][i] for i in sorted_indices]) - + Function to create a Jtube object and attach it to a platform''' + # create an id if needed + if id == None: + if platform != None: + id = platform.id + len(platform.attachments) + + # create J-tube object + jt = Jtube(id=id) - return(newdd) + # attach subordinately to platform and provide relative location + if platform: + platform.attach(jt, r_rel=r_rel) + + # attach equally to mooring end connector + if cable: + if end in ['a','A',0]: + cable.subcomponents[0].attachTo(jt) + elif end in ['b','B',1]: + cable.subcomponents[-1].attachTo(jt) + + # return fairlead object + return(jt) @@ -1736,6 +1916,7 @@ def addCablesConnections(self,connDict,cableType_def='dynamic_cable_66',oss=Fals cableConfig=None, configType=0,heading_buffer=30, route_anchors=False, adj_dir=1, consider_alternate_side=False): + '''Adds cables and connects them to existing platforms/substations based on info in connDict Designed to work with cable optimization output designed by Michael Biglu @@ -1827,12 +2008,11 @@ def addCablesConnections(self,connDict,cableType_def='dynamic_cable_66',oss=Fals attB = pf # update platform location pf.r[:2] = connDict[i]['coordinates'][-1] - - # get heading of cable from attached object coordinates - headingA = np.radians(90) - np.arctan2((connDict[i]['coordinates'][-1][0]-connDict[i]['coordinates'][0][0]), - (connDict[i]['coordinates'][-1][1]-connDict[i]['coordinates'][0][1])) - headingB = np.radians(90) - np.arctan2((connDict[i]['coordinates'][0][0]-connDict[i]['coordinates'][-1][0]), - (connDict[i]['coordinates'][0][1]-connDict[i]['coordinates'][-1][1])) + + # get heading of cable from attached object coordinates (compass heading) + headingA = calc_heading(connDict[i]['coordinates'][-1], + connDict[i]['coordinates'][0]) + headingB = headingA + np.pi # figure out approx. depth at location initial_depths = [] @@ -1844,11 +2024,12 @@ def addCablesConnections(self,connDict,cableType_def='dynamic_cable_66',oss=Fals # get depth at these locs initial_depths.append(self.getDepthAtLocation(*endLocA)) initial_depths.append(self.getDepthAtLocation(*endLocB)) - # select cable and collect design dictionary info on cable - selected_cable, dd = getCableDesign(connDict[i], cableType_def, - cableConfig, configType, - depth=np.mean(initial_depths)) + selected_cable, dd, cable_candidates = getCableDesign( + connDict[i], cableType_def, + cableConfig, configType, + depth=np.mean(initial_depths) + ) else: dd = {} dd['cables'] = [] @@ -1888,6 +2069,9 @@ def addCablesConnections(self,connDict,cableType_def='dynamic_cable_66',oss=Fals cab.attachTo(attB,end='b') if cableConfig: + if cable_candidates: + cab.subcomponents[0].alternate_cables=cable_candidates + cab.subcomponents[-1].alternate_cables=cable_candidates if 'head_offset' in selected_cable: headingA += np.radians(selected_cable['head_offset']) headingB -= np.radians(selected_cable['head_offset']) @@ -1899,6 +2083,7 @@ def addCablesConnections(self,connDict,cableType_def='dynamic_cable_66',oss=Fals msp = list(moors.values())[0].span + attA.rFair + 200 # add a bit extra # consider mooring headings from both ends if close enough pfsp = np.linalg.norm(attA.r-attB.r) + if consider_alternate_side and pfsp-2*attA.rFair < msp+dc0s: headingA = head_adjust([attA,attB], headingA, @@ -1941,8 +2126,8 @@ def addCablesConnections(self,connDict,cableType_def='dynamic_cable_66',oss=Fals stat_cable = cab.subcomponents[ind+ind_of_stat] # get new coordinate routing point stat_cable_end = stat_cable.rA if ind==0 else stat_cable.rB - coord = [stat_cable_end[0] + np.cos(heads[ii])*spandiff, - stat_cable_end[1] + np.sin(heads[ii])*spandiff] + coord = [stat_cable_end[0] + np.cos(np.pi/2-heads[ii])*spandiff, + stat_cable_end[1] + np.sin(np.pi/2-heads[ii])*spandiff] # append it to static cable object coordinates coords.append(coord) @@ -2073,20 +2258,36 @@ def plot2d(self, ax=None, plot_seabed=False,draw_soil=False,plot_bathymetry=True # Plot moorings one way or another (eventually might want to give Mooring a plot method) for mooring in self.mooringList.values(): - + lineList = [] if mooring.ss: # plot with Subsystem if available - labs = [] - for line in mooring.ss.lineList: - if 'chain' in line.type['material']: - line.color = 'k' - elif 'polyester' in line.type['material']: - line.color = [.3,.5,.5] - else: - line.color = [0.5,0.5,0.5] - labs.append(line.type['material'][0].upper()+ - line.type['material'][1:]+' Mooring') + lineList = mooring.ss.lineList + + elif mooring.parallels: + for i in mooring.i_sec: + sec = mooring.getSubcomponent(i) + if hasattr(sec,'mpLine'): + lineList.append(sec.mpLine) + line = sec.mpLine + + labs = [] + for line in lineList: + if 'chain' in line.type['material']: + line.color = 'k' + elif 'polyester' in line.type['material']: + line.color = [.3,.5,.5] + else: + line.color = [0.5,0.5,0.5] + labs.append(line.type['material'][0].upper()+ + line.type['material'][1:]+' Mooring') + + if mooring.ss: mooring.ss.drawLine2d(0, ax, color="self", endpoints=False, - Xuvec=[1,0,0], Yuvec=[0,1,0],label=labs) + Xuvec=[1,0,0], Yuvec=[0,1,0],label=labs) + elif mooring.parallels: + for i,line in enumerate(lineList): + line.drawLine2d(0, ax, color="self", + Xuvec=[1,0,0], Yuvec=[0,1,0],label=labs[i]) + else: # simple line plot ax.plot([mooring.rA[0], mooring.rB[0]], [mooring.rA[1], mooring.rB[1]], 'k', lw=0.5, label='Mooring Line') @@ -2413,6 +2614,19 @@ def plot3d(self, ax=None, figsize=(10,8), fowt=False, save=False, line.color = [0.5,0.5,0.5] line.lw = lw mooring.ss.drawLine(0, ax, color='self') + elif mooring.parallels: + for i in mooring.i_sec: + sec = mooring.getSubcomponent(i) + if hasattr(sec,'mpLine'): + line = sec.mpLine + if 'chain' in line.type['material']: + line.color = 'k' + elif 'polyester' in line.type['material']: + line.color = [.3,.5,.5] + else: + line.color = [0.5,0.5,0.5] + line.lw = lw + line.drawLine(0,ax,color='self') # plot the FOWTs using a RAFT FOWT if one is passed in (TEMPORARY) if fowt: @@ -2503,90 +2717,209 @@ def getMoorPyArray(self, plt=0, pristineLines=True, cables=True): else: self.ms.addBody(-1,r6,m=19911423.956678286,rCG=np.array([ 1.49820657e-15, 1.49820657e-15, -2.54122031e+00]),v=19480.104108645974,rM=np.array([2.24104273e-15, 1.49402849e-15, 1.19971829e+01]),AWP=446.69520543229874) body.body = self.ms.bodyList[-1] + # create anchor points and all mooring lines connected to the anchors (since all connected to anchors, can't be a shared mooring) - for i in self.anchorList: # i is key (name) of anchor - ssloc = [] - for j in self.anchorList[i].attachments: # j is key (name) of mooring object in anchor i + for anchor in self.anchorList.values(): # Go through each anchor + + # Create it's MoorPy Point object + if anchor.mpAnchor: # If anchor already exists in MoorPy + print("Why does this anchor already have a MoorPy Point?") + breakpoint() + + anchor.makeMoorPyAnchor(self.ms) + num = anchor.mpAnchor.number + + # Go through each thing/mooring attached to the anchor + for j, att in anchor.attachments.items(): + + mooring = att['obj'] + # create subsystem if pristineLines: - - self.anchorList[i].attachments[j]['obj'].createSubsystem(pristine=1, mooringSys=self.ms) + mooring.createSubsystem(pristine=True, ms=self.ms) # set location of subsystem for simpler coding - ssloc.append(self.anchorList[i].attachments[j]['obj'].ss) + ssloc = mooring.ss else: - self.anchorList[i].attachments[j]['obj'].createSubsystem(mooringSys=self.ms) + mooring.createSubsystem(pristine=False, ms=self.ms) # set location of subsystem for simpler coding - ssloc.append(self.anchorList[i].attachments[j]['obj'].ss_mod) - self.ms.lineList.append(ssloc[-1]) - ssloc[-1].number = len(self.ms.lineList) - # create anchor point if it doesn't already exist - if self.anchorList[i].mpAnchor: - # get point number of anchor - num = self.anchorList[i].mpAnchor.number - # attach line to anchor point - self.ms.pointList[num-1].attachLine(ssloc[-1].number,0) - else: - self.anchorList[i].makeMoorPyAnchor(self.ms) - # attach line to anchor point - self.ms.pointList[-1].attachLine(ssloc[-1].number,0) + ssloc = mooring.ss_mod + + # (ms.lineList.append is now done in Mooring.createSubsystem) + + # Attach the Mooring to the anchor + if mooring.parallels: # the case with parallel sections, multiple MoorPy objects + + # note: att['end'] should always be 0 in this part of the + # code, but keeping the end variable here in case it opens + # up ideas for code consolidation later. + + subcom = mooring.subcomponents[-att['end']] # check what's on the end of the mooring + + if isinstance(subcom, list): # bridle case + print('This case not implemented yet') + breakpoint() + elif isinstance(subcom, Node): + # TODO: get rel dist from connector to anchor + # for now, just assume 0 rel dist until anchor lug objects introduced + r_rel = [0,0,0] + subcom.mpConn.type = 1 + # attach anchor body to subcom connector point + anchor.mpAnchor.attachPoint(subcom.mpConn.number,r_rel) + # (the section line object(s) should already be attached to this point) + #TODO >>> still need to handle possibility of anchor bridle attachment, multiple anchor lugs, etc. <<< + + else: # Original case with Subsystem + # need to create "dummy" point to connect to anchor body + point = self.ms.addPoint(1,anchor.r) + # attach dummy point to anchor body + anchor.mpAnchor.attachPoint(point.number,[0,0,0]) + # now attach dummy point to line + point.attachLine(ssloc.number, att['end']) + + # Check for fancy case of any lugs (nodes) attached to the anchor + if any([ isinstance(a['obj'], Node) for a in anchor.attachments.values()]): + print('Warning: anchor lugs are not supported yet') + breakpoint() # find associated platform and attach body to point (since not a shared line, should only be one platform with this mooring object) - for ii,k in enumerate(self.platformList): # ii is index in dictionary, k is key (name) of platform - if j in self.platformList[k].attachments: # j is key (name) of mooring object in anchor i checking if that same mooring object name is attached to platform k - PF = self.platformList[k] # platform object associated with mooring line j and anchor i - body = PF.body + for platform in self.platformList.values(): # ii is index in dictionary, k is key (name) of platform + if j in platform.attachments: # j is key (name) of mooring object in anchor i checking if that same mooring object name is attached to platform k + PF = platform # platform object associated with mooring line j and anchor i + break + # attach rB point to platform - # add fairlead point - self.ms.addPoint(1,ssloc[-1].rB) - # add connector info for fairlead point - self.ms.pointList[-1].m = self.ms.lineList[-1].pointList[-1].m - self.ms.pointList[-1].v = self.ms.lineList[-1].pointList[-1].v - self.ms.pointList[-1].CdA = self.ms.lineList[-1].pointList[-1].CdA - # attach the line to point - self.ms.pointList[-1].attachLine(ssloc[-1].number,1) - body.attachPoint(len(self.ms.pointList),[ssloc[-1].rB[0]-PF.r[0],ssloc[-1].rB[1]-PF.r[1],ssloc[-1].rB[2]-PF.r[2]]) # attach to fairlead (need to subtract out location of platform from point for subsystem integration to work correctly) - - - check = np.ones((len(self.mooringList),1)) - # now create and attach any shared lines or hybrid lines attached to buoys - for ii,i in enumerate(self.mooringList): # loop through all lines - ii is index of mooring object in dictionary, i is key (name) of mooring object + if mooring.parallels: # case with paralles/bridles + + # Look at end B object(s) + subcom = mooring.subcomponents[-1] + + if isinstance(subcom, list): # bridle case + for parallel in subcom: + subcom2 = parallel[-1] # end subcomponent of the parallel path + + # Code repetition for the moment: + if isinstance(subcom2, Edge): + r = subcom2.attached_to[1].r # approximate end point...? + point = self.ms.addPoint(1, r) + PF.body.attachPoint(point.number, r-PF.r) + point.attachLine(subcom2.mpLine.number, 1) # attach the subcomponent's line object end B + + elif isinstance(subcom2, Node): + r = subcom2.r # approximate end point...? + pnum = subcom2.mpConn.number + PF.body.attachPoint(pnum, r-PF.r) + + elif isinstance(subcom, Edge): + r = subcom.attached_to[1].r # approximate end point...? + point = self.ms.addPoint(1, r) + PF.body.attachPoint(point.number, r-PF.r) + point.attachLine(subcom.mpLine.number, 1) # attach the subcomponent's line object end B + + elif isinstance(subcom, Node): + r = subcom.r # approximate end point...? + pnum = subcom.mpConn.number + PF.body.attachPoint(pnum, r-PF.r) + # (the section line object(s) should already be attached to this point) + + + else: # normal serial/subsystem case + # add fairlead point + point = self.ms.addPoint(1,ssloc.rB) + # add connector info for fairlead point + # >>> MH: these next few lines might result in double counting <<< + point.m = self.ms.lineList[-1].pointList[-1].m + point.v = self.ms.lineList[-1].pointList[-1].v + point.CdA = self.ms.lineList[-1].pointList[-1].CdA + # attach the line to point + point.attachLine(ssloc.number,1) + PF.body.attachPoint(point.number, ssloc.rB-PF.r) # attach to fairlead (need to subtract out location of platform from point for subsystem integration to work correctly) + + + # Create and attach any shared lines or hybrid lines attached to buoys + for mkey, mooring in self.mooringList.items(): # loop through all lines + check = 1 # temporary approach to identify shared lines <<< for j in self.anchorList: # j is key (name) of anchor object - if i in self.anchorList[j].attachments: # check if line has already been put in ms - check[ii] = 0 - if check[ii] == 1: # mooring object not in any anchor lists + if mkey in self.anchorList[j].attachments: # check if line has already been put in ms + check = 0 + break + if check == 1: # mooring object not in any anchor lists # new shared line # create subsystem for shared line - if hasattr(self.mooringList[i],'shared'): - self.mooringList[i].createSubsystem(case=self.mooringList[i].shared,pristine=pristineLines, mooringSys=self.ms) + if hasattr(mooring, 'shared'): # <<< + mooring.createSubsystem(case=mooring.shared, + pristine=pristineLines, ms=self.ms) else: - self.mooringList[i].createSubsystem(case=1,pristine=pristineLines, mooringSys=self.ms) # we doubled all symmetric lines so any shared lines should be case 1 + mooring.createSubsystem(case=1,pristine=pristineLines, + ms=self.ms) # we doubled all symmetric lines so any shared lines should be case 1 # set location of subsystem for simpler coding if pristineLines: - ssloc = self.mooringList[i].ss + ssloc = mooring.ss else: - ssloc = self.mooringList[i].ss_mod - # add subsystem as a line in moorpy system - self.ms.lineList.append(ssloc) - ssloc.number = len(self.ms.lineList) + ssloc = mooring.ss_mod + + # (ms.lineList.append is now done in Mooring.createSubsystem) # find associated platforms/ buoys - att = self.mooringList[i].attached_to + att = mooring.attached_to # connect line ends to the body/buoy - ends = [ssloc.rA,ssloc.rB] - for ki in range(0,2): - if isinstance(att[ki],Platform): - if att[ki]: - # add fairlead point and attach the line to it - self.ms.addPoint(1,ends[ki]) - self.ms.pointList[-1].attachLine(ssloc.number,ki) - att[ki].body.attachPoint(len(self.ms.pointList),[ends[ki][0]-att[ki].r[0],ends[ki][1]-att[ki].r[1],ends[ki][2]-att[ki].r[2]]) - else: - # this end is unattached - pass - + for ki in range(0,2): # for each end of the mooring + if isinstance(att[ki],Platform): # if it's attached to a platform + + platform = att[ki] + if mooring.parallels: # case with paralles/bridles + + # Look at end object(s) + subcom = mooring.subcomponents[-ki] + + if isinstance(subcom, list): # bridle case + for parallel in subcom: + subcom2 = parallel[-ki] # end subcomponent of the parallel path + + # Code repetition for the moment: + if isinstance(subcom2, Edge): + r = subcom2.attached_to[ki].r # approximate end point...? + point = self.ms.addPoint(1, r) + platform.body.attachPoint(point.number, r-platform.r) + point.attachLine(subcom2.mpLine.number, ki) # attach the subcomponent's line object end + + elif isinstance(subcom2, Node): + r = subcom2.r # approximate end point...? + pnum = subcom2.mpConn.number + platform.body.attachPoint(pnum, r-platform.r) + + elif isinstance(subcom, Edge): + r = subcom.attached_to[ki].r # approximate end point...? + point = self.ms.addPoint(1, r) + platform.body.attachPoint(point.number, r-platform.r) + point.attachLine(subcom.mpLine.number, ki) # attach the subcomponent's line object end + + elif isinstance(subcom, Node): + r = subcom.r # approximate end point...? + pnum = subcom.mpConn.number + platform.body.attachPoint(pnum, r-platform.r) + # (the section line object(s) should already be attached to this point) + + + else: # normal serial/subsystem case + + if ki==0: + rEnd = mooring.rA + else: + rEnd = mooring.rB + + # add fairlead point A and attach the line to it + point = self.ms.addPoint(1, rEnd) + point.attachLine(ssloc.number, ki) + platform.body.attachPoint(point.number, rEnd-platform.r) + + else: + # this end is unattached + pass + + # add in cables if desired if cables: @@ -2912,6 +3245,7 @@ def getRAFT(self,RAFTDict,pristine=1): See RAFT documentation for requirements for each sub-dictionary ''' print('Creating RAFT object') + # create RAFT model if necessary components exist if 'platforms' in RAFTDict or 'platform' in RAFTDict: # set up a dictionary with keys as the table names for each row (ease of use later) @@ -3328,47 +3662,135 @@ def duplicate(self,pf, r=None,heading=None): self.platformList[newid] = pf2 count = 0 - for att in pf.attachments.values(): - if isinstance(att['obj'],Mooring): - if att['end'] == 'a': - endB = 0 - else: - endB = 1 - # grab all info from mooring object - md = deepcopy(att['obj'].dd) - mhead = att['obj'].heading - # detach mooring object from platform - pf2.detach(att['obj'],end=endB) - # create new mooring object - newm = Mooring(dd=md,id=newid+alph[count]) - self.mooringList[newm.id] = newm - newm.heading = mhead - # attach to platform - pf2.attach(newm,end=endB) - # grab info from anchor object and create new one - ad = deepcopy(att['obj'].attached_to[1-endB].dd) - newa = Anchor(dd=ad,id=newid+alph[count]) - self.anchorList[newa.id] = newa - # attach anchor to mooring - newm.attachTo(newa,end=1-endB) - newm.reposition(r_center=r,project=self) - zAnew, nAngle = self.getDepthAtLocation(newm.rA[0], newm.rA[1], return_n=True) - newm.rA[2] = -zAnew - newm.dd['zAnchor'] = -zAnew - newa.r = newm.rA - - count += 1 - - elif isinstance(att['obj'],Turbine): - pf2.detach(att['obj']) - turb = deepcopy(att['obj']) - turb.id = newid+'turb' - self.turbineList[turb.id] = turb - pf2.attach(turb) + # first check for fairlead objects + fairs = True if any([isinstance(att['obj'],Fairlead) for att in pf.attachments.values()]) else False + if fairs: + for att in pf.attachments.values(): + if isinstance(att['obj'],Fairlead): + r_rel = att['r_rel'] + if att['obj'].attachments: + for val in att['obj'].attachments.values(): + moor = val['obj'].part_of + endB = 1 + # grab all info from mooring object + md = deepcopy(moor.dd) + mhead = moor.heading + # detach mooring object from platform + pf2.detach(moor,end=endB) + pf2.detach(att['obj']) + # create new mooring object + newm = Mooring(dd=md,id=newid+alph[count]) + self.mooringList[newm.id] = newm + newm.heading = mhead + # check if fairlead + # for con in newm.subcons_B: + # if + # attach to platform + fl = self.addFairlead(platform=pf2,r_rel=r_rel,mooring=newm,id=att['obj'].id) + # grab info from anchor object and create new one + ad = deepcopy(moor.attached_to[1-endB].dd) + newa = Anchor(dd=ad,id=newid+alph[count]) + self.anchorList[newa.id] = newa + # attach anchor to mooring + newm.attachTo(newa,end=1-endB) + pf2.setPosition(r,heading=heading,project=self) + zAnew, nAngle = self.getDepthAtLocation(newm.rA[0], newm.rA[1], return_n=True) + newm.rA[2] = -zAnew + newm.dd['zAnchor'] = -zAnew + newa.r = newm.rA + + count += 1 + + else: + moor=None + + + # for att in pf.attachments.values(): + # if isinstance(att['obj'],Mooring): + # if att['end'] == 'a': + # endB = 0 + # else: + # endB = 1 + # # grab all info from mooring object + # md = deepcopy(att['obj'].dd) + # mhead = att['obj'].heading + # # detach mooring object from platform + # pf2.detach(att['obj'],end=endB) + # # create new mooring object + # newm = Mooring(dd=md,id=newid+alph[count]) + # self.mooringList[newm.id] = newm + # newm.heading = mhead + # # check if fairlead + # # for con in newm.subcons_B: + # # if + # # attach to platform + # pf2.attach(newm,end=endB) + # # grab info from anchor object and create new one + # ad = deepcopy(att['obj'].attached_to[1-endB].dd) + # newa = Anchor(dd=ad,id=newid+alph[count]) + # self.anchorList[newa.id] = newa + # # attach anchor to mooring + # newm.attachTo(newa,end=1-endB) + # newm.reposition(r_center=r,project=self) + # zAnew, nAngle = self.getDepthAtLocation(newm.rA[0], newm.rA[1], return_n=True) + # newm.rA[2] = -zAnew + # newm.dd['zAnchor'] = -zAnew + # newa.r = newm.rA + + # count += 1 + + elif isinstance(att['obj'],Turbine): + pf2.detach(att['obj']) + turb = deepcopy(att['obj']) + turb.id = newid+'turb' + self.turbineList[turb.id] = turb + pf2.attach(turb) + + elif isinstance(att['obj'],Cable): + # could be cable, just detach for now + pf2.detach(att['obj'],att['end']) + else: + for att in pf.attachments.values(): + if isinstance(att['obj'],Mooring): + if att['end'] == 'a': + endB = 0 + else: + endB = 1 + # grab all info from mooring object + md = deepcopy(att['obj'].dd) + mhead = att['obj'].heading + # detach mooring object from platform + pf2.detach(att['obj'],end=endB) + # create new mooring object + newm = Mooring(dd=md,id=newid+alph[count]) + self.mooringList[newm.id] = newm + newm.heading = mhead + pf2.attach(newm,end=endB) + # grab info from anchor object and create new one + ad = deepcopy(att['obj'].attached_to[1-endB].dd) + newa = Anchor(dd=ad,id=newid+alph[count]) + self.anchorList[newa.id] = newa + # attach anchor to mooring + newm.attachTo(newa,end=1-endB) + newm.reposition(r_center=r,project=self) + zAnew, nAngle = self.getDepthAtLocation(newm.rA[0], newm.rA[1], return_n=True) + newm.rA[2] = -zAnew + newm.dd['zAnchor'] = -zAnew + newa.r = newm.rA + + count += 1 + + elif isinstance(att['obj'],Turbine): + pf2.detach(att['obj']) + turb = deepcopy(att['obj']) + turb.id = newid+'turb' + self.turbineList[turb.id] = turb + pf2.attach(turb) + + elif isinstance(att['obj'],Cable): + # could be cable, just detach for now + pf2.detach(att['obj'],att['end']) - else: - # could be cable, just detach for now - pf2.detach(att['obj'],att['end']) # reposition platform as needed pf2.setPosition(r,heading=heading,project=self) @@ -3419,7 +3841,7 @@ def addPlatformMS(self,ms,r=[0,0,0]): alph = list(string.ascii_lowercase) for point in ms.bodyList[0].attachedP: for j,line in enumerate(ms.pointList[point-1].attached): - md = {'sections':[],'connectors':[]} # start set up of mooring design dictionary + md = {'subcomponents':[]} # start set up of mooring design dictionary rA = ms.lineList[line-1].rA rB = ms.lineList[line-1].rB pfloc = ms.bodyList[0].r6 @@ -3442,53 +3864,18 @@ def addPlatformMS(self,ms,r=[0,0,0]): md['zAnchor'] = -self.getDepthAtLocation(rA[0],rA[1]) else: md['zAnchor'] = -self.getDepthAtLocation(rB[0],rB[1]) - - # # add section and connector info - # md['sections'].append({'type':line.type}) - # md['sections'][-1]['L'] = line.L - # md['connectors'].append({'m':point.m,'v':point.v,'Ca':point.Ca,'CdA':point.CdA}) - - # anline = True - # for pt in ms.pointList: - # if line in pt.attached and pt != point: - # n_att = len(pt.attached) - # nextloc = np.where([x!=line for x in pt.attached])[0][0] - # if n_att == 1: - # # this is the anchor point - # ad = {'design':{}} - # ad['design']['m'] = pt.m - # ad['design']['v'] = pt.v - # ad['design']['CdA'] = pt.CdA - # ad['design']['Ca'] = pt.Ca - # if 'anchor_type' in pt.entity: - # ad['type'] = pt.entity['anchor_type'] - # self.anchorList[mList[-1].id] = Anchor(dd=ad,r=pt.r,id=mList[-1].id) - # self.anchorList[mList[-1].id].attach(mList[-1],end=1-endB[-1]) - # # reposition mooring and anchor - # mList[-1].reposition(r_center=r) - # zAnew = self.getDepthAtLocation(mList[-1].rA[0], - # mList[-1].rA[1]) - # mList[-1].rA[2] = -zAnew - # mList[-1].dd['zAnchor'] = -zAnew - # self.anchorList[mList[-1].id].r = mList[-1].rA - # anline = False - # else: - # # add section and connector info - # md['sections'].append({'type':sline.type}) - # md['sections'][-1]['L'] = sline.L - # spt = ms.lineList[line-1].pointList[k] - # md['connectors'].append({'m':spt.m,'v':spt.v,'Ca':spt.Ca,'CdA':spt.CdA}) for k,sline in enumerate(ms.lineList[line-1].lineList): # add section and connector info - md['sections'].append({'type':sline.type}) - md['sections'][-1]['L'] = sline.L spt = ms.lineList[line-1].pointList[k] - md['connectors'].append({'m':spt.m,'v':spt.v,'Ca':spt.Ca,'CdA':spt.CdA}) + md['subcomponents'].append({'m':spt.m,'v':spt.v,'Ca':spt.Ca,'CdA':spt.CdA}) + md['subcomponents'].append({'type':sline.type}) + md['subcomponents'][-1]['L'] = sline.L + spt = ms.lineList[line-1].pointList[k+1] - md['connectors'].append({'m':spt.m,'v':spt.v,'Ca':spt.Ca,'CdA':spt.CdA}) + md['subcomponents'].append({'m':spt.m,'v':spt.v,'Ca':spt.Ca,'CdA':spt.CdA}) mhead.append(90 - np.degrees(np.arctan2(vals[1],vals[0]))) mList.append(Mooring(dd=md,id=pfid+alph[count])) mList[-1].heading = mhead[-1] @@ -3595,7 +3982,7 @@ def addPlatformConfig(self,configDict,r=[0,0]): # create mooring objects for i in range(len(pfinfo['mooring_headings'])): head = pfinfo['mooring_headings'][i]+pfinfo['platform_heading'] - md = {'span':minfo['span'],'sections':[],'connectors':[]} + md = {'span':minfo['span'],'subcomponents':[]} def arrayWatchCircle(self,plot=False, ang_spacing=45, RNAheight=150, shapes=True,thrust=1.95e6,SFs=True,moor_envelopes=True, @@ -3635,20 +4022,22 @@ def arrayWatchCircle(self,plot=False, ang_spacing=45, RNAheight=150, dictionary of safety factors for mooring line tensions for each turbine ''' - + # get angles to iterate over angs = np.arange(0,360+ang_spacing,ang_spacing) n_angs = len(angs) - # lists to save info in - minSag = [None]*len(self.cableList) - minCurvSF = [None]*len(self.cableList) - CminTenSF = [None]*len(self.cableList) + # lists to save info in minTenSF = [None]*len(self.mooringList) + CminTenSF = [None]*len(self.cableList) + minCurvSF = [None]*len(self.cableList) F = [None]*len(self.anchorList) x = np.zeros((len(self.platformList),n_angs)) y = np.zeros((len(self.platformList),n_angs)) + info = {'analysisType': 'quasi-static (MoorPy)', + 'info': f'determined from arrayWatchCircle() with DAF of {DAF}'} + lBots = np.zeros(len(self.mooringList)) # initialize for maximum laid length per mooring if not self.ms: self.getMoorPyArray() @@ -3671,82 +4060,45 @@ def arrayWatchCircle(self,plot=False, ang_spacing=45, RNAheight=150, if SFs: # get loads on anchors (may be shared) for j,anch in enumerate(self.anchorList.values()): - atts = [att['obj'] for att in anch.attachments.values()] - F1 = [None]*len(atts) - for jj,moor in enumerate(atts): - if isinstance(moor.attached_to[0],Anchor): - # anchor attached to end A - F1[jj] = moor.ss.fA*DAF - else: - F1[jj] = moor.ss.fB*DAF - # add up all tensions on anchor in each direction (x,y,z) - F2 = [sum([a[0] for a in F1]),sum([a[1] for a in F1]),sum([a[2] for a in F1])] + F2 = anch.mpAnchor.getForces()*DAF # add up all forces on anchor body H = np.hypot(F2[0],F2[1]) # horizontal force T = np.sqrt(F2[0]**2+F2[1]**2+F2[2]**2) # total tension force - if not F[j] or T>np.sqrt(F[j][0]**2+F[j][1]**2+F[j][2]**2): + if F[j] is None or T>np.sqrt(F[j][0]**2+F[j][1]**2+F[j][2]**2): F[j] = F2 # max load on anchor # save anchor load information anch.loads['Hm'] = H anch.loads['Vm'] = F[j][2] anch.loads['thetam'] = np.degrees(np.arctan(anch.loads['Vm']/anch.loads['Hm'])) #[deg] anch.loads['mudline_load_type'] = 'max' - anch.loads['info'] = f'determined from arrayWatchCircle() with DAF of {DAF}' + anch.loads.update(info) # get tensions on mooring line for j, moor in enumerate(self.mooringList.values()): - MBLA = float(moor.ss.lineList[0].type['MBL']) - MBLB = float(moor.ss.lineList[-1].type['MBL']) - # print(MBLA,MBLB,moor.ss.TA,moor.ss.TB,MBLA/moor.ss.TA,MBLB/moor.ss.TB,abs(MBLA/moor.ss.TA),abs(MBLB/moor.ss.TB)) - MTSF = min([abs(MBLA/(moor.ss.TA*DAF)),abs(MBLB/(moor.ss.TB*DAF))]) - # atenMax[j], btenMax[j] = moor.updateTensions() - if not minTenSF[j] or minTenSF[j]>MTSF: - minTenSF[j] = deepcopy(MTSF) - moor.loads['TAmax'] = moor.ss.TA*DAF - moor.loads['TBmax'] = moor.ss.TB*DAF - moor.loads['info'] = f'determined from arrayWatchCircle() with DAF of {DAF}' - moor.safety_factors['tension'] = minTenSF[j] - moor.safety_factors['analysisType'] = 'quasi-static (MoorPy)' - - # store max. laid length of the mooring lines + lBot = 0 + moor.updateTensions(DAF=DAF) + moor.updateSafetyFactors(info=info) if moor_seabed_disturbance: - lBot = 0 - for line in moor.ss.lineList: - lBot += line.LBot + for sec in moor.sections(): + lBot += sec.mpLine.LBot lBots[j] = max(lBots[j], lBot) + # get tensions and curvature on cables for j,cab in enumerate(self.cableList.values()): dcs = [a for a in cab.subcomponents if isinstance(a,DynamicCable)] # dynamic cables in this cable - ndc = len(dcs) # number of dynamic cable objects in this single cable object - CminTenSF[j] = [None]*ndc - minCurvSF[j] = [None]*ndc - minSag[j] = [None]*ndc + cab.updateTensions(DAF=DAF) + cab.updateSafetyFactors(info=info) + minCurvSF[j] = [None]*len(dcs) + CminTenSF[j] = [None]*len(dcs) if dcs[0].ss: - for jj,dc in enumerate(dcs): - MBLA = dc.ss.lineList[0].type['MBL'] - MBLB = dc.ss.lineList[-1].type['MBL'] - CMTSF = min([abs(MBLA/dc.ss.TA),abs(MBLB/dc.ss.TB)]) - if not CminTenSF[j][jj] or CminTenSF[j][jj]>CMTSF: - CminTenSF[j][jj] = deepcopy(CMTSF) - dc.loads['TAmax'] = dc.ss.TA*DAF - dc.loads['TBmax'] = dc.ss.TB*DAF - dc.loads['info'] = f'determined from arrayWatchCircle() with DAF of {DAF}' - dc.safety_factors['tension'] = CminTenSF[j][jj] - # CatenMax[j], CbtenMax[j] = cab.updateTensions() + + for jj,dc in enumerate(dcs): + CminTenSF[j][jj] = dc.safety_factors['tension'] dc.ss.calcCurvature() mCSF = dc.ss.getMinCurvSF() if not minCurvSF[j][jj] or minCurvSF[j][jj]>mCSF: minCurvSF[j][jj] = mCSF dc.safety_factors['curvature'] = minCurvSF[j][jj] - # # determine number of buoyancy sections - # nb = len(dc.dd['buoyancy_sections']) - # m_s = [] - # for k in range(0,nb): - # m_s.append(dc.ss.getSag(2*k)) - # mS = min(m_s) - # if not minSag[j][jj] or minSag[j][jj]1 for at in atts[is_anch]]): # we have a shared anchor here, put mooring in array_mooring - headA = 'None' # no heading at end A because it's an anchor - # append mooring line to array_moor section - arrayMoor.append([current_config, atts[0].id, atts[1].id, headA,headB,int(0)]) + if fairleads: + # append mooring line to array_moor section + arrayMoor.append([current_config, + atts[0].id, + atts[1].id, + 'None', + flB]) + else: + # append mooring line to array_moor section + arrayMoor.append([current_config, + atts[0].id, + atts[1].id]) else: # not shared anchor or shared mooring, add line to mooring system - msys.append([current_config, - np.round(headB,2), - mapAnchNames[atts[is_anch][0].id], - 0]) + if fairleads: + msys.append([current_config, + np.round(headB,2), + mapAnchNames[atts[is_anch][0].id], + flB]) + else: + msys.append([current_config, + np.round(headB,2), + mapAnchNames[atts[is_anch][0].id]]) # check if an existing mooring system matches the current if len(msys)>0: @@ -4004,19 +4406,7 @@ def unload(self,file='project.yaml'): else: mname = 0 - if not 'type' in pf.dd: - pf_type_info = [pf.rFair, pf.zFair, pf.entity] - if not pf_type_info in pf_types: - pf_types.append(pf_type_info) - if not self.platformTypes: - self.platformTypes = [] - pf.dd['type'] = len(self.platformTypes) - self.platformTypes.append({'rFair': pf.rFair, - 'zFair': pf.zFair, - 'type': pf.entity}) - else: - tt = [n for n,ps in enumerate(pf_types) if ps==pf_type_info] - pf.dd['type'] = tt[0] + @@ -4062,12 +4452,12 @@ def unload(self,file='project.yaml'): # # build out mooring and anchor sections anchKeys = ['ID','type','x','y','embedment'] - lineKeys = ['MooringConfigID','endA','endB','headingA','headingB','lengthAdjust'] + lineKeys = ['MooringConfigID','endA','endB','fairleadA','fairleadB'] - msyskeys = ['MooringConfigID','heading','anchorType','lengthAdjust'] + msyskeys = ['MooringConfigID','heading','anchorType','fairlead'] moor_systems = {} for name,sys in mscs.items(): - moor_systems[name] = {'keys':msyskeys, + moor_systems[name] = {'keys':msyskeys[:len(sys[0])], 'data':sys} # set up mooring configs, connector and section types dictionaries @@ -4078,48 +4468,72 @@ def unload(self,file='project.yaml'): sUnique = [] for j,conf in enumerate(allconfigs): sections = [] - # iterate through sections - for i in range(len(conf['sections'])): - # add connector if it isn't empty - if not conf['connectors'][i]['m'] == 0 or not conf['connectors'][i]['CdA'] == 0 or not conf['connectors'][i]['v'] == 0: - # this is not an empty connector - if not 'type' in conf['connectors'][i]: - # make a new connector type - connTypes[str(int(len(connTypes)))] = dict(conf['connectors'][i]) - ctn = str(int(len(connTypes)-1)) # connector type name - else: - ctn = str(conf['connectors'][i]['type']) - connTypes[ctn] = dict(conf['connectors'][i]) - - sections.append({'connectorType':ctn}) - # add section info - stm = conf['sections'][i]['type']['material'] # section type material - stw = conf['sections'][i]['type']['w'] # section type weight - - sKey = (stm, stw) - if sKey not in sUnique: - sUnique.append(sKey) - conf['sections'][i]['type']['name'] = sIdx - stn = conf['sections'][i]['type']['name'] # section type name - secTypes[stn] = dict(conf['sections'][i]['type']) - #secTypes[stn] = cleanDataTypes(secTypes[stn]) - sIdx += 1 - - stn = sUnique.index(sKey) - sections.append({'type':stn,'length':float(conf['sections'][i]['L'])}) - - # add last connector if needed - if not conf['connectors'][i+1]['m'] == 0 or not conf['connectors'][i+1]['CdA'] == 0 or not conf['connectors'][i+1]['v'] == 0: - # this is not an empty connector - if not 'type' in conf['connectors'][i+1]: - # make a new connector type - #conf['connectors'][i+1] = cleanDataTypes(conf['connectors'][i+1]) - connTypes[str(len(connTypes))] = conf['connectors'][i+1] - ctn = str(int(len(connTypes)-1)) + # iterate through subcomponents + for comp in conf['subcomponents']: + if isinstance(comp,list): + sections.append({'subsections':[]}) + for subcomp in comp: + if isinstance(subcomp,list): + sections[-1]['subsections'].append([]) + for sc in subcomp: + if 'L' in sc: + # add section info + stm = sc['type']['material'] # section type material + stw = sc['type']['w'] # section type weight + + sKey = (stm, stw) + if sKey not in sUnique: + sUnique.append(sKey) + sc['type']['name'] = sIdx + stn = sc['type']['name'] # section type name + secTypes[stn] = dict(sc['type']) + sIdx += 1 + + stn = sUnique.index(sKey) + sections[-1]['subsections'][-1].append({'type':stn,'length':float(sc['L'])}) + else: + if not sc['m'] == 0 or not sc['CdA'] == 0 or not sc['v'] == 0: + # this is not an empty connector + if not 'type' in sc: + # make a new connector type + connTypes[str(int(len(connTypes)))] = dict(sc) + ctn = str(int(len(connTypes)-1)) # connector type name + else: + ctn = str(sc['type']) + connTypes[ctn] = dict(sc) + sections[-1]['subsections'][-1].append({'connectorType':ctn}) + else: - ctn = conf['connectors'][i+1]['type'] - connTypes[ctn] = dict(conf['connectors'][i+1]) - sections.append({'connectorType':ctn}) + if 'L' in comp: + # add section info + stm = comp['type']['material'] # section type material + stw = comp['type']['w'] # section type weight + + sKey = (stm, stw) + if sKey not in sUnique: + sUnique.append(sKey) + comp['type']['name'] = sIdx + stn = comp['type']['name'] # section type name + secTypes[stn] = dict(comp['type']) + sIdx += 1 + + stn = sUnique.index(sKey) + sections.append({'type':stn,'length':float(comp['L'])}) + else: + # add connector if it isn't empty + if not comp['m'] == 0 or not comp['CdA'] == 0 or not comp['v'] == 0: + # this is not an empty connector + if not 'type' in comp: + # make a new connector type + connTypes[str(int(len(connTypes)))] = dict(comp) + ctn = str(int(len(connTypes)-1)) # connector type name + else: + ctn = str(comp['type']) + connTypes[ctn] = dict(comp) + + sections.append({'connectorType':ctn}) + + # put mooring config dictionary together mooringConfigs[str(j)] = {'name':str(j),'span':float(conf['span']),'sections':sections} @@ -4144,6 +4558,10 @@ def unload(self,file='project.yaml'): statcab = 'None' dynCabs = [None,None] burial = None + jA = None + jB = None + jtubesA = [att['obj'].id for att in endA.attachments.values() if isinstance(att['obj'], Jtube)] + jtubesB = [att['obj'].id for att in endB.attachments.values() if isinstance(att['obj'], Jtube)] for kk,sub in enumerate(cab.subcomponents): currentConfig = {} @@ -4187,6 +4605,15 @@ def unload(self,file='project.yaml'): elif isinstance(sub,DynamicCable): + jtube = [att.id for att in sub.attached_to if isinstance(att,Jtube)] + # grab index of fairlead list from end B + + for jj in jtube: + if jj.attached_to == endA: + jA = jtubesA.index(jj)+1 + + elif jj.attached_to == endB: + jB = jtubesB.index(jj)+1 # pull out cable config and compare it to existing cableConfigs ct = sub.dd['type'] # static or dynamic ctw = sub.dd['cable_type']['w'] @@ -4252,9 +4679,11 @@ def unload(self,file='project.yaml'): jtn = 'joint_'+str(jIdx) bs.append({'type':jtn}) # create current cable config dictionary - currentConfig = {ctk:ctn,'A':ctA,'rJTube':sub.dd['rJTube'], + currentConfig = {ctk:ctn,'A':ctA, 'span':sub.dd['span'],'length':sub.L, 'voltage':sub.dd['cable_type']['voltage'],'sections':bs} + if 'rJTube' in sub.dd: + currentConfig['rJTube'] = sub.dd['rJTube'] # check if current cable config already exists in cable configs dictionary if currentConfig in cableConfigs.values(): ccn = [key for key,val in cableConfigs.items() if val==currentConfig][0] # get cable config key @@ -4275,9 +4704,13 @@ def unload(self,file='project.yaml'): endAdict = {'attachID':endA.id, 'heading':headA, 'dynamicID':dynCabs[0] if dynCabs[0] else 'None'} + if jA: + endAdict['JTube'] = jA endBdict = {'attachID':endB.id, 'heading':headB, 'dynamicID':dynCabs[1] if dynCabs[1] else 'None'} + if jB: + endBdict['JTube'] = jB cables.append({'name':cid,'endA':endAdict,'endB':endBdict,'type':statcab}) @@ -4293,11 +4726,15 @@ def unload(self,file='project.yaml'): # create master output dictionary for yaml + if arrayMoor: + arrayMooring = {'anchor_keys':anchKeys, 'anchor_data':arrayAnch, + 'line_keys':lineKeys[:len(arrayMoor[0])], 'line_data':arrayMoor} + else: + arrayMooring = {} output = {'site':site, 'array':{'keys':arrayKeys,'data':arrayData}, pfkey:pfTypes, 'topsides': topList, - 'array_mooring':{'anchor_keys':anchKeys, 'anchor_data':arrayAnch, - 'line_keys':lineKeys, 'line_data':arrayMoor}, + 'array_mooring':arrayMooring, 'mooring_systems':moor_systems, 'mooring_line_configs':mooringConfigs, 'mooring_line_types':secTypes, @@ -5006,7 +5443,7 @@ def style_it(sheet, row, col_start, col_end, fill_color="FFFF00"): ''' if __name__ == '__main__': - + ''' project = Project() project.loadSoil(filename='tests/soil_sample.txt') # create project class instance from yaml file @@ -5022,4 +5459,25 @@ def style_it(sheet, row, col_start, col_end, fill_color="FFFF00"): project.plot2d(plot_boundary=False) # this should also plot the watch circles/envelopes! + ''' + + + # point to location of yaml file with uniform array info + filename = '../Examples/OntologySample600m_shared.yaml' # yaml file for project + + # load in yaml + project = Project(file=filename,raft=False) + + + project.getMoorPyArray() + + # plot in 2d and 3d + #project.plot2d() + #project.plot3d(fowt=True) + + #plt.show() + + + # ---- + plt.show() \ No newline at end of file diff --git a/famodel/turbine/turbine.py b/famodel/turbine/turbine.py index 6267aec2..00138376 100644 --- a/famodel/turbine/turbine.py +++ b/famodel/turbine/turbine.py @@ -117,4 +117,3 @@ def getForces(self, yaw, pitch=0): self.grid_pitch, self.grid_f6) return f6 - \ No newline at end of file diff --git a/tests/mooring_ontology.yaml b/tests/mooring_ontology.yaml new file mode 100644 index 00000000..aa06dd1d --- /dev/null +++ b/tests/mooring_ontology.yaml @@ -0,0 +1,158 @@ +type: draft/example of floating array ontology under construction +name: +comments: +# Site condition information +site: + general: + water_depth : 600 # [m] uniform water depth + rho_water : 1025.0 # [kg/m^3] water density + rho_air : 1.225 # [kg/m^3] air density + mu_air : 1.81e-05 # air dynamic viscosity + #... + + +# ----- Array-level inputs ----- + +# Wind turbine array layout +array: + keys : [ID, topsideID, platformID, mooringID, x_location, y_location, heading_adjust] + data : # ID# ID# ID# [m] [m] [deg] + - [FOWT1, 0, 1, ms3, 0, 0, 180 ] # 2 array, shared moorings + - [FOWT2, 0, 1, ms2, 1600, 0, 0 ] + - [FOWT3, 0, 1, ms1, 0, 1656, 180 ] + - [FOWT4, 0, 1, ms4, 1600, 1600, 180] + + + +# Array-level mooring system (in addition to any per-turbine entries later) +array_mooring: + anchor_keys : + [ID, type, x, y, embedment ] + anchor_data : + - [ Anch1, suction_pile1, -828 , 828 , 2 ] + + line_keys : + [MooringConfigID , endA, endB, fairleadA, fairleadB] + line_data : + - [ rope_shared , FOWT1, FOWT2, 3, 3] + - [ rope_1 , Anch1, FOWT1, NONE, 2, NONE] + - [ rope_1 , Anch1, FOWT3, NONE, 1, NONE] + +platforms: + + - fairleads : + # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [58,0,-14] + headings: [30, 150, 270] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + type : FOWT + + +# ----- Mooring system ----- + +# Mooring system descriptions (each for an individual FOWT with no sharing) +mooring_systems: + + ms1: + name: 3-line semi-taut polyester mooring system with one line shared anchor + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 270 , suction_pile1, 3 ] + - [ rope_1, 135 , suction_pile1, 2 ] + + ms2: + name: 2-line semitaut with a third shared line + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 45 , suction_pile1, 1 ] + - [ rope_1, 135 , suction_pile1, 2 ] + + ms3: + name: 3-line semi-taut polyester mooring system with one line shared anchor and one shared line + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 45 , suction_pile1, 1 ] + + ms4: + name: 3 line taut poly mooring system + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 45 , suction_pile1, 1 ] + - [ rope_1, 135 , suction_pile1, 2 ] + - [ rope_1, 270 , suction_pile1, 3 ] + + +# Mooring line configurations +mooring_line_configs: + + rope_1: # mooring line configuration identifier + + name: rope configuration 1 # descriptive name + + span: 1131.37 + + + sections: #in order from anchor to fairlead + - type: chain_155mm + length: 20 + - type: rope # ID of a mooring line section type + length: 1170 # [m] usntretched length of line section + adjustable: True # flags that this section could be adjusted to accommodate different spacings... + + rope_shared: + name: shared rope + + span: 1484 + + + sections: + - type: rope + length: 150 + - connectorType: clump_weight_80 + - type: rope + length: 1172 + - connectorType: clump_weight_80 + - type: rope + length: 150 + + +# Mooring line cross-sectional properties +mooring_line_types: + + rope: + d_nom: 0.2246 + d_vol: 0.1797 + m: 34.85 + EA: 4.761e7 + MBL: 11.75e6 + material: rope + + chain_155mm: + d_nom: 0.155 # [m] nominal diameter + d_vol: 0.279 # [m] volume-equivalent diameter + m: 480.9 # [kg/m] mass per unit length (linear density) + EA: 2058e6 # [N] quasi-static stiffness + MBL: 25.2e6 # [N] minimum breaking load + cost: 1486 # [$/m] cost per unit length + material: chain # [-] material composition descriptor + material details: R3 studless + +# Mooring connector properties +mooring_connector_types: + + clump_weight_80: + m : 80000 # [kg] + v : 0.0 # [m^3] + + +# Anchor type properties +anchor_types: + suction_pile1: + type : suction_pile + L : 16.4 # length of pile [m] + D : 5.45 # diameter of pile [m] + zlug : 9.32 # embedded depth of padeye [m] \ No newline at end of file diff --git a/tests/mooring_ontology_parallels.yaml b/tests/mooring_ontology_parallels.yaml new file mode 100644 index 00000000..e98647ae --- /dev/null +++ b/tests/mooring_ontology_parallels.yaml @@ -0,0 +1,176 @@ +type: draft/example of floating array ontology under construction +name: +comments: +# Site condition information +site: + general: + water_depth : 600 # [m] uniform water depth + rho_water : 1025.0 # [kg/m^3] water density + rho_air : 1.225 # [kg/m^3] air density + mu_air : 1.81e-05 # air dynamic viscosity + #... + + +# ----- Array-level inputs ----- + +# Wind turbine array layout +array: + keys : [ID, topsideID, platformID, mooringID, x_location, y_location, heading_adjust] + data : # ID# ID# ID# [m] [m] [deg] + - [FOWT1, 0, 1, ms3, 0, 0, 180 ] # 2 array, shared moorings + - [FOWT2, 0, 1, ms2, 1600, 0, 0 ] + - [FOWT3, 0, 1, ms1, 0, 1656, 180 ] + - [FOWT4, 0, 1, ms4, 1600, 1600, 180] + + + +# Array-level mooring system (in addition to any per-turbine entries later) +array_mooring: + anchor_keys : + [ID, type, x, y, embedment ] + anchor_data : + - [ Anch1, suction_pile1, -828 , 828 , 2 ] + + line_keys : + [MooringConfigID , endA, endB, fairleadA, fairleadB] + line_data : + - [ rope_shared , FOWT1, FOWT2, 3, 3] + - [ rope_1 , Anch1, FOWT1, NONE, 2, NONE] + - [ rope_1 , Anch1, FOWT3, NONE, 1, NONE] + +platforms: + + - fairleads : + # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [58,0,-14] + headings: [30, 150, 270, 25, 35] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + + type : FOWT + + +# ----- Mooring system ----- + +# Mooring system descriptions (each for an individual FOWT with no sharing) +mooring_systems: + + ms1: + name: 3-line semi-taut polyester mooring system with one line shared anchor + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 270 , suction_pile1, 3 ] + - [ rope_1, 135 , suction_pile1, 2 ] + + ms2: + name: 2-line semitaut with a third shared line + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1_bridle, 45 , suction_pile1, [4,5] ] + - [ rope_1, 135 , suction_pile1, 2 ] + + ms3: + name: 3-line semi-taut polyester mooring system with one line shared anchor and one shared line + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 45 , suction_pile1, 1 ] + + ms4: + name: 3 line taut poly mooring system + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 45 , suction_pile1, 1 ] + - [ rope_1, 135 , suction_pile1, 2 ] + - [ rope_1, 270 , suction_pile1, 3 ] + + +# Mooring line configurations +mooring_line_configs: + + rope_1_bridle: # mooring line configuration identifier + + name: rope configuration 1 # descriptive name + + span: 1131.37 + + + sections: #in order from anchor to fairlead + - type: rope # ID of a mooring line section type + length: 1170 # [m] usntretched length of line section + adjustable: True # flags that this section could be adjusted to accommodate different spacings... + - subsections: + - - type: chain_155mm + length: 20 + - - type: chain_155mm + length: 20 + + rope_1: # mooring line configuration identifier + + name: rope configuration 1 # descriptive name + + span: 1131.37 + + + sections: #in order from anchor to fairlead + - type: chain_155mm + length: 20 + - type: rope # ID of a mooring line section type + length: 1170 # [m] usntretched length of line section + adjustable: True # flags that this section could be adjusted to accommodate different spacings... + + rope_shared: + name: shared rope + + span: 1484 + + + sections: + - type: rope + length: 150 + - connectorType: clump_weight_80 + - type: rope + length: 1172 + - connectorType: clump_weight_80 + - type: rope + length: 150 + + +# Mooring line cross-sectional properties +mooring_line_types: + + rope: + d_nom: 0.2246 + d_vol: 0.1797 + m: 34.85 + EA: 4.761e7 + MBL: 11.75e6 + material: rope + + chain_155mm: + d_nom: 0.155 # [m] nominal diameter + d_vol: 0.279 # [m] volume-equivalent diameter + m: 480.9 # [kg/m] mass per unit length (linear density) + EA: 2058e6 # [N] quasi-static stiffness + MBL: 25.2e6 # [N] minimum breaking load + cost: 1486 # [$/m] cost per unit length + material: chain # [-] material composition descriptor + material details: R3 studless + +# Mooring connector properties +mooring_connector_types: + + clump_weight_80: + m : 80000 # [kg] + v : 0.0 # [m^3] + + +# Anchor type properties +anchor_types: + suction_pile1: + type : suction_pile + L : 16.4 # length of pile [m] + D : 5.45 # diameter of pile [m] + zlug : 9.32 # embedded depth of padeye [m] \ No newline at end of file diff --git a/tests/platform_ontology.yaml b/tests/platform_ontology.yaml new file mode 100644 index 00000000..0dae6a53 --- /dev/null +++ b/tests/platform_ontology.yaml @@ -0,0 +1,1245 @@ +type: draft/example of floating array ontology under construction +name: +comments: +# Site condition information +site: + general: + water_depth : 600 # [m] uniform water depth + rho_water : 1025.0 # [kg/m^3] water density + rho_air : 1.225 # [kg/m^3] air density + mu_air : 1.81e-05 # air dynamic viscosity + #... + +# ----- Array-level inputs ----- + +# Wind turbine array layout +array: + keys : [ID, topsideID, platformID, mooringID, x_location, y_location, heading_adjust] + data : # ID# ID# ID# [m] [m] [deg] + - [FOWT1, 0, 1, 0, 0, 0, 180 ] # 2 array, shared moorings + - [FOWT2, 1, 1, ms1, 1600, 0, 0 ] + + + +# ----- turbines and platforms ----- + +topsides: + + - type : Turbine + mRNA : 991000 # [kg] RNA mass + IxRNA : 0 # [kg-m2] RNA moment of inertia about local x axis (assumed to be identical to rotor axis for now, as approx) [kg-m^2] + IrRNA : 0 # [kg-m2] RNA moment of inertia about local y or z axes [kg-m^2] + xCG_RNA : 0 # [m] x location of RNA center of mass [m] (Actual is ~= -0.27 m) + hHub : 150.0 # [m] hub height above water line [m] + Fthrust : 1500.0E3 # [N] temporary thrust force to use + + I_drivetrain: 318628138.0 # full rotor + drivetrain inertia as felt on the high-speed shaft + + nBlades : 3 # number of blades + Zhub : 150.0 # hub height [m] + Rhub : 3.97 # hub radius [m] + precone : 4.0 # [deg] + shaft_tilt : 6.0 # [deg] + overhang : -12.0313 # [m] + aeroServoMod : 2 # 0 aerodynamics off; 1 aerodynamics on (no control); 2 aerodynamics and control on + + blade: + precurveTip : -3.9999999999999964 # + presweepTip : 0.0 # + Rtip : 120.96999999936446 # rotor radius + + # r chord theta precurve presweep + geometry: + - [ 8.004, 5.228, 15.474, 0.035, 0.000 ] + - [ 12.039, 5.321, 14.692, 0.084, 0.000 ] + - [ 16.073, 5.458, 13.330, 0.139, 0.000 ] + - [ 20.108, 5.602, 11.644, 0.192, 0.000 ] + - [ 24.142, 5.718, 9.927, 0.232, 0.000 ] + - [ 28.177, 5.767, 8.438, 0.250, 0.000 ] + - [ 32.211, 5.713, 7.301, 0.250, 0.000 ] + - [ 36.246, 5.536, 6.232, 0.246, 0.000 ] + - [ 40.280, 5.291, 5.230, 0.240, 0.000 ] + - [ 44.315, 5.035, 4.348, 0.233, 0.000 ] + - [ 48.349, 4.815, 3.606, 0.218, 0.000 ] + - [ 52.384, 4.623, 2.978, 0.178, 0.000 ] + - [ 56.418, 4.432, 2.423, 0.100, 0.000 ] + - [ 60.453, 4.245, 1.924, 0.000, 0.000 ] + - [ 64.487, 4.065, 1.467, -0.112, 0.000 ] + - [ 68.522, 3.896, 1.056, -0.244, 0.000 ] + - [ 72.556, 3.735, 0.692, -0.415, 0.000 ] + - [ 76.591, 3.579, 0.355, -0.620, 0.000 ] + - [ 80.625, 3.425, 0.019, -0.846, 0.000 ] + - [ 84.660, 3.268, -0.358, -1.080, 0.000 ] + - [ 88.694, 3.112, -0.834, -1.330, 0.000 ] + - [ 92.729, 2.957, -1.374, -1.602, 0.000 ] + - [ 96.763, 2.800, -1.848, -1.895, 0.000 ] + - [ 100.798, 2.637, -2.136, -2.202, 0.000 ] + - [ 104.832, 2.464, -2.172, -2.523, 0.000 ] + - [ 108.867, 2.283, -2.108, -2.864, 0.000 ] + - [ 112.901, 2.096, -1.953, -3.224, 0.000 ] + - [ 116.936, 1.902, -1.662, -3.605, 0.000 ] + # station(rel) airfoil name + airfoils: + - [ 0.00000, circular ] + - [ 0.02000, circular ] + - [ 0.15000, SNL-FFA-W3-500 ] + - [ 0.24517, FFA-W3-360 ] + - [ 0.32884, FFA-W3-330blend ] + - [ 0.43918, FFA-W3-301 ] + - [ 0.53767, FFA-W3-270blend ] + - [ 0.63821, FFA-W3-241 ] + - [ 0.77174, FFA-W3-211 ] + - [ 1.00000, FFA-W3-211 ] + + + airfoils: + - name : circular # + relative_thickness : 1.0 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00010, 0.35000, -0.00010 ] + - [ 179.9087, 0.00010, 0.35000, -0.00010 ] + - name : SNL-FFA-W3-500 # + relative_thickness : 0.5 # + data: # alpha c_l c_d c_m + - [ -179.9660, 0.00000, 0.08440, 0.00000 ] + - [ -170.0000, 0.44190, 0.08440, 0.31250 ] + - [ -160.0002, 0.88370, 0.12680, 0.28310 ] + - [ -149.9998, 0.96740, 0.29270, 0.26320 ] + - [ -139.9999, 0.78010, 0.49700, 0.20480 ] + - [ -130.0001, 0.62930, 0.71610, 0.19320 ] + - [ -120.0003, 0.47850, 0.92460, 0.20080 ] + - [ -109.9999, 0.31890, 1.09850, 0.21360 ] + - [ -100.0000, 0.15530, 1.21820, 0.22210 ] + - [ -90.0002, 0.00000, 1.27070, 0.21980 ] + - [ -79.9998, -0.15530, 1.21820, 0.19600 ] + - [ -70.0000, -0.31890, 1.09850, 0.16350 ] + - [ -60.0001, -0.47840, 0.92460, 0.12850 ] + - [ -49.9997, -0.62930, 0.71610, 0.09650 ] + - [ -39.9999, -0.78010, 0.49700, 0.07160 ] + - [ -30.0001, -0.96740, 0.29270, 0.05220 ] + - [ -20.0002, -1.02810, 0.14990, -0.00630 ] + - [ -19.7499, -1.02430, 0.14720, -0.00890 ] + - [ -19.2502, -1.00520, 0.14470, -0.00990 ] + - [ -18.9999, -0.99710, 0.14330, -0.01050 ] + - [ -18.7500, -1.00520, 0.14030, -0.01100 ] + - [ -18.5002, -0.99950, 0.13860, -0.01160 ] + - [ -18.2499, -0.99080, 0.13730, -0.01200 ] + - [ -18.0000, -0.98150, 0.13600, -0.01260 ] + - [ -17.4998, -0.97640, 0.13220, -0.01350 ] + - [ -17.2500, -0.97050, 0.13060, -0.01390 ] + - [ -17.0002, -0.96550, 0.12900, -0.01430 ] + - [ -16.7498, -0.96620, 0.12680, -0.01470 ] + - [ -16.5000, -0.95440, 0.12580, -0.01510 ] + - [ -16.2502, -0.94440, 0.12460, -0.01550 ] + - [ -15.9998, -0.94050, 0.12290, -0.01580 ] + - [ -15.7500, -0.94330, 0.12060, -0.01610 ] + - [ -15.5002, -0.93300, 0.11950, -0.01640 ] + - [ -15.2498, -0.92110, 0.11850, -0.01680 ] + - [ -14.7502, -0.91580, 0.11500, -0.01730 ] + - [ -14.4998, -0.90700, 0.11380, -0.01750 ] + - [ -14.2500, -0.89590, 0.11270, -0.01780 ] + - [ -14.0002, -0.89260, 0.11100, -0.01810 ] + - [ -13.7498, -0.88080, 0.11000, -0.01840 ] + - [ -13.5000, -0.87220, 0.10890, -0.01860 ] + - [ -13.2502, -0.86600, 0.10750, -0.01880 ] + - [ -12.9998, -0.86260, 0.10590, -0.01880 ] + - [ -12.7500, -0.84890, 0.10510, -0.01920 ] + - [ -12.5002, -0.83630, 0.10420, -0.01940 ] + - [ -12.2498, -0.83630, 0.10230, -0.01940 ] + - [ -12.0000, -0.82710, 0.10130, -0.01960 ] + - [ -11.7502, -0.81410, 0.10040, -0.01980 ] + - [ -11.4998, -0.80040, 0.09970, -0.02000 ] + - [ -11.0002, -0.78900, 0.09710, -0.01990 ] + - [ -10.7498, -0.78620, 0.09560, -0.01960 ] + - [ -10.5000, -0.77470, 0.09480, -0.01940 ] + - [ -10.2502, -0.77010, 0.09400, -0.01840 ] + - [ -9.9998, -0.76740, 0.09250, -0.01830 ] + - [ -9.7500, -0.75060, 0.09170, -0.01920 ] + - [ -9.5002, -0.72900, 0.09120, -0.02050 ] + - [ -9.2498, -0.70950, 0.09020, -0.02240 ] + - [ -9.0000, -0.68550, 0.08950, -0.02470 ] + - [ -8.7502, -0.65900, 0.08910, -0.02670 ] + - [ -8.4998, -0.63190, 0.08870, -0.02870 ] + - [ -8.2500, -0.60190, 0.08790, -0.03200 ] + - [ -8.0002, -0.57180, 0.08750, -0.03450 ] + - [ -7.7498, -0.54240, 0.08730, -0.03670 ] + - [ -7.5000, -0.50980, 0.08680, -0.03990 ] + - [ -7.2502, -0.47670, 0.08640, -0.04300 ] + - [ -6.9998, -0.44540, 0.08620, -0.04530 ] + - [ -6.7500, -0.41420, 0.08600, -0.04760 ] + - [ -6.5002, -0.37910, 0.08560, -0.05100 ] + - [ -6.2498, -0.34600, 0.08530, -0.05380 ] + - [ -6.0000, -0.31440, 0.08520, -0.05600 ] + - [ -5.7502, -0.28170, 0.08500, -0.05860 ] + - [ -5.4998, -0.24610, 0.08470, -0.06190 ] + - [ -5.2500, -0.21330, 0.08460, -0.06440 ] + - [ -5.0002, -0.18270, 0.08450, -0.06630 ] + - [ -4.7498, -0.14940, 0.08430, -0.06880 ] + - [ -4.5000, -0.11580, 0.08420, -0.07150 ] + - [ -4.2502, -0.08370, 0.08400, -0.07370 ] + - [ -3.9998, -0.05290, 0.08400, -0.07560 ] + - [ -3.7500, -0.02250, 0.08390, -0.07740 ] + - [ -3.5002, 0.00890, 0.08380, -0.07930 ] + - [ -3.2498, 0.03920, 0.08380, -0.08110 ] + - [ -3.0000, 0.06860, 0.08380, -0.08260 ] + - [ -2.7502, 0.09740, 0.08380, -0.08380 ] + - [ -2.4998, 0.12600, 0.08380, -0.08520 ] + - [ -2.2500, 0.15550, 0.08380, -0.08670 ] + - [ -2.0002, 0.18530, 0.08380, -0.08830 ] + - [ -1.7498, 0.21460, 0.08370, -0.08970 ] + - [ -1.5000, 0.24300, 0.08370, -0.09100 ] + - [ -1.2502, 0.27130, 0.08380, -0.09210 ] + - [ -0.9998, 0.30060, 0.08380, -0.09360 ] + - [ -0.7500, 0.32950, 0.08380, -0.09490 ] + - [ -0.5002, 0.35780, 0.08380, -0.09610 ] + - [ -0.2498, 0.38570, 0.08380, -0.09720 ] + - [ 0.0000, 0.41350, 0.08380, -0.09830 ] + - [ 0.2298, 0.44250, 0.08390, -0.09950 ] + - [ 0.4698, 0.47150, 0.08390, -0.10080 ] + - [ 0.7002, 0.50030, 0.08390, -0.10190 ] + - [ 0.9402, 0.52860, 0.08400, -0.10290 ] + - [ 1.1700, 0.55670, 0.08400, -0.10400 ] + - [ 1.3997, 0.58500, 0.08410, -0.10500 ] + - [ 1.6398, 0.61350, 0.08410, -0.10610 ] + - [ 1.8701, 0.64170, 0.08420, -0.10720 ] + - [ 2.1102, 0.66970, 0.08420, -0.10820 ] + - [ 2.3400, 0.69750, 0.08430, -0.10910 ] + - [ 2.5697, 0.72510, 0.08430, -0.11000 ] + - [ 2.8098, 0.75280, 0.08440, -0.11090 ] + - [ 3.0401, 0.78070, 0.08450, -0.11190 ] + - [ 3.2802, 0.80830, 0.08460, -0.11280 ] + - [ 3.5099, 0.83580, 0.08460, -0.11370 ] + - [ 3.7403, 0.86310, 0.08470, -0.11460 ] + - [ 3.9798, 0.89020, 0.08470, -0.11530 ] + - [ 4.2101, 0.91730, 0.08480, -0.11610 ] + - [ 4.4502, 0.94440, 0.08490, -0.11700 ] + - [ 4.6799, 0.97130, 0.08500, -0.11780 ] + - [ 4.9102, 0.99810, 0.08510, -0.11850 ] + - [ 5.1497, 1.02490, 0.08520, -0.11920 ] + - [ 5.3801, 1.05150, 0.08530, -0.11990 ] + - [ 5.6201, 1.07790, 0.08530, -0.12060 ] + - [ 5.8499, 1.10410, 0.08540, -0.12120 ] + - [ 6.0802, 1.13020, 0.08560, -0.12180 ] + - [ 6.3197, 1.15600, 0.08570, -0.12240 ] + - [ 6.5501, 1.18180, 0.08580, -0.12300 ] + - [ 6.7901, 1.20760, 0.08590, -0.12350 ] + - [ 7.0199, 1.23340, 0.08600, -0.12400 ] + - [ 7.2502, 1.25890, 0.08610, -0.12450 ] + - [ 7.4903, 1.28410, 0.08620, -0.12500 ] + - [ 7.7200, 1.30880, 0.08640, -0.12540 ] + - [ 7.9601, 1.33310, 0.08650, -0.12570 ] + - [ 8.1899, 1.35700, 0.08670, -0.12590 ] + - [ 8.4202, 1.38100, 0.08690, -0.12620 ] + - [ 8.6603, 1.40540, 0.08700, -0.12650 ] + - [ 8.8900, 1.42950, 0.08710, -0.12670 ] + - [ 9.1198, 1.45310, 0.08730, -0.12700 ] + - [ 9.8801, 1.51540, 0.08790, -0.12650 ] + - [ 10.6398, 1.57490, 0.08860, -0.12560 ] + - [ 11.4001, 1.61510, 0.08950, -0.12140 ] + - [ 12.1501, 1.64430, 0.09120, -0.11630 ] + - [ 12.9099, 1.68240, 0.09300, -0.11330 ] + - [ 13.6702, 1.71460, 0.09540, -0.11070 ] + - [ 14.4202, 1.73620, 0.09890, -0.10800 ] + - [ 15.1799, 1.76270, 0.10240, -0.10630 ] + - [ 15.9403, 1.77060, 0.10760, -0.10420 ] + - [ 16.6903, 1.76390, 0.11440, -0.10250 ] + - [ 17.4500, 1.76040, 0.12110, -0.10130 ] + - [ 18.2097, 1.72510, 0.13100, -0.10010 ] + - [ 18.9701, 1.70350, 0.13990, -0.09980 ] + - [ 19.7201, 1.67840, 0.14920, -0.10010 ] + - [ 20.4798, 1.65050, 0.15910, -0.10160 ] + - [ 21.2401, 1.62270, 0.16910, -0.10360 ] + - [ 21.9901, 1.60670, 0.17780, -0.10640 ] + - [ 22.7499, 1.59720, 0.18580, -0.10990 ] + - [ 23.5102, 1.58920, 0.19370, -0.11360 ] + - [ 24.2602, 1.58150, 0.20140, -0.11800 ] + - [ 25.0199, 1.55630, 0.21350, -0.12490 ] + - [ 25.7802, 1.52720, 0.22670, -0.13250 ] + - [ 26.5302, 1.49820, 0.23990, -0.14000 ] + - [ 27.2900, 1.46910, 0.25310, -0.14760 ] + - [ 28.0497, 1.44010, 0.26630, -0.15510 ] + - [ 28.8100, 1.41100, 0.27950, -0.16270 ] + - [ 29.5600, 1.38200, 0.29270, -0.17030 ] + - [ 30.3198, 1.36220, 0.30780, -0.17400 ] + - [ 31.0801, 1.34240, 0.32300, -0.17770 ] + - [ 31.8301, 1.32250, 0.33810, -0.18150 ] + - [ 32.5898, 1.30270, 0.35320, -0.18520 ] + - [ 33.3502, 1.28290, 0.36840, -0.18890 ] + - [ 34.1002, 1.26310, 0.38350, -0.19260 ] + - [ 34.8599, 1.24330, 0.39870, -0.19640 ] + - [ 35.6202, 1.22340, 0.41380, -0.20010 ] + - [ 36.3800, 1.20360, 0.42890, -0.20390 ] + - [ 37.1300, 1.18380, 0.44410, -0.20760 ] + - [ 37.8903, 1.16400, 0.45920, -0.21130 ] + - [ 38.6500, 1.14420, 0.47430, -0.21500 ] + - [ 39.4000, 1.12430, 0.48950, -0.21880 ] + - [ 40.1598, 1.10640, 0.50520, -0.22180 ] + - [ 40.9201, 1.09050, 0.52140, -0.22420 ] + - [ 41.6701, 1.07450, 0.53760, -0.22660 ] + - [ 42.4298, 1.05860, 0.55380, -0.22890 ] + - [ 43.1901, 1.04260, 0.57010, -0.23130 ] + - [ 43.9401, 1.02670, 0.58630, -0.23370 ] + - [ 44.6999, 1.01070, 0.60250, -0.23610 ] + - [ 45.4602, 0.99480, 0.61880, -0.23840 ] + - [ 46.2199, 0.97880, 0.63500, -0.24080 ] + - [ 46.9699, 0.96280, 0.65120, -0.24320 ] + - [ 47.7302, 0.94690, 0.66750, -0.24550 ] + - [ 48.4900, 0.93090, 0.68370, -0.24790 ] + - [ 49.2400, 0.91500, 0.69990, -0.25030 ] + - [ 49.9997, 0.89900, 0.71610, -0.25270 ] + - [ 60.0001, 0.68360, 0.92460, -0.28330 ] + - [ 70.0000, 0.45560, 1.09850, -0.31560 ] + - [ 79.9998, 0.22190, 1.21820, -0.34820 ] + - [ 90.0002, 0.00000, 1.27070, -0.37730 ] + - [ 100.0000, -0.15530, 1.21820, -0.38770 ] + - [ 109.9999, -0.31890, 1.09850, -0.38650 ] + - [ 120.0003, -0.47840, 0.92460, -0.38060 ] + - [ 130.0001, -0.62930, 0.71610, -0.38030 ] + - [ 139.9999, -0.78010, 0.49700, -0.40320 ] + - [ 149.9998, -0.96740, 0.29270, -0.48540 ] + - [ 160.0002, -0.88370, 0.12680, -0.53250 ] + - [ 170.0000, -0.44180, 0.08440, -0.39060 ] + - [ 179.9660, 0.00000, 0.08440, 0.00000 ] + - name : FFA-W3-211 # + relative_thickness : 0.211 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00000, 0.02464, 0.00000 ] + - [ -177.7143, 0.05403, 0.02534, 0.09143 ] + - [ -175.4286, 0.10805, 0.02742, 0.18286 ] + - [ -173.1429, 0.16208, 0.03088, 0.27429 ] + - [ -170.8572, 0.21610, 0.03570, 0.36571 ] + - [ -168.5716, 0.27013, 0.05599, 0.39192 ] + - [ -166.2857, 0.32415, 0.08143, 0.37898 ] + - [ -164.0000, 0.37818, 0.11112, 0.36605 ] + - [ -161.7145, 0.43220, 0.14485, 0.35312 ] + - [ -159.4284, 0.48623, 0.18242, 0.34768 ] + - [ -157.1428, 0.54025, 0.22359, 0.36471 ] + - [ -154.8573, 0.59428, 0.26810, 0.38175 ] + - [ -152.5714, 0.64830, 0.31566, 0.39878 ] + - [ -150.2857, 0.70233, 0.36597, 0.41581 ] + - [ -148.0000, 0.75635, 0.41871, 0.41955 ] + - [ -143.8571, 0.73188, 0.51941, 0.42287 ] + - [ -139.7143, 0.70655, 0.62488, 0.42632 ] + - [ -135.5714, 0.67760, 0.73293, 0.43163 ] + - [ -131.4286, 0.64333, 0.84130, 0.43694 ] + - [ -127.2857, 0.60277, 0.94773, 0.44389 ] + - [ -123.1429, 0.55550, 1.05001, 0.45171 ] + - [ -119.0000, 0.50156, 1.14600, 0.45897 ] + - [ -114.8571, 0.44131, 1.23371, 0.46448 ] + - [ -110.7143, 0.37542, 1.31129, 0.46998 ] + - [ -106.5714, 0.30482, 1.37714, 0.47096 ] + - [ -102.4286, 0.23063, 1.42988, 0.47101 ] + - [ -98.2857, 0.15413, 1.46842, 0.46824 ] + - [ -94.1429, 0.07675, 1.49196, 0.46149 ] + - [ -90.0000, 0.00000, 1.50000, 0.45474 ] + - [ -85.8571, -0.07675, 1.49196, 0.44026 ] + - [ -81.7143, -0.15413, 1.46842, 0.42578 ] + - [ -77.5714, -0.23063, 1.42988, 0.40821 ] + - [ -73.4286, -0.30482, 1.37714, 0.38846 ] + - [ -69.2857, -0.37542, 1.31129, 0.36815 ] + - [ -65.1429, -0.44131, 1.23371, 0.34519 ] + - [ -61.0000, -0.50156, 1.14600, 0.32223 ] + - [ -56.8571, -0.55550, 1.05001, 0.29864 ] + - [ -52.7143, -0.60277, 0.94773, 0.27486 ] + - [ -48.5714, -0.64333, 0.84130, 0.25128 ] + - [ -44.4286, -0.67760, 0.73293, 0.22810 ] + - [ -40.2857, -0.70655, 0.62488, 0.20491 ] + - [ -36.1429, -0.73188, 0.51941, 0.15416 ] + - [ -32.0000, -0.75635, 0.41871, 0.10137 ] + - [ -28.0000, -0.85636, 0.28691, 0.06527 ] + - [ -24.0000, -1.18292, 0.13960, 0.01647 ] + - [ -20.0000, -1.23596, 0.08345, -0.00352 ] + - [ -18.0000, -1.22536, 0.06509, -0.00672 ] + - [ -16.0000, -1.20476, 0.04888, -0.00881 ] + - [ -14.0000, -1.18332, 0.03417, -0.01101 ] + - [ -12.0000, -1.10093, 0.02132, -0.02269 ] + - [ -10.0000, -0.88209, 0.01386, -0.04397 ] + - [ -8.0000, -0.62981, 0.01075, -0.05756 ] + - [ -6.0000, -0.37670, 0.00882, -0.06747 ] + - [ -4.0000, -0.12177, 0.00702, -0.07680 ] + - [ -2.0000, 0.12810, 0.00663, -0.08283 ] + - [ -1.0000, 0.25192, 0.00664, -0.08534 ] + - [ 0.0000, 0.37535, 0.00670, -0.08777 ] + - [ 1.0000, 0.49828, 0.00681, -0.09011 ] + - [ 2.0000, 0.62052, 0.00698, -0.09234 ] + - [ 3.0000, 0.74200, 0.00720, -0.09447 ] + - [ 4.0000, 0.86238, 0.00751, -0.09646 ] + - [ 5.0000, 0.98114, 0.00796, -0.09828 ] + - [ 6.0000, 1.09662, 0.00872, -0.09977 ] + - [ 7.0000, 1.20904, 0.00968, -0.10095 ] + - [ 8.0000, 1.31680, 0.01097, -0.10163 ] + - [ 9.0000, 1.42209, 0.01227, -0.10207 ] + - [ 10.0000, 1.52361, 0.01369, -0.10213 ] + - [ 11.0000, 1.61988, 0.01529, -0.10174 ] + - [ 12.0000, 1.70937, 0.01717, -0.10087 ] + - [ 13.0000, 1.78681, 0.01974, -0.09936 ] + - [ 14.0000, 1.84290, 0.02368, -0.09720 ] + - [ 15.0000, 1.85313, 0.03094, -0.09410 ] + - [ 16.0000, 1.80951, 0.04303, -0.09144 ] + - [ 18.0000, 1.66033, 0.07730, -0.09242 ] + - [ 20.0000, 1.56152, 0.11202, -0.09871 ] + - [ 24.0000, 1.43327, 0.18408, -0.11770 ] + - [ 28.0000, 1.29062, 0.27589, -0.14566 ] + - [ 32.0000, 1.08050, 0.41871, -0.18266 ] + - [ 36.1429, 1.04554, 0.51941, -0.20913 ] + - [ 40.2857, 1.00936, 0.62488, -0.23534 ] + - [ 44.4286, 0.96801, 0.73293, -0.25784 ] + - [ 48.5714, 0.91904, 0.84130, -0.28035 ] + - [ 52.7143, 0.86109, 0.94773, -0.30163 ] + - [ 56.8571, 0.79357, 1.05001, -0.32226 ] + - [ 61.0000, 0.71651, 1.14600, -0.34247 ] + - [ 65.1429, 0.63044, 1.23371, -0.36135 ] + - [ 69.2857, 0.53632, 1.31129, -0.38024 ] + - [ 73.4286, 0.43546, 1.37714, -0.39704 ] + - [ 77.5714, 0.32947, 1.42988, -0.41341 ] + - [ 81.7143, 0.22019, 1.46842, -0.42844 ] + - [ 85.8571, 0.10965, 1.49196, -0.44159 ] + - [ 90.0000, 0.00000, 1.50000, -0.45474 ] + - [ 94.1429, -0.07675, 1.49196, -0.46149 ] + - [ 98.2857, -0.15413, 1.46842, -0.46824 ] + - [ 102.4286, -0.23063, 1.42988, -0.47101 ] + - [ 106.5714, -0.30482, 1.37714, -0.47096 ] + - [ 110.7143, -0.37542, 1.31129, -0.46998 ] + - [ 114.8571, -0.44131, 1.23371, -0.46448 ] + - [ 119.0000, -0.50156, 1.14600, -0.45897 ] + - [ 123.1429, -0.55550, 1.05001, -0.45171 ] + - [ 127.2857, -0.60277, 0.94773, -0.44389 ] + - [ 131.4286, -0.64333, 0.84130, -0.43694 ] + - [ 135.5714, -0.67760, 0.73293, -0.43163 ] + - [ 139.7143, -0.70655, 0.62488, -0.42632 ] + - [ 143.8571, -0.73188, 0.51941, -0.42287 ] + - [ 148.0000, -0.75635, 0.41871, -0.41955 ] + - [ 150.2857, -0.70233, 0.36597, -0.41581 ] + - [ 152.5714, -0.64830, 0.31566, -0.39878 ] + - [ 154.8571, -0.59428, 0.26810, -0.38175 ] + - [ 157.1429, -0.54025, 0.22359, -0.36471 ] + - [ 159.4286, -0.48623, 0.18242, -0.34768 ] + - [ 161.7143, -0.43220, 0.14485, -0.37026 ] + - [ 164.0000, -0.37818, 0.11112, -0.40605 ] + - [ 166.2857, -0.32415, 0.08143, -0.44184 ] + - [ 168.5714, -0.27013, 0.05599, -0.47763 ] + - [ 170.8571, -0.21610, 0.03570, -0.45714 ] + - [ 173.1429, -0.16208, 0.03088, -0.34286 ] + - [ 175.4286, -0.10805, 0.02742, -0.22857 ] + - [ 177.7143, -0.05403, 0.02534, -0.11429 ] + - [ 179.9087, 0.00000, 0.02464, 0.00000 ] + - name : FFA-W3-241 # + relative_thickness : 0.241 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00000, 0.01178, 0.00000 ] + - [ -177.7143, 0.05818, 0.01248, 0.09143 ] + - [ -175.4286, 0.11636, 0.01460, 0.18286 ] + - [ -173.1429, 0.17453, 0.01811, 0.27429 ] + - [ -170.8572, 0.23271, 0.02300, 0.36571 ] + - [ -168.5716, 0.29089, 0.02922, 0.39568 ] + - [ -166.2857, 0.34907, 0.05382, 0.38876 ] + - [ -164.0000, 0.40725, 0.08379, 0.38184 ] + - [ -161.7145, 0.46542, 0.11786, 0.37492 ] + - [ -159.4284, 0.52360, 0.15581, 0.37408 ] + - [ -157.1428, 0.58178, 0.19740, 0.39148 ] + - [ -154.8573, 0.63996, 0.24237, 0.40888 ] + - [ -152.5714, 0.69814, 0.29043, 0.42628 ] + - [ -150.2857, 0.75631, 0.34128, 0.44368 ] + - [ -148.0000, 0.81449, 0.39460, 0.44537 ] + - [ -143.8571, 0.77925, 0.49645, 0.44436 ] + - [ -139.7143, 0.74511, 0.60319, 0.44360 ] + - [ -135.5714, 0.70881, 0.71263, 0.44609 ] + - [ -131.4286, 0.66835, 0.82249, 0.44858 ] + - [ -127.2857, 0.62253, 0.93051, 0.45370 ] + - [ -123.1429, 0.57080, 1.03447, 0.46020 ] + - [ -119.0000, 0.51307, 1.13222, 0.46633 ] + - [ -114.8571, 0.44965, 1.22176, 0.47130 ] + - [ -110.7143, 0.38115, 1.30123, 0.47627 ] + - [ -106.5714, 0.30846, 1.36903, 0.47705 ] + - [ -102.4286, 0.23266, 1.42376, 0.47695 ] + - [ -98.2857, 0.15503, 1.46433, 0.47409 ] + - [ -94.1429, 0.07698, 1.48990, 0.46732 ] + - [ -90.0000, 0.00000, 1.50000, 0.46055 ] + - [ -85.8571, -0.07698, 1.48990, 0.44509 ] + - [ -81.7143, -0.15503, 1.46433, 0.42964 ] + - [ -77.5714, -0.23266, 1.42376, 0.41125 ] + - [ -73.4286, -0.30846, 1.36903, 0.39081 ] + - [ -69.2857, -0.38115, 1.30123, 0.36988 ] + - [ -65.1429, -0.44965, 1.22176, 0.34663 ] + - [ -61.0000, -0.51307, 1.13222, 0.32339 ] + - [ -56.8571, -0.57080, 1.03447, 0.29984 ] + - [ -52.7143, -0.62253, 0.93051, 0.27618 ] + - [ -48.5714, -0.66835, 0.82249, 0.25280 ] + - [ -44.4286, -0.70881, 0.71263, 0.22992 ] + - [ -40.2857, -0.74511, 0.60319, 0.20705 ] + - [ -36.1429, -0.77925, 0.49645, 0.14561 ] + - [ -32.0000, -0.81449, 0.39460, 0.08131 ] + - [ -28.0000, -1.07781, 0.22252, 0.04592 ] + - [ -24.0000, -1.12692, 0.15159, 0.01901 ] + - [ -20.0000, -1.14480, 0.09699, 0.00063 ] + - [ -18.0000, -1.12797, 0.07744, -0.00342 ] + - [ -16.0000, -1.09392, 0.06122, -0.00587 ] + - [ -14.0000, -1.05961, 0.04667, -0.00652 ] + - [ -12.0000, -1.03121, 0.03302, -0.00755 ] + - [ -10.0000, -0.93706, 0.02027, -0.02243 ] + - [ -8.0000, -0.67380, 0.01168, -0.05583 ] + - [ -6.0000, -0.40391, 0.00918, -0.07159 ] + - [ -4.0000, -0.14226, 0.00839, -0.08123 ] + - [ -2.0000, 0.11580, 0.00810, -0.08892 ] + - [ -1.0000, 0.24382, 0.00808, -0.09235 ] + - [ 0.0000, 0.37113, 0.00813, -0.09556 ] + - [ 1.0000, 0.49766, 0.00824, -0.09857 ] + - [ 2.0000, 0.62334, 0.00842, -0.10139 ] + - [ 3.0000, 0.74798, 0.00867, -0.10403 ] + - [ 4.0000, 0.87137, 0.00901, -0.10645 ] + - [ 5.0000, 0.99320, 0.00945, -0.10863 ] + - [ 6.0000, 1.11325, 0.00998, -0.11057 ] + - [ 7.0000, 1.23037, 0.01070, -0.11214 ] + - [ 8.0000, 1.34496, 0.01153, -0.11337 ] + - [ 9.0000, 1.45407, 0.01269, -0.11396 ] + - [ 10.0000, 1.55911, 0.01396, -0.11403 ] + - [ 11.0000, 1.65779, 0.01545, -0.11336 ] + - [ 12.0000, 1.74834, 0.01724, -0.11187 ] + - [ 13.0000, 1.82666, 0.01961, -0.10935 ] + - [ 14.0000, 1.88831, 0.02293, -0.10606 ] + - [ 15.0000, 1.92579, 0.02795, -0.10238 ] + - [ 16.0000, 1.92722, 0.03609, -0.09887 ] + - [ 18.0000, 1.80055, 0.06534, -0.09497 ] + - [ 20.0000, 1.63088, 0.10459, -0.09996 ] + - [ 24.0000, 1.43345, 0.19148, -0.12589 ] + - [ 28.0000, 1.28805, 0.28629, -0.15453 ] + - [ 32.0000, 1.16356, 0.39460, -0.18396 ] + - [ 36.1429, 1.11321, 0.49645, -0.21099 ] + - [ 40.2857, 1.06444, 0.60319, -0.23768 ] + - [ 44.4286, 1.01259, 0.71263, -0.25992 ] + - [ 48.5714, 0.95478, 0.82249, -0.28216 ] + - [ 52.7143, 0.88932, 0.93051, -0.30323 ] + - [ 56.8571, 0.81542, 1.03447, -0.32368 ] + - [ 61.0000, 0.73296, 1.13222, -0.34380 ] + - [ 65.1429, 0.64236, 1.22176, -0.36292 ] + - [ 69.2857, 0.54450, 1.30123, -0.38204 ] + - [ 73.4286, 0.44065, 1.36903, -0.39944 ] + - [ 77.5714, 0.33237, 1.42376, -0.41648 ] + - [ 81.7143, 0.22148, 1.46433, -0.43231 ] + - [ 85.8571, 0.10997, 1.48990, -0.44643 ] + - [ 90.0000, 0.00000, 1.50000, -0.46055 ] + - [ 94.1429, -0.07698, 1.48990, -0.46732 ] + - [ 98.2857, -0.15503, 1.46433, -0.47409 ] + - [ 102.4286, -0.23266, 1.42376, -0.47695 ] + - [ 106.5714, -0.30846, 1.36903, -0.47705 ] + - [ 110.7143, -0.38115, 1.30123, -0.47627 ] + - [ 114.8571, -0.44965, 1.22176, -0.47130 ] + - [ 119.0000, -0.51307, 1.13222, -0.46633 ] + - [ 123.1429, -0.57080, 1.03447, -0.46020 ] + - [ 127.2857, -0.62253, 0.93051, -0.45370 ] + - [ 131.4286, -0.66835, 0.82249, -0.44858 ] + - [ 135.5714, -0.70881, 0.71263, -0.44609 ] + - [ 139.7143, -0.74511, 0.60319, -0.44360 ] + - [ 143.8571, -0.77925, 0.49645, -0.44436 ] + - [ 148.0000, -0.81449, 0.39460, -0.44537 ] + - [ 150.2857, -0.75631, 0.34128, -0.44368 ] + - [ 152.5714, -0.69814, 0.29043, -0.42628 ] + - [ 154.8571, -0.63996, 0.24237, -0.40888 ] + - [ 157.1429, -0.58178, 0.19740, -0.39148 ] + - [ 159.4286, -0.52360, 0.15581, -0.37408 ] + - [ 161.7143, -0.46542, 0.11786, -0.39207 ] + - [ 164.0000, -0.40725, 0.08379, -0.42184 ] + - [ 166.2857, -0.34907, 0.05382, -0.45162 ] + - [ 168.5714, -0.29089, 0.02922, -0.48139 ] + - [ 170.8571, -0.23271, 0.02300, -0.45714 ] + - [ 173.1429, -0.17453, 0.01811, -0.34286 ] + - [ 175.4286, -0.11636, 0.01460, -0.22857 ] + - [ 177.7143, -0.05818, 0.01248, -0.11429 ] + - [ 179.9087, 0.00000, 0.01178, 0.00000 ] + - name : FFA-W3-270blend # + relative_thickness : 0.27 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00000, 0.01545, 0.00000 ] + - [ -177.7143, 0.06213, 0.01611, 0.09143 ] + - [ -175.4286, 0.12426, 0.01807, 0.18286 ] + - [ -173.1429, 0.18639, 0.02133, 0.27429 ] + - [ -170.8572, 0.24852, 0.02587, 0.36571 ] + - [ -168.5716, 0.31064, 0.03289, 0.39874 ] + - [ -166.2857, 0.37277, 0.05681, 0.39672 ] + - [ -164.0000, 0.43490, 0.08471, 0.39470 ] + - [ -161.7145, 0.49703, 0.11643, 0.39268 ] + - [ -159.4284, 0.55916, 0.15176, 0.39544 ] + - [ -157.1428, 0.62129, 0.19048, 0.41254 ] + - [ -154.8573, 0.68342, 0.23234, 0.42964 ] + - [ -152.5714, 0.74555, 0.27708, 0.44674 ] + - [ -150.2857, 0.80768, 0.32441, 0.46384 ] + - [ -148.0000, 0.86981, 0.37404, 0.46186 ] + - [ -143.8571, 0.81660, 0.46882, 0.45335 ] + - [ -139.7143, 0.76812, 0.56814, 0.44523 ] + - [ -135.5714, 0.72040, 0.66995, 0.44237 ] + - [ -131.4286, 0.67095, 0.77214, 0.43951 ] + - [ -127.2857, 0.61828, 0.87258, 0.44072 ] + - [ -123.1429, 0.56158, 0.96921, 0.44407 ] + - [ -119.0000, 0.50057, 1.06002, 0.44739 ] + - [ -114.8571, 0.43540, 1.14315, 0.45063 ] + - [ -110.7143, 0.36655, 1.21688, 0.45387 ] + - [ -106.5714, 0.29475, 1.27969, 0.45377 ] + - [ -102.4286, 0.22098, 1.33030, 0.45298 ] + - [ -98.2857, 0.14639, 1.36768, 0.44973 ] + - [ -94.1429, 0.07227, 1.39107, 0.44302 ] + - [ -90.0000, 0.00000, 1.40000, 0.43630 ] + - [ -85.8571, -0.07227, 1.39107, 0.42180 ] + - [ -81.7143, -0.14639, 1.36768, 0.40730 ] + - [ -77.5714, -0.22098, 1.33030, 0.39020 ] + - [ -73.4286, -0.29475, 1.27969, 0.37125 ] + - [ -69.2857, -0.36655, 1.21688, 0.35190 ] + - [ -65.1429, -0.43540, 1.14315, 0.33068 ] + - [ -61.0000, -0.50057, 1.06002, 0.30945 ] + - [ -56.8571, -0.56158, 0.96921, 0.28815 ] + - [ -52.7143, -0.61828, 0.87258, 0.26684 ] + - [ -48.5714, -0.67095, 0.77214, 0.24576 ] + - [ -44.4286, -0.72040, 0.66995, 0.22512 ] + - [ -40.2857, -0.76812, 0.56814, 0.20447 ] + - [ -36.1429, -0.81660, 0.46882, 0.13957 ] + - [ -32.0000, -0.86981, 0.37404, 0.07138 ] + - [ -28.0000, -1.09837, 0.21880, 0.04400 ] + - [ -24.0000, -1.08339, 0.15982, 0.02166 ] + - [ -20.0000, -1.06990, 0.10744, 0.00422 ] + - [ -18.0000, -1.05454, 0.08690, -0.00035 ] + - [ -16.0000, -1.03432, 0.06844, -0.00334 ] + - [ -14.0000, -1.08360, 0.04733, -0.00283 ] + - [ -12.0000, -1.09489, 0.03085, -0.00556 ] + - [ -10.0000, -0.92665, 0.01984, -0.02952 ] + - [ -8.0000, -0.69676, 0.01439, -0.04822 ] + - [ -6.0000, -0.43628, 0.01155, -0.06483 ] + - [ -4.0000, -0.16252, 0.01026, -0.07919 ] + - [ -2.0000, 0.10709, 0.00976, -0.09041 ] + - [ -1.0000, 0.23993, 0.00967, -0.09517 ] + - [ 0.0000, 0.37158, 0.00968, -0.09953 ] + - [ 1.0000, 0.50210, 0.00976, -0.10355 ] + - [ 2.0000, 0.63139, 0.00993, -0.10725 ] + - [ 3.0000, 0.75951, 0.01016, -0.11068 ] + - [ 4.0000, 0.88638, 0.01045, -0.11385 ] + - [ 5.0000, 1.01172, 0.01082, -0.11673 ] + - [ 6.0000, 1.13430, 0.01140, -0.11923 ] + - [ 7.0000, 1.25536, 0.01198, -0.12145 ] + - [ 8.0000, 1.37379, 0.01267, -0.12328 ] + - [ 9.0000, 1.48841, 0.01353, -0.12460 ] + - [ 10.0000, 1.59782, 0.01460, -0.12526 ] + - [ 11.0000, 1.70005, 0.01597, -0.12505 ] + - [ 12.0000, 1.79190, 0.01777, -0.12370 ] + - [ 13.0000, 1.86782, 0.02035, -0.12093 ] + - [ 14.0000, 1.92687, 0.02385, -0.11725 ] + - [ 15.0000, 1.90901, 0.03236, -0.10931 ] + - [ 16.0000, 1.88548, 0.04259, -0.10525 ] + - [ 18.0000, 1.72106, 0.07672, -0.10292 ] + - [ 20.0000, 1.54737, 0.11914, -0.11017 ] + - [ 24.0000, 1.37176, 0.20189, -0.13431 ] + - [ 28.0000, 1.33611, 0.27981, -0.15777 ] + - [ 32.0000, 1.24258, 0.37404, -0.18432 ] + - [ 36.1429, 1.16657, 0.46882, -0.21002 ] + - [ 40.2857, 1.09731, 0.56814, -0.23531 ] + - [ 44.4286, 1.02914, 0.66995, -0.25508 ] + - [ 48.5714, 0.95850, 0.77214, -0.27485 ] + - [ 52.7143, 0.88325, 0.87258, -0.29346 ] + - [ 56.8571, 0.80225, 0.96921, -0.31145 ] + - [ 61.0000, 0.71510, 1.06002, -0.32925 ] + - [ 65.1429, 0.62200, 1.14315, -0.34641 ] + - [ 69.2857, 0.52364, 1.21688, -0.36357 ] + - [ 73.4286, 0.42107, 1.27969, -0.37949 ] + - [ 77.5714, 0.31569, 1.33030, -0.39517 ] + - [ 81.7143, 0.20913, 1.36768, -0.40983 ] + - [ 85.8571, 0.10324, 1.39107, -0.42306 ] + - [ 90.0000, 0.00000, 1.40000, -0.43630 ] + - [ 94.1429, -0.07227, 1.39107, -0.44302 ] + - [ 98.2857, -0.14639, 1.36768, -0.44973 ] + - [ 102.4286, -0.22098, 1.33030, -0.45298 ] + - [ 106.5714, -0.29475, 1.27969, -0.45377 ] + - [ 110.7143, -0.36655, 1.21688, -0.45387 ] + - [ 114.8571, -0.43540, 1.14315, -0.45063 ] + - [ 119.0000, -0.50057, 1.06002, -0.44739 ] + - [ 123.1429, -0.56158, 0.96921, -0.44407 ] + - [ 127.2857, -0.61828, 0.87258, -0.44072 ] + - [ 131.4286, -0.67095, 0.77214, -0.43951 ] + - [ 135.5714, -0.72040, 0.66995, -0.44237 ] + - [ 139.7143, -0.76812, 0.56814, -0.44523 ] + - [ 143.8571, -0.81660, 0.46882, -0.45335 ] + - [ 148.0000, -0.86981, 0.37404, -0.46186 ] + - [ 150.2857, -0.80768, 0.32441, -0.46384 ] + - [ 152.5714, -0.74555, 0.27708, -0.44674 ] + - [ 154.8571, -0.68342, 0.23234, -0.42964 ] + - [ 157.1429, -0.62129, 0.19048, -0.41254 ] + - [ 159.4286, -0.55916, 0.15176, -0.39544 ] + - [ 161.7143, -0.49703, 0.11643, -0.40982 ] + - [ 164.0000, -0.43490, 0.08471, -0.43470 ] + - [ 166.2857, -0.37277, 0.05681, -0.45958 ] + - [ 168.5714, -0.31064, 0.03289, -0.48445 ] + - [ 170.8571, -0.24852, 0.02587, -0.45714 ] + - [ 173.1429, -0.18639, 0.02133, -0.34286 ] + - [ 175.4286, -0.12426, 0.01807, -0.22857 ] + - [ 177.7143, -0.06213, 0.01611, -0.11429 ] + - [ 179.9087, 0.00000, 0.01545, 0.00000 ] + - name : FFA-W3-301 # + relative_thickness : 0.301 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00000, 0.02454, 0.00000 ] + - [ -177.7143, 0.06508, 0.02514, 0.09143 ] + - [ -175.4286, 0.13016, 0.02694, 0.18286 ] + - [ -173.1429, 0.19525, 0.02993, 0.27429 ] + - [ -170.8572, 0.26033, 0.03408, 0.36571 ] + - [ -168.5716, 0.32541, 0.03938, 0.40085 ] + - [ -166.2857, 0.39049, 0.05910, 0.40220 ] + - [ -164.0000, 0.45557, 0.08495, 0.40356 ] + - [ -161.7145, 0.52066, 0.11433, 0.40492 ] + - [ -159.4284, 0.58574, 0.14704, 0.41010 ] + - [ -157.1428, 0.65082, 0.18290, 0.42678 ] + - [ -154.8573, 0.71590, 0.22166, 0.44345 ] + - [ -152.5714, 0.78098, 0.26309, 0.46013 ] + - [ -150.2857, 0.84607, 0.30692, 0.47680 ] + - [ -148.0000, 0.91115, 0.35287, 0.47162 ] + - [ -143.8571, 0.84257, 0.44061, 0.45656 ] + - [ -139.7143, 0.78187, 0.53255, 0.44202 ] + - [ -135.5714, 0.72448, 0.62677, 0.43452 ] + - [ -131.4286, 0.66755, 0.72131, 0.42701 ] + - [ -127.2857, 0.60928, 0.81421, 0.42483 ] + - [ -123.1429, 0.54868, 0.90355, 0.42544 ] + - [ -119.0000, 0.48530, 0.98748, 0.42634 ] + - [ -114.8571, 0.41915, 1.06425, 0.42813 ] + - [ -110.7143, 0.35056, 1.13227, 0.42992 ] + - [ -106.5714, 0.28017, 1.19015, 0.42916 ] + - [ -102.4286, 0.20881, 1.23669, 0.42788 ] + - [ -98.2857, 0.13754, 1.27093, 0.42444 ] + - [ -94.1429, 0.06751, 1.29218, 0.41794 ] + - [ -90.0000, 0.00000, 1.30000, 0.41144 ] + - [ -85.8571, -0.06751, 1.29218, 0.39804 ] + - [ -81.7143, -0.13754, 1.27093, 0.38464 ] + - [ -77.5714, -0.20881, 1.23669, 0.36892 ] + - [ -73.4286, -0.28017, 1.19015, 0.35157 ] + - [ -69.2857, -0.35056, 1.13227, 0.33391 ] + - [ -65.1429, -0.41915, 1.06425, 0.31474 ] + - [ -61.0000, -0.48530, 0.98748, 0.29557 ] + - [ -56.8571, -0.54868, 0.90355, 0.27653 ] + - [ -52.7143, -0.60928, 0.81421, 0.25754 ] + - [ -48.5714, -0.66755, 0.72131, 0.23873 ] + - [ -44.4286, -0.72448, 0.62677, 0.22027 ] + - [ -40.2857, -0.78187, 0.53255, 0.20181 ] + - [ -36.1429, -0.84257, 0.44061, 0.13644 ] + - [ -32.0000, -0.91115, 0.35287, 0.06760 ] + - [ -28.0000, -1.10349, 0.21721, 0.04231 ] + - [ -24.0000, -1.10737, 0.15629, 0.02026 ] + - [ -20.0000, -1.11815, 0.10335, 0.00407 ] + - [ -18.0000, -1.12332, 0.08180, 0.00017 ] + - [ -16.0000, -1.11865, 0.06331, -0.00167 ] + - [ -14.0000, -1.11620, 0.04718, -0.00120 ] + - [ -12.0000, -1.09588, 0.03280, -0.00463 ] + - [ -10.0000, -0.91767, 0.02351, -0.02494 ] + - [ -8.0000, -0.69311, 0.01793, -0.04304 ] + - [ -6.0000, -0.45396, 0.01431, -0.05868 ] + - [ -4.0000, -0.17779, 0.01242, -0.07601 ] + - [ -2.0000, 0.10480, 0.01160, -0.09121 ] + - [ -1.0000, 0.24383, 0.01143, -0.09763 ] + - [ 0.0000, 0.38111, 0.01138, -0.10341 ] + - [ 1.0000, 0.51660, 0.01143, -0.10861 ] + - [ 2.0000, 0.65044, 0.01156, -0.11333 ] + - [ 3.0000, 0.78267, 0.01177, -0.11762 ] + - [ 4.0000, 0.91326, 0.01204, -0.12154 ] + - [ 5.0000, 1.04207, 0.01239, -0.12510 ] + - [ 6.0000, 1.16873, 0.01283, -0.12828 ] + - [ 7.0000, 1.29296, 0.01338, -0.13104 ] + - [ 8.0000, 1.41390, 0.01406, -0.13332 ] + - [ 9.0000, 1.53088, 0.01488, -0.13503 ] + - [ 10.0000, 1.64208, 0.01592, -0.13599 ] + - [ 11.0000, 1.74568, 0.01726, -0.13605 ] + - [ 12.0000, 1.83887, 0.01908, -0.13514 ] + - [ 13.0000, 1.91764, 0.02169, -0.13322 ] + - [ 14.0000, 1.97413, 0.02572, -0.13020 ] + - [ 15.0000, 1.99916, 0.03222, -0.12641 ] + - [ 16.0000, 1.99377, 0.04157, -0.12265 ] + - [ 18.0000, 1.91720, 0.06731, -0.11675 ] + - [ 20.0000, 1.73683, 0.10526, -0.11652 ] + - [ 24.0000, 1.47321, 0.19229, -0.13790 ] + - [ 28.0000, 1.36017, 0.27449, -0.16242 ] + - [ 32.0000, 1.30164, 0.35287, -0.18463 ] + - [ 36.1429, 1.20367, 0.44061, -0.20894 ] + - [ 40.2857, 1.11695, 0.53255, -0.23276 ] + - [ 44.4286, 1.03498, 0.62677, -0.25011 ] + - [ 48.5714, 0.95364, 0.72131, -0.26746 ] + - [ 52.7143, 0.87040, 0.81421, -0.28365 ] + - [ 56.8571, 0.78383, 0.90355, -0.29923 ] + - [ 61.0000, 0.69329, 0.98748, -0.31472 ] + - [ 65.1429, 0.59878, 1.06425, -0.32988 ] + - [ 69.2857, 0.50080, 1.13227, -0.34505 ] + - [ 73.4286, 0.40024, 1.19015, -0.35942 ] + - [ 77.5714, 0.29831, 1.23669, -0.37363 ] + - [ 81.7143, 0.19648, 1.27093, -0.38702 ] + - [ 85.8571, 0.09644, 1.29218, -0.39923 ] + - [ 90.0000, 0.00000, 1.30000, -0.41144 ] + - [ 94.1429, -0.06751, 1.29218, -0.41794 ] + - [ 98.2857, -0.13754, 1.27093, -0.42444 ] + - [ 102.4286, -0.20881, 1.23669, -0.42788 ] + - [ 106.5714, -0.28017, 1.19015, -0.42916 ] + - [ 110.7143, -0.35056, 1.13227, -0.42992 ] + - [ 114.8571, -0.41915, 1.06425, -0.42813 ] + - [ 119.0000, -0.48530, 0.98748, -0.42634 ] + - [ 123.1429, -0.54868, 0.90355, -0.42544 ] + - [ 127.2857, -0.60928, 0.81421, -0.42483 ] + - [ 131.4286, -0.66755, 0.72131, -0.42701 ] + - [ 135.5714, -0.72448, 0.62677, -0.43452 ] + - [ 139.7143, -0.78187, 0.53255, -0.44202 ] + - [ 143.8571, -0.84257, 0.44061, -0.45656 ] + - [ 148.0000, -0.91115, 0.35287, -0.47162 ] + - [ 150.2857, -0.84607, 0.30692, -0.47680 ] + - [ 152.5714, -0.78098, 0.26309, -0.46013 ] + - [ 154.8571, -0.71590, 0.22166, -0.44345 ] + - [ 157.1429, -0.65082, 0.18290, -0.42678 ] + - [ 159.4286, -0.58574, 0.14704, -0.41010 ] + - [ 161.7143, -0.52066, 0.11433, -0.42206 ] + - [ 164.0000, -0.45557, 0.08495, -0.44356 ] + - [ 166.2857, -0.39049, 0.05910, -0.46506 ] + - [ 168.5714, -0.32541, 0.03938, -0.48656 ] + - [ 170.8571, -0.26033, 0.03408, -0.45714 ] + - [ 173.1429, -0.19525, 0.02993, -0.34286 ] + - [ 175.4286, -0.13016, 0.02694, -0.22857 ] + - [ 177.7143, -0.06508, 0.02514, -0.11429 ] + - [ 179.9087, 0.00000, 0.02454, 0.00000 ] + - name : FFA-W3-330blend # + relative_thickness : 0.33 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00000, 0.03169, 0.00000 ] + - [ -177.7143, 0.06960, 0.03228, 0.09143 ] + - [ -175.4286, 0.13920, 0.03406, 0.18286 ] + - [ -173.1429, 0.20880, 0.03702, 0.27429 ] + - [ -170.8572, 0.27841, 0.04114, 0.36571 ] + - [ -168.5716, 0.34801, 0.04638, 0.40308 ] + - [ -166.2857, 0.41761, 0.05732, 0.40801 ] + - [ -164.0000, 0.48721, 0.08319, 0.41294 ] + - [ -161.7145, 0.55681, 0.11258, 0.41788 ] + - [ -159.4284, 0.62641, 0.14533, 0.42586 ] + - [ -157.1428, 0.69601, 0.18121, 0.44302 ] + - [ -154.8573, 0.76562, 0.22000, 0.46017 ] + - [ -152.5714, 0.83522, 0.26146, 0.47732 ] + - [ -150.2857, 0.90482, 0.30532, 0.49447 ] + - [ -148.0000, 0.97442, 0.35131, 0.48743 ] + - [ -143.8571, 0.89412, 0.43913, 0.46839 ] + - [ -139.7143, 0.82382, 0.53115, 0.44996 ] + - [ -135.5714, 0.75845, 0.62546, 0.43985 ] + - [ -131.4286, 0.69477, 0.72010, 0.42974 ] + - [ -127.2857, 0.63079, 0.81310, 0.42589 ] + - [ -123.1429, 0.56532, 0.90255, 0.42535 ] + - [ -119.0000, 0.49783, 0.98659, 0.42528 ] + - [ -114.8571, 0.42823, 1.06348, 0.42673 ] + - [ -110.7143, 0.35680, 1.13162, 0.42817 ] + - [ -106.5714, 0.28412, 1.18963, 0.42745 ] + - [ -102.4286, 0.21103, 1.23629, 0.42628 ] + - [ -98.2857, 0.13851, 1.27067, 0.42303 ] + - [ -94.1429, 0.06775, 1.29204, 0.41683 ] + - [ -90.0000, 0.00000, 1.30000, 0.41063 ] + - [ -85.8571, -0.06775, 1.29204, 0.39752 ] + - [ -81.7143, -0.13851, 1.27067, 0.38441 ] + - [ -77.5714, -0.21103, 1.23629, 0.36905 ] + - [ -73.4286, -0.28412, 1.18963, 0.35212 ] + - [ -69.2857, -0.35680, 1.13162, 0.33491 ] + - [ -65.1429, -0.42823, 1.06348, 0.31634 ] + - [ -61.0000, -0.49783, 0.98659, 0.29777 ] + - [ -56.8571, -0.56532, 0.90255, 0.27947 ] + - [ -52.7143, -0.63079, 0.81310, 0.26125 ] + - [ -48.5714, -0.69477, 0.72010, 0.24322 ] + - [ -44.4286, -0.75845, 0.62546, 0.22556 ] + - [ -40.2857, -0.82382, 0.53115, 0.20789 ] + - [ -36.1429, -0.89412, 0.43913, 0.13731 ] + - [ -32.0000, -0.97442, 0.35131, 0.06280 ] + - [ -28.0000, -1.16308, 0.20648, 0.03905 ] + - [ -24.0000, -1.14892, 0.15001, 0.01853 ] + - [ -20.0000, -1.09451, 0.10600, 0.00441 ] + - [ -18.0000, -1.05801, 0.08732, -0.00061 ] + - [ -16.0000, -1.02281, 0.07051, -0.00342 ] + - [ -14.0000, -0.99810, 0.05474, -0.00401 ] + - [ -12.0000, -0.98515, 0.04052, -0.00272 ] + - [ -10.0000, -0.89583, 0.02929, -0.01198 ] + - [ -8.0000, -0.67539, 0.02207, -0.03458 ] + - [ -6.0000, -0.43247, 0.01735, -0.05466 ] + - [ -4.0000, -0.15881, 0.01473, -0.07425 ] + - [ -2.0000, 0.13456, 0.01362, -0.09270 ] + - [ -1.0000, 0.28014, 0.01339, -0.10074 ] + - [ 0.0000, 0.42386, 0.01330, -0.10802 ] + - [ 1.0000, 0.56519, 0.01333, -0.11450 ] + - [ 2.0000, 0.70410, 0.01345, -0.12028 ] + - [ 3.0000, 0.84071, 0.01366, -0.12546 ] + - [ 4.0000, 0.97500, 0.01397, -0.13011 ] + - [ 5.0000, 1.10680, 0.01437, -0.13425 ] + - [ 6.0000, 1.23603, 0.01486, -0.13793 ] + - [ 7.0000, 1.36223, 0.01547, -0.14108 ] + - [ 8.0000, 1.48424, 0.01623, -0.14363 ] + - [ 9.0000, 1.60097, 0.01718, -0.14545 ] + - [ 10.0000, 1.71010, 0.01841, -0.14636 ] + - [ 11.0000, 1.80957, 0.02010, -0.14635 ] + - [ 12.0000, 1.89473, 0.02258, -0.14544 ] + - [ 13.0000, 1.95698, 0.02671, -0.14378 ] + - [ 14.0000, 1.98576, 0.03380, -0.14185 ] + - [ 15.0000, 1.99260, 0.04333, -0.14004 ] + - [ 16.0000, 1.99617, 0.05354, -0.13823 ] + - [ 18.0000, 1.96398, 0.07706, -0.13351 ] + - [ 20.0000, 1.81179, 0.11169, -0.13135 ] + - [ 24.0000, 1.56073, 0.19103, -0.14660 ] + - [ 28.0000, 1.46798, 0.27199, -0.17242 ] + - [ 32.0000, 1.39203, 0.35131, -0.19417 ] + - [ 36.1429, 1.27731, 0.43913, -0.21792 ] + - [ 40.2857, 1.17689, 0.53115, -0.24115 ] + - [ 44.4286, 1.08350, 0.62546, -0.25734 ] + - [ 48.5714, 0.99253, 0.72010, -0.27354 ] + - [ 52.7143, 0.90112, 0.81310, -0.28862 ] + - [ 56.8571, 0.80760, 0.90255, -0.30311 ] + - [ 61.0000, 0.71119, 0.98659, -0.31757 ] + - [ 65.1429, 0.61175, 1.06348, -0.33194 ] + - [ 69.2857, 0.50971, 1.13162, -0.34631 ] + - [ 73.4286, 0.40589, 1.18963, -0.36014 ] + - [ 77.5714, 0.30146, 1.23629, -0.37385 ] + - [ 81.7143, 0.19788, 1.27067, -0.38681 ] + - [ 85.8571, 0.09679, 1.29204, -0.39872 ] + - [ 90.0000, 0.00000, 1.30000, -0.41063 ] + - [ 94.1429, -0.06775, 1.29204, -0.41683 ] + - [ 98.2857, -0.13851, 1.27067, -0.42303 ] + - [ 102.4286, -0.21103, 1.23629, -0.42628 ] + - [ 106.5714, -0.28412, 1.18963, -0.42745 ] + - [ 110.7143, -0.35680, 1.13162, -0.42817 ] + - [ 114.8571, -0.42823, 1.06348, -0.42673 ] + - [ 119.0000, -0.49783, 0.98659, -0.42528 ] + - [ 123.1429, -0.56532, 0.90255, -0.42535 ] + - [ 127.2857, -0.63079, 0.81310, -0.42589 ] + - [ 131.4286, -0.69477, 0.72010, -0.42974 ] + - [ 135.5714, -0.75845, 0.62546, -0.43985 ] + - [ 139.7143, -0.82382, 0.53115, -0.44996 ] + - [ 143.8571, -0.89412, 0.43913, -0.46839 ] + - [ 148.0000, -0.97442, 0.35131, -0.48743 ] + - [ 150.2857, -0.90482, 0.30532, -0.49447 ] + - [ 152.5714, -0.83522, 0.26146, -0.47732 ] + - [ 154.8571, -0.76562, 0.22000, -0.46017 ] + - [ 157.1429, -0.69601, 0.18121, -0.44302 ] + - [ 159.4286, -0.62641, 0.14533, -0.42586 ] + - [ 161.7143, -0.55681, 0.11258, -0.43502 ] + - [ 164.0000, -0.48721, 0.08319, -0.45294 ] + - [ 166.2857, -0.41761, 0.05732, -0.47087 ] + - [ 168.5714, -0.34801, 0.04638, -0.48880 ] + - [ 170.8571, -0.27841, 0.04114, -0.45714 ] + - [ 173.1429, -0.20880, 0.03702, -0.34286 ] + - [ 175.4286, -0.13920, 0.03406, -0.22857 ] + - [ 177.7143, -0.06960, 0.03228, -0.11429 ] + - [ 179.9087, 0.00000, 0.03169, 0.00000 ] + - name : FFA-W3-360 # + relative_thickness : 0.36 # + data: # alpha c_l c_d c_m + - [ -179.9087, 0.00000, 0.03715, 0.00000 ] + - [ -177.7143, 0.07178, 0.03774, 0.09143 ] + - [ -175.4286, 0.14356, 0.03951, 0.18286 ] + - [ -173.1429, 0.21534, 0.04245, 0.27429 ] + - [ -170.8572, 0.28713, 0.04653, 0.36571 ] + - [ -168.5716, 0.35891, 0.05174, 0.40313 ] + - [ -166.2857, 0.43069, 0.06068, 0.40814 ] + - [ -164.0000, 0.50247, 0.08651, 0.41315 ] + - [ -161.7145, 0.57425, 0.11586, 0.41816 ] + - [ -159.4284, 0.64603, 0.14856, 0.42627 ] + - [ -157.1428, 0.71781, 0.18439, 0.44370 ] + - [ -154.8573, 0.78960, 0.22313, 0.46114 ] + - [ -152.5714, 0.86138, 0.26453, 0.47857 ] + - [ -150.2857, 0.93316, 0.30832, 0.49600 ] + - [ -148.0000, 1.00494, 0.35424, 0.48830 ] + - [ -143.8571, 0.91898, 0.44192, 0.46784 ] + - [ -139.7143, 0.84406, 0.53379, 0.44803 ] + - [ -135.5714, 0.77483, 0.62793, 0.43697 ] + - [ -131.4286, 0.70790, 0.72238, 0.42591 ] + - [ -127.2857, 0.64116, 0.81520, 0.42150 ] + - [ -123.1429, 0.57335, 0.90444, 0.42058 ] + - [ -119.0000, 0.50388, 0.98826, 0.42024 ] + - [ -114.8571, 0.43261, 1.06493, 0.42168 ] + - [ -110.7143, 0.35981, 1.13285, 0.42312 ] + - [ -106.5714, 0.28603, 1.19061, 0.42258 ] + - [ -102.4286, 0.21209, 1.23704, 0.42163 ] + - [ -98.2857, 0.13899, 1.27116, 0.41864 ] + - [ -94.1429, 0.06787, 1.29229, 0.41277 ] + - [ -90.0000, 0.00000, 1.30000, 0.40690 ] + - [ -85.8571, -0.06787, 1.29229, 0.39426 ] + - [ -81.7143, -0.13899, 1.27116, 0.38162 ] + - [ -77.5714, -0.21209, 1.23704, 0.36676 ] + - [ -73.4286, -0.28603, 1.19061, 0.35033 ] + - [ -69.2857, -0.35981, 1.13285, 0.33362 ] + - [ -65.1429, -0.43261, 1.06493, 0.31561 ] + - [ -61.0000, -0.50388, 0.98826, 0.29759 ] + - [ -56.8571, -0.57335, 0.90444, 0.27989 ] + - [ -52.7143, -0.64116, 0.81520, 0.26230 ] + - [ -48.5714, -0.70790, 0.72238, 0.24491 ] + - [ -44.4286, -0.77483, 0.62793, 0.22794 ] + - [ -40.2857, -0.84406, 0.53379, 0.21097 ] + - [ -36.1429, -0.91898, 0.44192, 0.13525 ] + - [ -32.0000, -1.00494, 0.35424, 0.05517 ] + - [ -28.0000, -1.11306, 0.20494, 0.03211 ] + - [ -24.0000, -1.05425, 0.15434, 0.01268 ] + - [ -20.0000, -0.98247, 0.10967, -0.00282 ] + - [ -18.0000, -0.94173, 0.09249, -0.00741 ] + - [ -16.0000, -0.89333, 0.07597, -0.01107 ] + - [ -14.0000, -0.85472, 0.06054, -0.01250 ] + - [ -12.0000, -0.82348, 0.04641, -0.01177 ] + - [ -10.0000, -0.79541, 0.03441, -0.01082 ] + - [ -8.0000, -0.63650, 0.02548, -0.02769 ] + - [ -6.0000, -0.39095, 0.01994, -0.05107 ] + - [ -4.0000, -0.13071, 0.01653, -0.07148 ] + - [ -2.0000, 0.16173, 0.01507, -0.09179 ] + - [ -1.0000, 0.31121, 0.01477, -0.10119 ] + - [ 0.0000, 0.45956, 0.01465, -0.10988 ] + - [ 1.0000, 0.60566, 0.01466, -0.11776 ] + - [ 2.0000, 0.74868, 0.01481, -0.12477 ] + - [ 3.0000, 0.88862, 0.01507, -0.13098 ] + - [ 4.0000, 1.02544, 0.01544, -0.13648 ] + - [ 5.0000, 1.15878, 0.01593, -0.14130 ] + - [ 6.0000, 1.28822, 0.01654, -0.14540 ] + - [ 7.0000, 1.41282, 0.01731, -0.14875 ] + - [ 8.0000, 1.53090, 0.01831, -0.15118 ] + - [ 9.0000, 1.64065, 0.01963, -0.15262 ] + - [ 10.0000, 1.73926, 0.02150, -0.15310 ] + - [ 11.0000, 1.81971, 0.02445, -0.15254 ] + - [ 12.0000, 1.87065, 0.02966, -0.15121 ] + - [ 13.0000, 1.89221, 0.03770, -0.14969 ] + - [ 14.0000, 1.87910, 0.04824, -0.14562 ] + - [ 15.0000, 1.88111, 0.05838, -0.14358 ] + - [ 16.0000, 1.86359, 0.06992, -0.14095 ] + - [ 18.0000, 1.73324, 0.10166, -0.13711 ] + - [ 20.0000, 1.59357, 0.13916, -0.14082 ] + - [ 24.0000, 1.46708, 0.21002, -0.15693 ] + - [ 28.0000, 1.44834, 0.28200, -0.17979 ] + - [ 32.0000, 1.43563, 0.35424, -0.20147 ] + - [ 36.1429, 1.31283, 0.44192, -0.22409 ] + - [ 40.2857, 1.20580, 0.53379, -0.24619 ] + - [ 44.4286, 1.10690, 0.62793, -0.26133 ] + - [ 48.5714, 1.01129, 0.72238, -0.27648 ] + - [ 52.7143, 0.91594, 0.81520, -0.29062 ] + - [ 56.8571, 0.81907, 0.90444, -0.30424 ] + - [ 61.0000, 0.71982, 0.98826, -0.31787 ] + - [ 65.1429, 0.61801, 1.06493, -0.33154 ] + - [ 69.2857, 0.51401, 1.13285, -0.34522 ] + - [ 73.4286, 0.40862, 1.19061, -0.35846 ] + - [ 77.5714, 0.30299, 1.23704, -0.37161 ] + - [ 81.7143, 0.19855, 1.27116, -0.38405 ] + - [ 85.8571, 0.09695, 1.29229, -0.39547 ] + - [ 90.0000, 0.00000, 1.30000, -0.40690 ] + - [ 94.1429, -0.06787, 1.29229, -0.41277 ] + - [ 98.2857, -0.13899, 1.27116, -0.41864 ] + - [ 102.4286, -0.21209, 1.23704, -0.42163 ] + - [ 106.5714, -0.28603, 1.19061, -0.42258 ] + - [ 110.7143, -0.35981, 1.13285, -0.42312 ] + - [ 114.8571, -0.43261, 1.06493, -0.42168 ] + - [ 119.0000, -0.50388, 0.98826, -0.42024 ] + - [ 123.1429, -0.57335, 0.90444, -0.42058 ] + - [ 127.2857, -0.64116, 0.81520, -0.42150 ] + - [ 131.4286, -0.70790, 0.72238, -0.42591 ] + - [ 135.5714, -0.77483, 0.62793, -0.43697 ] + - [ 139.7143, -0.84406, 0.53379, -0.44803 ] + - [ 143.8571, -0.91898, 0.44192, -0.46784 ] + - [ 148.0000, -1.00494, 0.35424, -0.48830 ] + - [ 150.2857, -0.93316, 0.30832, -0.49600 ] + - [ 152.5714, -0.86138, 0.26453, -0.47857 ] + - [ 154.8571, -0.78960, 0.22313, -0.46114 ] + - [ 157.1429, -0.71781, 0.18439, -0.44370 ] + - [ 159.4286, -0.64603, 0.14856, -0.42627 ] + - [ 161.7143, -0.57425, 0.11586, -0.43530 ] + - [ 164.0000, -0.50247, 0.08651, -0.45315 ] + - [ 166.2857, -0.43069, 0.06068, -0.47100 ] + - [ 168.5714, -0.35891, 0.05174, -0.48884 ] + - [ 170.8571, -0.28713, 0.04653, -0.45714 ] + - [ 173.1429, -0.21534, 0.04245, -0.34286 ] + - [ 175.4286, -0.14356, 0.03951, -0.22857 ] + - [ 177.7143, -0.07178, 0.03774, -0.11429 ] + - [ 179.9087, 0.00000, 0.03715, 0.00000 ] + + pitch_control: + GS_Angles: [0.06019804, 0.08713416, 0.10844806, 0.12685912, 0.14339822, 0.1586021 , 0.17279614, 0.18618935, 0.19892772, 0.21111989, 0.22285021, 0.23417256, 0.2451469 , 0.25580691, 0.26619545, 0.27632495, 0.28623134, 0.29593266, 0.30544521, 0.314779 , 0.32395154, 0.33297489, 0.3418577 , 0.35060844, 0.35923641, 0.36774807, 0.37614942, 0.38444655, 0.39264363, 0.40074407] + GS_Kp: [-0.9394215 , -0.80602855, -0.69555026, -0.60254912, -0.52318192, -0.45465531, -0.39489024, -0.34230736, -0.29568537, -0.25406506, -0.2166825 , -0.18292183, -0.15228099, -0.12434663, -0.09877533, -0.0752794 , -0.05361604, -0.0335789 , -0.01499149, 0.00229803, 0.01842102, 0.03349169, 0.0476098 , 0.0608629 , 0.07332812, 0.0850737 , 0.0961602 , 0.10664158, 0.11656607, 0.12597691] + GS_Ki: [-0.07416547, -0.06719673, -0.0614251 , -0.05656651, -0.0524202 , -0.04884022, -0.04571796, -0.04297091, -0.04053528, -0.03836094, -0.03640799, -0.03464426, -0.03304352, -0.03158417, -0.03024826, -0.02902079, -0.02788904, -0.02684226, -0.02587121, -0.02496797, -0.02412567, -0.02333834, -0.02260078, -0.02190841, -0.0212572 , -0.02064359, -0.0200644 , -0.01951683, -0.01899836, -0.01850671] + Fl_Kp: -9.35 + wt_ops: # operating points: wind speed [m/s], blade pitch [deg], rotor speed [rpm] + v: [3.0, 3.266896551724138, 3.533793103448276, 3.800689655172414, 4.067586206896552, 4.334482758620689, 4.601379310344828, 4.868275862068966, 5.135172413793104, 5.402068965517241, 5.6689655172413795, 5.935862068965518, 6.2027586206896554, 6.469655172413793, 6.736551724137931, 7.00344827586207, 7.270344827586207, 7.537241379310345, 7.804137931034483, 8.071034482758622, 8.337931034482759, 8.604827586206897, 8.871724137931036, 9.138620689655173, 9.405517241379311, 9.672413793103448, 9.939310344827586, 10.206206896551725, 10.473103448275863, 10.74, 11.231724137931035, 11.723448275862069, 12.215172413793104, 12.706896551724139, 13.198620689655172, 13.690344827586207, 14.182068965517242, 14.673793103448276, 15.16551724137931, 15.657241379310346, 16.14896551724138, 16.640689655172416, 17.13241379310345, 17.624137931034483, 18.11586206896552, 18.607586206896553, 19.099310344827586, 19.591034482758623, 20.082758620689653, 20.57448275862069, 21.066206896551726, 21.557931034482756, 22.049655172413793, 22.54137931034483, 23.03310344827586, 23.524827586206897, 24.016551724137933, 24.508275862068963, 25.0] + #pitch_op: [-0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, -0.25, 3.57152, 5.12896, 6.36736, 7.43866, 8.40197, 9.28843, 10.1161, 10.8974, 11.641, 12.3529, 13.038, 13.6997, 14.3409, 14.9642, 15.5713, 16.1639, 16.7435, 17.3109, 17.8673, 18.4136, 18.9506, 19.4788, 19.9989, 20.5112, 21.0164, 21.5147, 22.0067, 22.4925, 22.9724] # original + pitch_op: [3.44, 3.44, 3.44, 3.44, 3.44, 3.44, 3.19, 2.94, 2.65, 2.32, 1.97, 1.59, 1.19, 0.79, 0.38, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.46, 1.27, 1.99, 2.61, 3.05, 3.69, 5.13, 6.37, 7.44, 8.40, 9.29, 10.12, 10.90, 11.64, 12.35, 13.04, 13.70, 14.34, 14.96, 15.57, 16.16, 16.74, 17.31, 17.87, 18.41, 18.95, 19.48, 20.00, 20.51, 21.02, 21.51, 22.01, 22.49, 22.97] # updated with min pitch to achieve peak thrust shaving + omega_op: [2.1486, 2.3397, 2.5309, 2.722, 2.9132, 3.1043, 3.2955, 3.4866, 3.6778, 3.8689, 4.0601, 4.2512, 4.4424, 4.6335, 4.8247, 5.0159, 5.207, 5.3982, 5.5893, 5.7805, 5.9716, 6.1628, 6.3539, 6.5451, 6.7362, 6.9274, 7.1185, 7.3097, 7.5008, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56, 7.56] + gear_ratio: 1 + torque_control: + VS_KP: -38609162.66552 + VS_KI: -4588245.18720 + + + tower: # (could remove some entries that don't apply for the tower) + dlsMax : 5.0 # maximum node splitting section amount; can't be 0 + + name : tower # [-] an identifier (no longer has to be number) + type : 1 # [-] + rA : [ 0, 0, 15] # [m] end A coordinates + rB : [ 0, 0, 144.582] # [m] and B coordinates + shape : circ # [-] circular or rectangular + gamma : 0.0 # [deg] twist angle about the member's z-axis + + # --- outer shell including hydro--- + stations : [ 15, 28, 28.001, 41, 41.001, 54, 54.001, 67, 67.001, 80, 80.001, 93, 93.001, 106, 106.001, 119, 119.001, 132, 132.001, 144.582 ] # [-] location of stations along axis. Will be normalized such that start value maps to rA and end value to rB + d : [ 10, 9.964, 9.964, 9.967, 9.967, 9.927, 9.927, 9.528, 9.528, 9.149, 9.149, 8.945, 8.945, 8.735, 8.735, 8.405, 8.405, 7.321, 7.321, 6.5 ] # [m] diameters if circular or side lengths if rectangular (can be pairs) + t : [ 0.082954, 0.082954, 0.083073, 0.083073, 0.082799, 0.082799, 0.0299, 0.0299, 0.027842, 0.027842, 0.025567, 0.025567, 0.022854, 0.022854, 0.02025, 0.02025, 0.018339, 0.018339, 0.021211, 0.021211 ] # [m] wall thicknesses (scalar or list of same length as stations) + Cd : 0.0 # [-] transverse drag coefficient (optional, scalar or list of same length as stations) + Ca : 0.0 # [-] transverse added mass coefficient (optional, scalar or list of same length as stations) + # (neglecting axial coefficients for now) + CdEnd : 0.0 # [-] end axial drag coefficient (optional, scalar or list of same length as stations) + CaEnd : 0.0 # [-] end axial added mass coefficient (optional, scalar or list of same length as stations) + rho_shell : 7850 # [kg/m3] material density + +platforms: + + - potModMaster : 1 # [int] master switch for potMod variables; 0=keeps all member potMod vars the same, 1=turns all potMod vars to False (no HAMS), 2=turns all potMod vars to True (no strip) + dlsMax : 5.0 # maximum node splitting section amount for platform members; can't be 0 + rFair : 40.5 # platform fairlead radius + zFair : -20 # platform fairlead z-location + type : FOWT + fairleads : # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [40.5,0,-20] + headings: [270, 30, 150] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + Jtubes : # list of Jtube coordinates for the platform relative to platform coordinate and 0-degree heading + - name: Jtube1 + r_rel: [5,0,-20] + headings: [90, 210, 330] # headings in degrees for the Jtube (if multiple headings, the Jtube will be repeated for each heading) + + members: # list all members here + + - name : center_column # [-] an identifier (no longer has to be number) + type : 2 # [-] + rA : [ 0, 0, -20] # [m] end A coordinates + rB : [ 0, 0, 15] # [m] and B coordinates + shape : circ # [-] circular or rectangular + gamma : 0.0 # [deg] twist angle about the member's z-axis + potMod : True # [bool] Whether to model the member with potential flow (BEM model) plus viscous drag or purely strip theory + # --- outer shell including hydro--- + stations : [0, 1] # [-] location of stations along axis. Will be normalized such that start value maps to rA and end value to rB + d : 10.0 # [m] diameters if circular or side lengths if rectangular (can be pairs) + t : 0.05 # [m] wall thicknesses (scalar or list of same length as stations) + Cd : 0.6 # [-] transverse drag coefficient (optional, scalar or list of same length as stations) + Ca : 0.93 # [-] transverse added mass coefficient (optional, scalar or list of same length as stations) + CdEnd : 0.6 # [-] end axial drag coefficient (optional, scalar or list of same length as stations) + CaEnd : 1.0 # [-] end axial added mass coefficient (optional, scalar or list of same length as stations) + rho_shell : 7850 # [kg/m3] + # --- handling of end caps or any internal structures if we need them --- + cap_stations : [ 0 ] # [m] location along member of any inner structures (in same scaling as set by 'stations') + cap_t : [ 0.001 ] # [m] thickness of any internal structures + cap_d_in : [ 0 ] # [m] inner diameter of internal structures (0 for full cap/bulkhead, >0 for a ring shape) + + + - name : outer_column # [-] an identifier (no longer has to be number) + type : 2 # [-] + rA : [51.75, 0, -20] # [m] end A coordinates + rB : [51.75, 0, 15] # [m] and B coordinates + heading : [ 60, 180, 300] # [deg] heading rotation of column about z axis (for repeated members) + shape : circ # [-] circular or rectangular + gamma : 0.0 # [deg] twist angle about the member's z-axis + potMod : True # [bool] Whether to model the member with potential flow (BEM model) plus viscous drag or purely strip theory + # --- outer shell including hydro--- + stations : [0, 35] # [-] location of stations along axis. Will be normalized such that start value maps to rA and end value to rB + d : 12.5 # [m] diameters if circular or side lengths if rectangular (can be pairs) + t : 0.05 # [m] wall thicknesses (scalar or list of same length as stations) + Cd : 0.6 # [-] transverse drag coefficient (optional, scalar or list of same length as stations) + Ca : 0.93 # [-] transverse added mass coefficient (optional, scalar or list of same length as stations) + CdEnd : 1.0 # [-] end axial drag coefficient (optional, scalar or list of same length as stations) + CaEnd : 0.7 # value of 3.0 gives more heave response # [-] end axial added mass coefficient (optional, scalar or list of same length as stations) + rho_shell : 7850 # [kg/m3] + # --- ballast --- + l_fill : 1.4 # [m] + rho_fill : 5000 # [kg/m3] + # --- handling of end caps or any internal structures if we need them --- + cap_stations : [ 0 ] # [m] location along member of any inner structures (in same scaling as set by 'stations') + cap_t : [ 0.001 ] # [m] thickness of any internal structures + cap_d_in : [ 0 ] # [m] inner diameter of internal structures (0 for full cap/bulkhead, >0 for a ring shape) + + + - name : pontoon # [-] an identifier (no longer has to be number) + type : 2 # [-] + rA : [ 5 , 0, -16.5] # [m] end A coordinates + rB : [ 45.5, 0, -16.5] # [m] and B coordinates + heading : [ 60, 180, 300] # [deg] heading rotation of column about z axis (for repeated members) + shape : rect # [-] circular or rectangular + gamma : 0.0 # [deg] twist angle about the member's z-axis + potMod : False # [bool] Whether to model the member with potential flow (BEM model) plus viscous drag or purely strip theory + # --- outer shell including hydro--- + stations : [0, 40.5] # [-] location of stations along axis. Will be normalized such that start value maps to rA and end value to rB + d : [12.4, 7.0] # [m] diameters if circular or side lengths if rectangular (can be pairs) + t : 0.05 # [m] wall thicknesses (scalar or list of same length as stations) + Cd : [1.5, 2.2 ] # [-] transverse drag coefficient (optional, scalar or list of same length as stations) + Ca : [2.2, 0.2 ] # [-] transverse added mass coefficient (optional, scalar or list of same length as stations) + CdEnd : 0.0 # [-] end axial drag coefficient (optional, scalar or list of same length as stations) + CaEnd : 0.0 # [-] end axial added mass coefficient (optional, scalar or list of same length as stations) + rho_shell : 7850 # [kg/m3] + l_fill : 40.5 # [m] + rho_fill : 1025.0 # [kg/m3] + + + - name : upper_support # [-] an identifier (no longer has to be number) + type : 2 # [-] + rA : [ 5 , 0, 14.545] # [m] end A coordinates + rB : [ 45.5, 0, 14.545] # [m] and B coordinates + heading : [ 60, 180, 300] # [deg] heading rotation of column about z axis (for repeated members) + shape : circ # [-] circular or rectangular + gamma : 0.0 # [deg] twist angle about the member's z-axis + potMod : False # [bool] Whether to model the member with potential flow (BEM model) plus viscous drag or purely strip theory + # --- outer shell including hydro--- + stations : [0, 1] # [-] location of stations along axis. Will be normalized such that start value maps to rA and end value to rB + d : 0.91 # [m] diameters if circular or side lengths if rectangular (can be pairs) + t : 0.01 # [m] wall thicknesses (scalar or list of same length as stations) + Cd : 0.0 # [-] transverse drag coefficient (optional, scalar or list of same length as stations) + Ca : 0.0 # [-] transverse added mass coefficient (optional, scalar or list of same length as stations) + CdEnd : 0.0 # [-] end axial drag coefficient (optional, scalar or list of same length as stations) + CaEnd : 0.0 # [-] end axial added mass coefficient (optional, scalar or list of same length as stations) + rho_shell : 7850 # [kg/m3] + + +# ----- Mooring system ----- + +# Mooring system descriptions (each for an individual FOWT with no sharing) +mooring_systems: + + ms1: + name: 3 line taut poly mooring system + + keys: [MooringConfigID, heading, anchorType, fairlead] + data: + - [ rope_1, 45 , suction_pile1, 2 ] + - [ rope_1, 135 , suction_pile1, 3 ] + - [ rope_1, 270 , suction_pile1, 1 ] + + +# Mooring line configurations +mooring_line_configs: + + rope_1: # mooring line configuration identifier + + name: rope configuration 1 # descriptive name + + span: 1131.37 + + + sections: #in order from anchor to fairlead + - type: chain_155mm + length: 20 + - type: rope # ID of a mooring line section type + length: 1170 # [m] usntretched length of line section + adjustable: True # flags that this section could be adjusted to accommodate different spacings... + + +# Mooring line cross-sectional properties +mooring_line_types: + + rope: + d_nom: 0.2246 + d_vol: 0.1797 + m: 34.85 + EA: 4.761e7 + MBL: 11.75e6 + material: rope + + chain_155mm: + d_nom: 0.155 # [m] nominal diameter + d_vol: 0.279 # [m] volume-equivalent diameter + m: 480.9 # [kg/m] mass per unit length (linear density) + EA: 2058e6 # [N] quasi-static stiffness + MBL: 25.2e6 # [N] minimum breaking load + cost: 1486 # [$/m] cost per unit length + material: chain # [-] material composition descriptor + material details: R3 studless + +# Anchor type properties +anchor_types: + suction_pile1: + type : suction_pile + L : 16.4 # length of pile [m] + D : 5.45 # diameter of pile [m] + zlug : 9.32 # embedded depth of padeye [m] \ No newline at end of file diff --git a/tests/testOntology.yaml b/tests/testOntology.yaml index b554d5f0..dca64548 100644 --- a/tests/testOntology.yaml +++ b/tests/testOntology.yaml @@ -125,20 +125,20 @@ array_mooring: # - [ 5, suction1, -1900 , 0 , 2 ] line_keys : - [MooringConfigID , endA, endB, headingA, headingB, lengthAdjust] + [MooringConfigID , endA, endB, fairleadA, fairleadB, lengthAdjust] line_data : - - [ rope_shared , FOWT1, FOWT2, 270, 270, 0] - - [ rope_1 , Anch1, FOWT1, NONE, 135, 0] - - [ rope_1 , Anch1, FOWT3, NONE, 45, 0] + - [ rope_shared , FOWT1, FOWT2, 1, 1, 0] + - [ rope_1 , Anch1, FOWT1, NONE, 3, 0] + - [ rope_1 , Anch1, FOWT3, NONE, 2, 0] # - [ shared-2-clump , FOWT 2, FOWT 3, 0, 0, 0] # Array cables (compact table format, without routing info) array_cables: - keys: [ AttachA, AttachB, DynCableA, DynCableB, headingA, headingB, cableType, lengthAdjust] + keys: [ AttachA, AttachB, DynCableA, DynCableB, JtubeA, JtubeB, headingA, headingB, cableType, lengthAdjust] data: - - [ FOWT1, FOWT3, suspended_1, NONE, 180, 0, NONE, 0] - - [ FOWT3, FOWT4, lazy_wave1, lazy_wave1, 90, 90, static_cable_36, 0] + - [ FOWT1, FOWT3, suspended_1, NONE, 2, 3, 180, 0, NONE, 0] + - [ FOWT3, FOWT4, lazy_wave1, lazy_wave1, 3, 1, 285, 80, static_cable_36, 0] # ----- turbines and platforms ----- @@ -1203,6 +1203,14 @@ platforms: rFair : 40.5 # platform fairlead radius zFair : -20 # platform fairlead z-location type : FOWT + fairleads : # list of fairlead coordinates for the platform relative to platform coordinate and 0-degree heading + - name: fairlead1 + r_rel: [40.5,0,-20] + headings: [270, 30, 150] # headings in degrees for the fairlead (if multiple headings, the fairlead will be repeated for each heading) + Jtubes : # list of Jtube coordinates for the platform relative to platform coordinate and 0-degree heading + - name: Jtube1 + r_rel: [5, 0, -20] + headings: [90, 210, 330] # headings in degrees for the Jtube (if multiple headings, the Jtube will be repeated for each heading) members: # list all members here @@ -1302,34 +1310,34 @@ mooring_systems: ms1: name: 3-line semi-taut polyester mooring system with one line shared anchor - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 270 , suction_pile1, 0 ] - - [ rope_1, 135 , suction_pile1, 0 ] + - [ rope_1, 270 , suction_pile1, 1 ] + - [ rope_1, 135 , suction_pile1, 3 ] ms2: name: 2-line semitaut with a third shared line - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 45 , suction_pile1, 0 ] - - [ rope_1, 135 , suction_pile1, 0 ] + - [ rope_1, 45 , suction_pile1, 2 ] + - [ rope_1, 135 , suction_pile1, 3 ] ms3: name: 3-line semi-taut polyester mooring system with one line shared anchor and one shared line - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 45 , suction_pile1, 0 ] + - [ rope_1, 45 , suction_pile1, 2 ] ms4: name: 3 line taut poly mooring system - keys: [MooringConfigID, heading, anchorType, lengthAdjust] + keys: [MooringConfigID, heading, anchorType, fairlead] data: - - [ rope_1, 45 , suction_pile1, 0 ] - - [ rope_1, 135 , suction_pile1, 0 ] - - [ rope_1, 270 , suction_pile1, 0 ] + - [ rope_1, 45 , suction_pile1, 2 ] + - [ rope_1, 135 , suction_pile1, 3 ] + - [ rope_1, 270 , suction_pile1, 1 ] # Mooring line configurations @@ -1524,10 +1532,12 @@ cables: attachID: FOWT1 # FOWT/substation/junction ID heading: 270 # [deg] heading of attachment at end A dynamicID: lazy_wave1 # ID of dynamic cable configuration at this end + Jtube: 2 endB: attachID: FOWT2 heading: 270 dynamicID: lazy_wave1 + Jtube: 2 routing_x_y_r: - [700,150,20] # [x (m), y (m), radius (m)] - [900,150,20] diff --git a/tests/test_anchors.py b/tests/test_anchors.py index 0292c66a..ae177770 100644 --- a/tests/test_anchors.py +++ b/tests/test_anchors.py @@ -2,12 +2,16 @@ from famodel.project import Project import numpy as np import os +import matplotlib.pyplot as plt +import pytest +@pytest.fixture +def project(): + dir = os.path.dirname(os.path.realpath(__file__)) + return(Project(file=os.path.join(dir,'testOntology.yaml'), raft=False)) -def test_anchor_loads(): +def test_anchor_loads(project): # load in famodel project - dir = os.path.dirname(os.path.realpath(__file__)) - project = Project(file=os.path.join(dir,'testOntology.yaml'), raft=False) project.getMoorPyArray(cables=1) anch = project.anchorList['FOWT1a'] @@ -18,10 +22,8 @@ def test_anchor_loads(): assert('Hm' in anch.loads) assert(anch.loads['Ha'] != anch.loads['Hm']) -def test_anchor_capacities(): +def test_anchor_capacities(project): # load in famodel project (suction pile anchor) - dir = os.path.dirname(os.path.realpath(__file__)) - project = Project(file=os.path.join(dir,'testOntology.yaml'), raft=False) project.getMoorPyArray(cables=1) anch = project.anchorList['FOWT1a'] diff --git a/tests/test_integrations.py b/tests/test_integrations.py index d028035e..ced4120b 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -15,10 +15,12 @@ import os - -def test_MoorPy_integration(): +@pytest.fixture +def project(): dir = os.path.dirname(os.path.realpath(__file__)) - project = Project(file=os.path.join(dir,'testOntology.yaml'), raft=False) + return(Project(file=os.path.join(dir,'testOntology.yaml'), raft=False)) + +def test_MoorPy_integration(project): project.getMoorPyArray(cables=1,plt=1) # check a random mooring line for ss assert project.mooringList['FOWT1a'].ss is not None @@ -30,12 +32,11 @@ def test_RAFT_integration(): '''def test_FLORIS_integration():''' -def test_lineDesign_integration(): +def test_lineDesign_integration(project): # make a dummy design dictionary for Mooring to make a Subsystem with - dd = dict(sections={}, connectors={}) - dd['sections'] = [{} for i in range(1)] - dd['connectors'] = [{} for i in range(2)] + dd = dict(subcomponents=[]) + dd['subcomponents'] = [{} for i in range(3)] import moorpy as mp ms = mp.System(depth=200) # the sizing function coefficients to use in the design @@ -44,8 +45,8 @@ def test_lineDesign_integration(): # Assign section properties for use in Mooring's Subsystem.makeGeneric call for i in range(1): - dd['sections'][i]['type'] = lineProps[i] - dd['sections'][i]['L'] = lengths[i] + dd['subcomponents'][i+1]['type'] = lineProps[i] + dd['subcomponents'][i+1]['L'] = lengths[i] # # Assign props for intermediate points/connectors # for i in range(self.nLines-1): diff --git a/tests/test_moorings.py b/tests/test_moorings.py new file mode 100644 index 00000000..5dcc855d --- /dev/null +++ b/tests/test_moorings.py @@ -0,0 +1,169 @@ + +""" +Test mooring loading, configurations, methods +""" +import pytest +from pytest import approx + +import numpy as np +from numpy.testing import assert_allclose + +from famodel.project import Project + +from famodel.platform.fairlead import Fairlead +from famodel.platform.platform import Platform +from famodel.mooring.connector import Section, Connector +from famodel.famodel_base import Node, Edge + +import os + + +@pytest.fixture +def setup_project(): + dir = os.path.dirname(os.path.realpath(__file__)) + return(Project(file=os.path.join(dir,'mooring_ontology.yaml'), raft=False)) + + +def test_num_moorings(setup_project): + + assert(len(setup_project.mooringList)==11) + +def test_moor_heading(setup_project): + + moor = setup_project.mooringList['FOWT1a'] + dists = moor.rA[:2]-moor.rB[:2] + heading = np.pi/2 - np.arctan2(dists[1], dists[0]) + pf = setup_project.platformList['FOWT1'] + assert(heading == approx(np.radians(45+180),abs=1e-5)) + assert(heading == approx(pf.mooring_headings[0]+pf.phi,abs=1e-5)) + assert(heading == approx(np.radians(moor.heading),abs=1e-5)) + +def test_platform_connection(setup_project): + + moor = setup_project.mooringList['FOWT1a'] + assert(moor.attached_to[0]==setup_project.anchorList['FOWT1a']) + assert(moor.attached_to[1]==setup_project.platformList['FOWT1']) + +def test_fairlead_connection(setup_project): + + end_sub = setup_project.mooringList['FOWT1a'].subcomponents[-1] + assert(len(end_sub.attachments)==2) + assert(np.any([isinstance(att['obj'], Fairlead) for att in end_sub.attachments.values()])) + +def test_fairlead_position(setup_project): + moor = setup_project.mooringList['FOWT1a'] + fl = moor.subcomponents[-1].attachments['FOWT1_F1']['obj'] + # check fairlead is at same position as moor.rB + assert_allclose(fl.r,setup_project.mooringList['FOWT1a'].rB) + pf = setup_project.platformList['FOWT1'] + new_head = 15 + pf.setPosition(r = pf.r, + heading=new_head, + degrees=True, + project=setup_project) + head_fl = np.radians(90-30) # relative heading of fairlead to platform + head_pf = pf.phi + # calculate moor.rB manually and compare to fairlead position + # heading is 90-compass fairlead heading - platform heading + # ( compass platform heading = - unit circle platform heading ) + # this is because platform 0 is the same for both since it's user-defined + # but the direction they rotate is opposite + # but mooring headings are based on conventional 0s for compass and unit circle + # AND their rotation direction is opposite + assert_allclose(fl.r,[58*np.cos(head_fl-head_pf), + 58*np.sin(head_fl-head_pf),-14]) + +def test_rA_depth(setup_project): + moor = setup_project.mooringList['FOWT1a'] + loc = moor.rA + true_depth = setup_project.getDepthAtLocation(loc[0],loc[1]) + assert(moor.rA[2] == -true_depth) + assert(moor.dd['zAnchor'] == -true_depth) + assert(moor.z_anch == -true_depth) + setup_project.getMoorPyArray() + assert(moor.ss.rA[2] == -true_depth) + +''' + +def test_end_locs(self): + moor = self.project.mooringList['fowt1a'] + assert(moor.rB == ) + assert(moor.rA == ) +''' + +def test_num_sections(setup_project): + moor = setup_project.mooringList['FOWT1a'] + setup_project.getMoorPyArray() + assert(len(moor.i_sec)==len(moor.ss.lineList)) + assert(len(moor.i_sec)==2) + +def test_num_connectors(setup_project): + moor = setup_project.mooringList['FOWT1a'] + assert(len(moor.i_con)==3) + +def test_shared_connections(setup_project): + + moor = setup_project.mooringList['FOWT1-FOWT2'] + assert(len(moor.subcomponents[0].attachments)==2) + assert(np.any([isinstance(att['obj'], Fairlead) for att in moor.subcomponents[0].attachments.values()])) + assert(isinstance(moor.attached_to[0], Platform)) + assert(isinstance(moor.attached_to[1], Platform)) + +def test_shared_flag(setup_project): + + moor = setup_project.mooringList['FOWT1-FOWT2'] + assert(moor.shared == 1) + +# - - - -tests in progress- - - - + +@pytest.fixture +def bridle_project(): + dir = os.path.dirname(os.path.realpath(__file__)) + return(Project(file=os.path.join(dir,'mooring_ontology_parallels.yaml'), raft=False)) + +def test_bridle_setup(bridle_project): + moor = bridle_project.mooringList['FOWT2a'] + # check subcons_B is a list of length 2 + assert(len(moor.subcons_B)==2) + # check each item in subcons_B is attached to 2 things (fairlead and another subcomponent) + for sub in moor.subcons_B: + assert(isinstance(sub,Connector)) + assert(len(sub.attachments)==2) + for att in sub.attachments.values(): + assert(isinstance(att['obj'],(Fairlead,Section))) + pf = moor.attached_to[1] + fl_attachment = [False, False] + for i,sub in enumerate(moor.subcons_B): + for att in pf.attachments.values(): + if isinstance(att['obj'],Node) and sub.id in att['obj'].attachments: + fl_attachment[i] = True + + assert(all(fl_attachment)) + +def test_bridle_end_locs(bridle_project): + moor = bridle_project.mooringList['FOWT1a'] + # check rB is at midpoint of fairlead locs + fl_locs = [] + for sub in moor.subcons_B: + att = [att['obj'] for att in sub.attachments.values() if isinstance(att['obj'],Fairlead)] + fl_locs.append(att[0].r) + from famodel.helpers import calc_midpoint + midpoint = calc_midpoint(fl_locs) + assert_allclose(midpoint, moor.rB) + # check + # check location of anchor is correct + u = np.array([np.cos(np.radians(90-moor.heading)),np.sin(np.radians(90-moor.heading))]) + anch_loc = np.hstack((np.array(midpoint[:2])+moor.span*u,-bridle_project.depth)) + assert_allclose(anch_loc, moor.rA) + + + + +''' +def test_shared_depth(self): + + moor = self.project.mooringList['fowt1b'] + self.project.getMoorPyArray() + assert(moor.ss) +''' + diff --git a/tests/test_platform.py b/tests/test_platform.py new file mode 100644 index 00000000..d16365c7 --- /dev/null +++ b/tests/test_platform.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Jul 14 13:33:12 2025 + +@author: lsirkis +""" +import pytest +import os +from famodel import Project +import numpy as np +from copy import deepcopy +from numpy.testing import assert_allclose + +@pytest.fixture +def project(): + dir = os.path.dirname(os.path.realpath(__file__)) + return(Project(file=os.path.join(dir,'platform_ontology.yaml'), raft=False)) + +def test_platform_location(project): + assert_allclose(project.platformList['FOWT2'].r, [1600, 0, 0]) + +def test_basic_pf_relocation(project): + project.platformList['FOWT1'].setPosition(r=[20, 20, -10], heading=30, degrees=True) + assert_allclose(project.platformList['FOWT1'].r,[20, 20, -10]) + assert pytest.approx(project.platformList['FOWT1'].phi) == np.radians(30) + +def test_fl_relloc(project): + fl = project.platformList['FOWT2'].attachments['FOWT2_F2'] + fl_loc = [np.cos(np.radians(60))*40.5, + np.sin(np.radians(60))*40.5, + -20] + assert_allclose(fl['r_rel'],fl_loc) + +def test_pf_relocation(project): + new_r = [1500, 1500] + new_head = 15 + moor = project.mooringList['FOWT2a'] + moor_head_start = deepcopy(project.mooringList['FOWT2a'].heading) + fl = project.platformList['FOWT2'].attachments['FOWT2_F2'] + project.platformList['FOWT2'].setPosition(r = new_r, + heading=new_head, + degrees=True, + project=project) + assert pytest.approx(moor.heading) == new_head+moor_head_start + fl_loc_new = [1500 + np.cos(np.radians(60-new_head))*40.5, + 1500 + np.sin(np.radians(60-new_head))*40.5, + -20] + fl = project.platformList['FOWT2'].attachments['FOWT2_F2'] + assert_allclose(fl['obj'].r, fl_loc_new) + moor_head_new = np.radians(90-(new_head+moor_head_start)) + new_x = fl['obj'].r[0] + np.cos(moor_head_new)*moor.span + new_y = fl['obj'].r[1] + np.sin(moor_head_new)*moor.span + new_anch_r = [new_x, + new_y, + -project.getDepthAtLocation(new_x,new_y)] + project.plot2d() + assert_allclose(project.anchorList['FOWT2a'].r, new_anch_r) + + + + diff --git a/tests/test_project.py b/tests/test_project.py index 31c92133..61aac3f9 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -108,18 +108,20 @@ def test_check_connections(): # check number of things connected to platform if i == 1 or i == 3: - assert len(pf.attachments) == 5 # 3 lines, 1 turbine, 1 cable + assert len(pf.attachments) == 11 # 3 lines, 1 turbine, 1 cable , 3 fairleads, 3 j-tubes else: - assert len(pf.attachments) == 6 # 3 lines, 1 turbine, 2 cables + assert len(pf.attachments) == 12 # 3 lines, 1 turbine, 2 cables, 3 fairleads, 3 j-tubes def test_headings_repositioning(): dir = os.path.dirname(os.path.realpath(__file__)) project = Project(file=os.path.join(dir,'testOntology.yaml'), raft=False) # check angles and repositioning for regular mooring line and shared mooring line, reg cable and suspended cable assert_allclose(np.hstack((project.mooringList['FOWT1a'].rA,project.mooringList['FOWT1-FOWT2'].rA)), - np.hstack(([-828.637,-828.637,-600],[40.5,0,-20])),rtol=0,atol=0.5) + np.hstack(([-820.25,-835.07,-600],[40.5,0,-20])),rtol=0,atol=0.5) + x_off = 5*np.cos(np.radians(-60)) + y_off = 5*np.sin(np.radians(-60)) assert_allclose(np.hstack((project.cableList['array_cable12'].subcomponents[0].rB,project.cableList['cable0'].subcomponents[0].rB)), - np.hstack(([605,0,-600],[0,1615.5,-20])),rtol=0,atol=0.5) + np.hstack(([600+x_off,0+y_off,-600],[0+x_off,1656+y_off,-20])),rtol=0,atol=0.5) def test_marine_growth(): dir = os.path.dirname(os.path.realpath(__file__))