Skip to content
LIBRARY
examples.FloatingWindTurbine.md

examples.FloatingWindTurbine

Floating wind turbine moored to the seabed by four cables.

A BuoyantBody spar platform floats in a traveling surface wave and carries a WindTurbine on a tower. The turbine is mounted through a vertical-axis revolute yaw joint at the tower top. A proportional yaw controller with low bandwidth drives the yaw joint torque from the turbine's heading-error signal sin_err, slowly orienting the rotor toward the current wind direction; a yaw bearing damper lets the pure P controller settle. The wind direction varies slowly and sinusoidally with time.

Four mooring cables run from fairleads on the spar (below the waterline) to anchors fixed on the seabed, arranged symmetrically around the platform. Each mooring line is a Cable with n_segments segments, iscut = true (the last segment is a SphericalSpherical rod that closes the kinematic loop at the anchor with a single distance constraint carrying only axial force), and initialize_orientation = false (a straight cable is not a consistent initial condition for the loop, so initialization solves for the joint orientations; provide off-identity guesses for the joint quaternions). The total cable length includes a few percent of slack relative to the straight fairlead-anchor distance so the mooring is compliant. Each cable link carries a linear water drag evaluated relative to the wave orbital velocity (supplied through the cables' velocity field ports), which damps the mooring swinging. n_segments must be at least 2.

The passing waves excite heave, surge, and pitch of the platform, while the rotor thrust leans the platform downwind and the controller tracks the wind. Initialization: the platform orientation carries no initial conditions and the angular accelerations of the platform and of every cable joint are zero at init (initial equations), so initialization solves for the static equilibrium orientation and mooring hang shape; the platform position starts at the equilibrium draft (balanced initialization; the resulting state does not depend on the guesses). The model carries guesses with a small bend away from identity for every cable joint quaternion (slightly out of each cable's vertical plane) so the initialization solver starts away from the singular identity configuration for any n_segments; use a least-squares initialization algorithm, since the quaternion-Spherical cable chains carry redundant quaternion-norm equations. Simulate with optimize = :basic (also passed to the initialization system via initsys_mtkcompile_kwargs = (; optimize = :basic), so its torn linear blocks use the robust linear solver too) and a solver that handles mass matrices, e.g. FBDF.

Usage

MultibodyComponents.examples.FloatingWindTurbine(length_spar=10, tower_height=15, depth=20, anchor_radius=15, fairlead_radius=1, fairlead_drop=2, cable_length=22, cable_mass=500, d_drag=300, wave_k=wave_omega ^ 2 / 9.80665, kp_yaw=1000, d_yaw=3000, theta_w_amplitude=0.6, theta_w_frequency=0.02, wave_amplitude=0.5, wave_omega=0.9)

Parameters:

NameDescriptionUnitsDefault value
n_segmentsNumber of segments in each mooring cable, including the loop-closing rod3
length_sparLength of the platform sparm10
tower_heightHeight of the turbine tower from the spar top to the yaw bearingm15
depthWater depth (seabed at y = -depth)m20
anchor_radiusHorizontal distance from the platform center to each anchorm15
fairlead_radiusHorizontal distance from the spar axis to each fairleadm1
fairlead_dropDistance below the spar center at which the fairleads attachm2
cable_lengthTotal length of each mooring cablem22
cable_massTotal mass of each mooring cablekg500
d_dragLinear water drag coefficient per cable link300
kp_yawProportional gain of the yaw controller1000
d_yawDamping coefficient of the yaw bearing3000
theta_w_amplitudeAmplitude of the wind direction variation0.6
theta_w_frequencyFrequency of the wind direction variation0.02
wave_amplitudeAmplitude of the surface wavem0.5
wave_omegaAngular frequency of the surface wave0.9

Variables

NameDescriptionUnits
heavePlatform heave (vertical position of the spar center)
surgePlatform surge (downwind position of the spar center)
water_levelWave elevation at the world origin, used to place the rendered water plane

Behavior

Source

dyad
"""
Floating wind turbine moored to the seabed by four cables.

A `BuoyantBody` spar platform floats in a traveling surface wave and carries
a `WindTurbine` on a tower. The turbine is mounted through a vertical-axis
revolute yaw joint at the tower top. A proportional yaw controller with low
bandwidth drives the yaw joint torque from the turbine's heading-error signal
`sin_err`, slowly orienting the rotor toward the current wind direction; a
yaw bearing damper lets the pure P controller settle. The wind direction
varies slowly and sinusoidally with time.

Four mooring cables run from fairleads on the spar (below the waterline) to
anchors fixed on the seabed, arranged symmetrically around the platform. Each
mooring line is a `Cable` with `n_segments` segments, `iscut = true` (the
last segment is a `SphericalSpherical` rod that closes the kinematic loop at
the anchor with a single distance constraint carrying only axial force), and
`initialize_orientation = false` (a straight cable is not a consistent
initial condition for the loop, so initialization solves for the joint
orientations; provide off-identity guesses for the joint quaternions). The
total cable length includes a few percent of slack relative to the straight
fairlead-anchor distance so the mooring is compliant. Each cable link
carries a linear water drag evaluated relative to the wave orbital velocity
(supplied through the cables' velocity field ports), which damps the
mooring swinging. `n_segments` must be at least 2.

The passing waves excite heave, surge, and pitch of the platform, while the
rotor thrust leans the platform downwind and the controller tracks the wind.
Initialization: the platform orientation carries no initial conditions and
the angular accelerations of the platform and of every cable joint are zero
at init (initial equations), so initialization solves for the static
equilibrium orientation and mooring hang shape; the platform position starts
at the equilibrium draft (balanced initialization; the resulting state does
not depend on the guesses). The model carries guesses with a small bend away from identity for every
cable joint quaternion (slightly out of each cable's vertical plane) so the
initialization solver starts away from the singular identity configuration
for any `n_segments`; use a least-squares initialization algorithm, since
the quaternion-Spherical cable chains carry redundant quaternion-norm
equations.
Simulate with `optimize = :basic` (also passed to the initialization system
via `initsys_mtkcompile_kwargs = (; optimize = :basic)`, so its torn linear
blocks use the robust linear solver too) and a solver that handles mass
matrices, e.g. `FBDF`.
"""
example component FloatingWindTurbine
  world = MultibodyComponents.World(nominal_length = 10, render = false) {
    "Dyad": {"placement": {"diagram": {"x1": 720, "x2": 820, "y1": 710, "y2": 810}}}
  }
  platform = BuoyantBody(length_spar = length_spar, wave_amplitude = wave_amplitude, wave_omega = wave_omega, r_0_start = [0, -3.2, 0], initialize_orientation = false, m = 4e4, r_cm = [0, -4, 0], radius_spar = 1.3, I_11 = 4e5, I_33 = 4e5, d_translation = 6e4, d_rotation = 1e6) {
    "Dyad": {"placement": {"diagram": {"x1": 280, "x2": 540, "y1": 630, "y2": 890}}}
  }
  tower_mount = MultibodyComponents.FixedTranslation(r = [0, length_spar / 2 + tower_height, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 360, "x2": 460, "y1": 550, "y2": 650, "rot": 270}}
    }
  }
  yaw_joint = MultibodyComponents.Revolute(n = [0, 1, 0], statePriority = 20, phi(initial = 0), w(initial = 0), render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 360, "x2": 460, "y1": 440, "y2": 540, "rot": 270}}
    }
  }
  turbine = WindTurbine(tower_height = tower_height, V_wind = 10) {
    "Dyad": {"placement": {"diagram": {"x1": 260, "x2": 560, "y1": 110, "y2": 410}}}
  }
  yaw_drive = RotationalComponents.Sources.TorqueSource() {
    "Dyad": {"placement": {"diagram": {"x1": 720, "x2": 620, "y1": 280, "y2": 380}}}
  }
  yaw_damper = RotationalComponents.Components.Damper(d = d_yaw) {
    "Dyad": {"placement": {"diagram": {"x1": 550, "x2": 650, "y1": 440, "y2": 540}}}
  }
  wind_dir = BlockComponents.Sources.Sine(amplitude = theta_w_amplitude, frequency = theta_w_frequency) {
    "Dyad": {"placement": {"diagram": {"x1": 120, "x2": 220, "y1": 160, "y2": 260}}}
  }
  yaw_gain = BlockComponents.Math.Gain(k = kp_yaw) {
    "Dyad": {"placement": {"diagram": {"x1": 620, "x2": 720, "y1": 160, "y2": 260}}}
  }
  fairlead1 = MultibodyComponents.FixedTranslation(r = [fairlead_radius, -fairlead_drop, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 210, "x2": 310, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  fairlead2 = MultibodyComponents.FixedTranslation(r = [-fairlead_radius, -fairlead_drop, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 310, "x2": 410, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  fairlead3 = MultibodyComponents.FixedTranslation(r = [0, -fairlead_drop, fairlead_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 410, "x2": 510, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  fairlead4 = MultibodyComponents.FixedTranslation(r = [0, -fairlead_drop, -fairlead_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 500, "x2": 600, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  anchor1 = MultibodyComponents.Fixed(r = [anchor_radius, -depth, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 310, "x2": 210, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  anchor2 = MultibodyComponents.Fixed(r = [-anchor_radius, -depth, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 410, "x2": 310, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  anchor3 = MultibodyComponents.Fixed(r = [0, -depth, anchor_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 510, "x2": 410, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  anchor4 = MultibodyComponents.Fixed(r = [0, -depth, -anchor_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 600, "x2": 500, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  cable1 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.196, 0, 0.98], guess_turn = 1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [14, -16, 0], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 210, "x2": 310, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  cable2 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.196, 0, 0.98], guess_turn = -1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [-14, -16, 0], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 310, "x2": 410, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  cable3 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.98, 0, 0.196], guess_turn = -1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [0, -16, 14], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 410, "x2": 510, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  cable4 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.98, 0, 0.196], guess_turn = 1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [0, -16, -14], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 500, "x2": 600, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  water_vis = MultibodyComponents.BoxVisualizer(color = [0.1, 0.4, 0.8, 0.4], length = 60, width = 60, height = 0.05, length_direction = [1, 0, 0], width_direction = [0, 0, 1], r_shape = [-30, water_level, 0]) {
    "Dyad": {"placement": {"diagram": {"x1": 830, "x2": 930, "y1": 630, "y2": 730}}}
  }
  seabed_vis = MultibodyComponents.BoxVisualizer(color = [0.76, 0.7, 0.5, 1], length = 60, width = 60, height = 0.2, length_direction = [1, 0, 0], width_direction = [0, 0, 1], r_shape = [-30, -depth - 0.1, 0]) {
    "Dyad": {"placement": {"diagram": {"x1": 830, "x2": 930, "y1": 800, "y2": 900}}}
  }
  "Number of segments in each mooring cable, including the loop-closing rod"
  structural parameter n_segments::Integer = 3
  "Length of the platform spar"
  parameter length_spar::Length = 10
  "Height of the turbine tower from the spar top to the yaw bearing"
  parameter tower_height::Length = 15
  "Water depth (seabed at y = -depth)"
  parameter depth::Length = 20
  "Horizontal distance from the platform center to each anchor"
  parameter anchor_radius::Length = 15
  "Horizontal distance from the spar axis to each fairlead"
  parameter fairlead_radius::Length = 1
  "Distance below the spar center at which the fairleads attach"
  parameter fairlead_drop::Length = 2
  "Total length of each mooring cable"
  parameter cable_length::Length = 22
  "Total mass of each mooring cable"
  parameter cable_mass::Mass = 500
  "Linear water drag coefficient per cable link"
  parameter d_drag::Real = 300
  "Wave number from the deep-water dispersion relation"
  final parameter wave_k::Real = wave_omega ^ 2 / 9.80665
  "Proportional gain of the yaw controller"
  parameter kp_yaw::Real = 1000
  "Damping coefficient of the yaw bearing"
  parameter d_yaw::Real = 3000
  "Amplitude of the wind direction variation"
  parameter theta_w_amplitude::Real = 0.6
  "Frequency of the wind direction variation"
  parameter theta_w_frequency::Real = 0.02
  "Amplitude of the surface wave"
  parameter wave_amplitude::Length = 0.5
  "Angular frequency of the surface wave"
  parameter wave_omega::Real = 0.9
  "Platform heave (vertical position of the spar center)"
  variable heave::Real
  "Platform surge (downwind position of the spar center)"
  variable surge::Real
  "Wave elevation at the world origin, used to place the rendered water plane"
  variable water_level::Real
relations
  connect(platform.frame_a, tower_mount.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(tower_mount.frame_b, yaw_joint.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(yaw_joint.frame_b, turbine.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(yaw_drive.spline, yaw_joint.axis, yaw_damper.spline_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [{"x": 530, "y": 330}], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": 3, "M": [], "E": -1}
      ],
      "junctions": [{"x": 530, "y": 490}]
    }
  }
  cable3.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable3.field.r[1]) * exp(wave_k * min(cable3.field.r[2], 0)), 0, 0]
  cable4.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable4.field.r[1]) * exp(wave_k * min(cable4.field.r[2], 0)), 0, 0]
  connect(fairlead2.frame_a, platform.frame_a, fairlead1.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 410, "y": 920}], "E": 2},
        {"S": 3, "M": [{"x": 260, "y": 920}], "E": -1}
      ],
      "junctions": [{"x": 360, "y": 920}]
    }
  }
  connect(fairlead3.frame_a, platform.frame_a, fairlead4.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 410, "y": 920}], "E": 2},
        {"S": 3, "M": [{"x": 550, "y": 920}], "E": -1}
      ],
      "junctions": [{"x": 460, "y": 920}]
    }
  }
  connect(fairlead1.frame_b, cable1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(fairlead2.frame_b, cable2.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable2.frame_b, anchor2.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable1.frame_b, anchor1.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(anchor3.frame_b, cable3.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(anchor4.frame_b, cable4.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable4.frame_a, fairlead4.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable3.frame_a, fairlead3.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(yaw_drive.support, yaw_damper.spline_b) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 670, "y": 490}], "E": 2}]}}
  connect(yaw_joint.support, yaw_damper.spline_b) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 450, "y": 525},
            {"x": 450, "y": 570},
            {"x": 670, "y": 570},
            {"x": 670, "y": 490}
          ],
          "E": 2
        }
      ]
    }
  }
  connect(seabed_vis.frame_a, world.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 880, "y": 760}], "E": 2}]}}
  connect(water_vis.frame_a, world.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 880, "y": 760}], "E": 2}]}}
  connect(wind_dir.y, turbine.theta_w) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 281, "y": 210}], "E": 2}]}}
  connect(turbine.sin_err, yaw_gain.u) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 501.25, "y": 209}, {"x": 501.25, "y": 210}], "E": 2}]
    }
  }
  connect(yaw_gain.y, yaw_drive.tau) {
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 760, "y": 210}, {"x": 760, "y": 330}], "E": 2}]}
  }
  heave = platform.frame_a.r_0[2]
  surge = platform.frame_a.r_0[1]
  water_level = wave_amplitude * sin(wave_omega * time)
  # Equilibrium initialization: zero angular acceleration of every cable joint
  # selects the static hanging shape of the mooring lines, making the
  # initialization balanced instead of underdetermined (the joint orientations
  # have no initial conditions and would otherwise be free)
  for j in 1:n_segments - 1
    initial cable1.joints[j].z_a = [0, 0, 0]
    initial cable2.joints[j].z_a = [0, 0, 0]
    initial cable3.joints[j].z_a = [0, 0, 0]
    initial cable4.joints[j].z_a = [0, 0, 0]
    # Pin the scale gauge of the non-unit joint quaternions: the dynamics only
    # depend on the normalized quaternion, so without this the initialization
    # residual is flat along each Q_hat scale direction
    initial cable1.joints[j].n_q = 1
    initial cable2.joints[j].n_q = 1
    initial cable3.joints[j].n_q = 1
    initial cable4.joints[j].n_q = 1
  end
  # Rotational equilibrium of the platform at initialization: the orientation
  # carries no initial conditions (solved by initialization together with the
  # mooring shape); the position is pinned to the equilibrium draft
  initial platform.body.z_a = [0, 0, 0]
  initial platform.body.n_q = 1
  # Wave orbital velocity as the external field for the cable drag, evaluated
  # at each cable's midpoint and decaying exponentially with depth
  cable1.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable1.field.r[1]) * exp(wave_k * min(cable1.field.r[2], 0)), 0, 0]
  cable2.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable2.field.r[1]) * exp(wave_k * min(cable2.field.r[2], 0)), 0, 0]
metadata {"Dyad": {"labels": [{"label": "$(instance)", "x": 500, "y": 1100, "rot": 0}]}}
end
Flattened Source
dyad
"""
Floating wind turbine moored to the seabed by four cables.

A `BuoyantBody` spar platform floats in a traveling surface wave and carries
a `WindTurbine` on a tower. The turbine is mounted through a vertical-axis
revolute yaw joint at the tower top. A proportional yaw controller with low
bandwidth drives the yaw joint torque from the turbine's heading-error signal
`sin_err`, slowly orienting the rotor toward the current wind direction; a
yaw bearing damper lets the pure P controller settle. The wind direction
varies slowly and sinusoidally with time.

Four mooring cables run from fairleads on the spar (below the waterline) to
anchors fixed on the seabed, arranged symmetrically around the platform. Each
mooring line is a `Cable` with `n_segments` segments, `iscut = true` (the
last segment is a `SphericalSpherical` rod that closes the kinematic loop at
the anchor with a single distance constraint carrying only axial force), and
`initialize_orientation = false` (a straight cable is not a consistent
initial condition for the loop, so initialization solves for the joint
orientations; provide off-identity guesses for the joint quaternions). The
total cable length includes a few percent of slack relative to the straight
fairlead-anchor distance so the mooring is compliant. Each cable link
carries a linear water drag evaluated relative to the wave orbital velocity
(supplied through the cables' velocity field ports), which damps the
mooring swinging. `n_segments` must be at least 2.

The passing waves excite heave, surge, and pitch of the platform, while the
rotor thrust leans the platform downwind and the controller tracks the wind.
Initialization: the platform orientation carries no initial conditions and
the angular accelerations of the platform and of every cable joint are zero
at init (initial equations), so initialization solves for the static
equilibrium orientation and mooring hang shape; the platform position starts
at the equilibrium draft (balanced initialization; the resulting state does
not depend on the guesses). The model carries guesses with a small bend away from identity for every
cable joint quaternion (slightly out of each cable's vertical plane) so the
initialization solver starts away from the singular identity configuration
for any `n_segments`; use a least-squares initialization algorithm, since
the quaternion-Spherical cable chains carry redundant quaternion-norm
equations.
Simulate with `optimize = :basic` (also passed to the initialization system
via `initsys_mtkcompile_kwargs = (; optimize = :basic)`, so its torn linear
blocks use the robust linear solver too) and a solver that handles mass
matrices, e.g. `FBDF`.
"""
example component FloatingWindTurbine
  world = MultibodyComponents.World(nominal_length = 10, render = false) {
    "Dyad": {"placement": {"diagram": {"x1": 720, "x2": 820, "y1": 710, "y2": 810}}}
  }
  platform = BuoyantBody(length_spar = length_spar, wave_amplitude = wave_amplitude, wave_omega = wave_omega, r_0_start = [0, -3.2, 0], initialize_orientation = false, m = 4e4, r_cm = [0, -4, 0], radius_spar = 1.3, I_11 = 4e5, I_33 = 4e5, d_translation = 6e4, d_rotation = 1e6) {
    "Dyad": {"placement": {"diagram": {"x1": 280, "x2": 540, "y1": 630, "y2": 890}}}
  }
  tower_mount = MultibodyComponents.FixedTranslation(r = [0, length_spar / 2 + tower_height, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 360, "x2": 460, "y1": 550, "y2": 650, "rot": 270}}
    }
  }
  yaw_joint = MultibodyComponents.Revolute(n = [0, 1, 0], statePriority = 20, phi(initial = 0), w(initial = 0), render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 360, "x2": 460, "y1": 440, "y2": 540, "rot": 270}}
    }
  }
  turbine = WindTurbine(tower_height = tower_height, V_wind = 10) {
    "Dyad": {"placement": {"diagram": {"x1": 260, "x2": 560, "y1": 110, "y2": 410}}}
  }
  yaw_drive = RotationalComponents.Sources.TorqueSource() {
    "Dyad": {"placement": {"diagram": {"x1": 720, "x2": 620, "y1": 280, "y2": 380}}}
  }
  yaw_damper = RotationalComponents.Components.Damper(d = d_yaw) {
    "Dyad": {"placement": {"diagram": {"x1": 550, "x2": 650, "y1": 440, "y2": 540}}}
  }
  wind_dir = BlockComponents.Sources.Sine(amplitude = theta_w_amplitude, frequency = theta_w_frequency) {
    "Dyad": {"placement": {"diagram": {"x1": 120, "x2": 220, "y1": 160, "y2": 260}}}
  }
  yaw_gain = BlockComponents.Math.Gain(k = kp_yaw) {
    "Dyad": {"placement": {"diagram": {"x1": 620, "x2": 720, "y1": 160, "y2": 260}}}
  }
  fairlead1 = MultibodyComponents.FixedTranslation(r = [fairlead_radius, -fairlead_drop, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 210, "x2": 310, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  fairlead2 = MultibodyComponents.FixedTranslation(r = [-fairlead_radius, -fairlead_drop, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 310, "x2": 410, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  fairlead3 = MultibodyComponents.FixedTranslation(r = [0, -fairlead_drop, fairlead_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 410, "x2": 510, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  fairlead4 = MultibodyComponents.FixedTranslation(r = [0, -fairlead_drop, -fairlead_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 500, "x2": 600, "y1": 950, "y2": 1050, "rot": 90}}
    }
  }
  anchor1 = MultibodyComponents.Fixed(r = [anchor_radius, -depth, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 310, "x2": 210, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  anchor2 = MultibodyComponents.Fixed(r = [-anchor_radius, -depth, 0], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 410, "x2": 310, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  anchor3 = MultibodyComponents.Fixed(r = [0, -depth, anchor_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 510, "x2": 410, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  anchor4 = MultibodyComponents.Fixed(r = [0, -depth, -anchor_radius], render = false) {
    "Dyad": {
      "placement": {"diagram": {"x1": 600, "x2": 500, "y1": 1200, "y2": 1300, "rot": 90}}
    }
  }
  cable1 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.196, 0, 0.98], guess_turn = 1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [14, -16, 0], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 210, "x2": 310, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  cable2 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.196, 0, 0.98], guess_turn = -1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [-14, -16, 0], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 310, "x2": 410, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  cable3 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.98, 0, 0.196], guess_turn = -1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [0, -16, 14], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 410, "x2": 510, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  cable4 = MultibodyComponents.Cable(n = n_segments, end_joints = true, iscut = true, initialize_orientation = false, guess_axis = [0.98, 0, 0.196], guess_turn = 1, drag = true, external_field = true, d_drag = d_drag, l = cable_length, m = cable_mass, dir = [0, -16, -14], radius = 0.08) {
    "Dyad": {
      "placement": {"diagram": {"x1": 500, "x2": 600, "y1": 1090, "y2": 1190, "rot": 90}}
    }
  }
  water_vis = MultibodyComponents.BoxVisualizer(color = [0.1, 0.4, 0.8, 0.4], length = 60, width = 60, height = 0.05, length_direction = [1, 0, 0], width_direction = [0, 0, 1], r_shape = [-30, water_level, 0]) {
    "Dyad": {"placement": {"diagram": {"x1": 830, "x2": 930, "y1": 630, "y2": 730}}}
  }
  seabed_vis = MultibodyComponents.BoxVisualizer(color = [0.76, 0.7, 0.5, 1], length = 60, width = 60, height = 0.2, length_direction = [1, 0, 0], width_direction = [0, 0, 1], r_shape = [-30, -depth - 0.1, 0]) {
    "Dyad": {"placement": {"diagram": {"x1": 830, "x2": 930, "y1": 800, "y2": 900}}}
  }
  "Number of segments in each mooring cable, including the loop-closing rod"
  structural parameter n_segments::Integer = 3
  "Length of the platform spar"
  parameter length_spar::Length = 10
  "Height of the turbine tower from the spar top to the yaw bearing"
  parameter tower_height::Length = 15
  "Water depth (seabed at y = -depth)"
  parameter depth::Length = 20
  "Horizontal distance from the platform center to each anchor"
  parameter anchor_radius::Length = 15
  "Horizontal distance from the spar axis to each fairlead"
  parameter fairlead_radius::Length = 1
  "Distance below the spar center at which the fairleads attach"
  parameter fairlead_drop::Length = 2
  "Total length of each mooring cable"
  parameter cable_length::Length = 22
  "Total mass of each mooring cable"
  parameter cable_mass::Mass = 500
  "Linear water drag coefficient per cable link"
  parameter d_drag::Real = 300
  "Wave number from the deep-water dispersion relation"
  final parameter wave_k::Real = wave_omega ^ 2 / 9.80665
  "Proportional gain of the yaw controller"
  parameter kp_yaw::Real = 1000
  "Damping coefficient of the yaw bearing"
  parameter d_yaw::Real = 3000
  "Amplitude of the wind direction variation"
  parameter theta_w_amplitude::Real = 0.6
  "Frequency of the wind direction variation"
  parameter theta_w_frequency::Real = 0.02
  "Amplitude of the surface wave"
  parameter wave_amplitude::Length = 0.5
  "Angular frequency of the surface wave"
  parameter wave_omega::Real = 0.9
  "Platform heave (vertical position of the spar center)"
  variable heave::Real
  "Platform surge (downwind position of the spar center)"
  variable surge::Real
  "Wave elevation at the world origin, used to place the rendered water plane"
  variable water_level::Real
relations
  connect(platform.frame_a, tower_mount.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(tower_mount.frame_b, yaw_joint.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(yaw_joint.frame_b, turbine.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(yaw_drive.spline, yaw_joint.axis, yaw_damper.spline_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [{"x": 530, "y": 330}], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": 3, "M": [], "E": -1}
      ],
      "junctions": [{"x": 530, "y": 490}]
    }
  }
  cable3.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable3.field.r[1]) * exp(wave_k * min(cable3.field.r[2], 0)), 0, 0]
  cable4.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable4.field.r[1]) * exp(wave_k * min(cable4.field.r[2], 0)), 0, 0]
  connect(fairlead2.frame_a, platform.frame_a, fairlead1.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 410, "y": 920}], "E": 2},
        {"S": 3, "M": [{"x": 260, "y": 920}], "E": -1}
      ],
      "junctions": [{"x": 360, "y": 920}]
    }
  }
  connect(fairlead3.frame_a, platform.frame_a, fairlead4.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 410, "y": 920}], "E": 2},
        {"S": 3, "M": [{"x": 550, "y": 920}], "E": -1}
      ],
      "junctions": [{"x": 460, "y": 920}]
    }
  }
  connect(fairlead1.frame_b, cable1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(fairlead2.frame_b, cable2.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable2.frame_b, anchor2.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable1.frame_b, anchor1.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(anchor3.frame_b, cable3.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(anchor4.frame_b, cable4.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable4.frame_a, fairlead4.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(cable3.frame_a, fairlead3.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
  connect(yaw_drive.support, yaw_damper.spline_b) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 670, "y": 490}], "E": 2}]}}
  connect(yaw_joint.support, yaw_damper.spline_b) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 450, "y": 525},
            {"x": 450, "y": 570},
            {"x": 670, "y": 570},
            {"x": 670, "y": 490}
          ],
          "E": 2
        }
      ]
    }
  }
  connect(seabed_vis.frame_a, world.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 880, "y": 760}], "E": 2}]}}
  connect(water_vis.frame_a, world.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 880, "y": 760}], "E": 2}]}}
  connect(wind_dir.y, turbine.theta_w) {"Dyad": {"edges": [{"S": 1, "M": [{"x": 281, "y": 210}], "E": 2}]}}
  connect(turbine.sin_err, yaw_gain.u) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 501.25, "y": 209}, {"x": 501.25, "y": 210}], "E": 2}]
    }
  }
  connect(yaw_gain.y, yaw_drive.tau) {
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 760, "y": 210}, {"x": 760, "y": 330}], "E": 2}]}
  }
  heave = platform.frame_a.r_0[2]
  surge = platform.frame_a.r_0[1]
  water_level = wave_amplitude * sin(wave_omega * time)
  # Equilibrium initialization: zero angular acceleration of every cable joint
  # selects the static hanging shape of the mooring lines, making the
  # initialization balanced instead of underdetermined (the joint orientations
  # have no initial conditions and would otherwise be free)
  for j in 1:n_segments - 1
    initial cable1.joints[j].z_a = [0, 0, 0]
    initial cable2.joints[j].z_a = [0, 0, 0]
    initial cable3.joints[j].z_a = [0, 0, 0]
    initial cable4.joints[j].z_a = [0, 0, 0]
    # Pin the scale gauge of the non-unit joint quaternions: the dynamics only
    # depend on the normalized quaternion, so without this the initialization
    # residual is flat along each Q_hat scale direction
    initial cable1.joints[j].n_q = 1
    initial cable2.joints[j].n_q = 1
    initial cable3.joints[j].n_q = 1
    initial cable4.joints[j].n_q = 1
  end
  # Rotational equilibrium of the platform at initialization: the orientation
  # carries no initial conditions (solved by initialization together with the
  # mooring shape); the position is pinned to the equilibrium draft
  initial platform.body.z_a = [0, 0, 0]
  initial platform.body.n_q = 1
  # Wave orbital velocity as the external field for the cable drag, evaluated
  # at each cable's midpoint and decaying exponentially with depth
  cable1.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable1.field.r[1]) * exp(wave_k * min(cable1.field.r[2], 0)), 0, 0]
  cable2.field.v = [wave_amplitude * wave_omega * cos(wave_omega * time - wave_k * cable2.field.r[1]) * exp(wave_k * min(cable2.field.r[2], 0)), 0, 0]
metadata {"Dyad": {"labels": [{"label": "$(instance)", "x": 500, "y": 1100, "rot": 0}]}}
end


Test Cases

No test cases defined.

  • Examples

  • Experiments

  • Analyses