Skip to content

Commit 0bcc3ae

Browse files
authored
Merge pull request #68 from WISDEM/rc1.3.2
RAFT v1.3.2
2 parents c9daef3 + f43f0bc commit 0bcc3ae

22 files changed

+174
-118
lines changed

.github/workflows/CI_RAFT.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
os: ["ubuntu-latest", "macOS-latest", "windows-latest"]
1919
python-version: ["3.10", "3.11"]
2020

21+
2122
steps:
2223
- name: checkout repository
2324
uses: actions/checkout@v4

examples/VolturnUS-S_example.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ cases:
2727

2828
turbine:
2929

30-
mRNA : 991000 # [kg] RNA mass
30+
mRNA : 1001505.907 # [kg] RNA mass: m_blades = 3*68076.969 = 204230.907 kg (from ED.sum), m_yawbearing = 100000 kg, m_hub = 190000 kg, m_nacelle = 507275 kg
3131
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]
3232
IrRNA : 0 # [kg-m2] RNA moment of inertia about local y or z axes [kg-m^2]
33-
xCG_RNA : 0 # [m] x location of RNA center of mass [m] (Actual is ~= -0.27 m)
33+
xCG_RNA : -7.27 # [m] x location of RNA center of mass [m]: xg_blades = -13.0 m, xg_hub = -11.0 m, xg_nac = -4.99 m, xg_yaw = 0.0 m
3434
hHub : 150.0 # [m] hub height above water line [m]
3535
#rRNA : [0,0, 148.742] # [m] Can use the position of the RNA reference point (which the RNA yaws about) with respect to the FOWT reference point. If also providing hHub, the z coord be overwritten.
3636
Fthrust : 1500.0E3 # [N] temporary thrust force to use
@@ -43,7 +43,7 @@ turbine:
4343
precone : 4.0 # [deg]
4444
shaft_tilt : 6.0 # [deg]
4545
overhang : -12.0313 # [m]
46-
aeroMod : 1 # 0 aerodynamics off; 1 aerodynamics on
46+
aeroServoMod : 1 # 0 aerodynamics off; 1 aerodynamics on
4747

4848

4949
blade:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "OpenRAFT"
7-
version = "1.3.1"
7+
version = "1.3.2"
88
description = "RAFT: Response Amplitudes of Floating Turbines"
99
readme = "README.md"
1010
requires-python = ">=3.9"

raft/omdao_raft.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
600600
ring_spacing = inputs[m_name+'ring_spacing']
601601
n_stiff = 0 if ring_spacing == 0.0 else int(np.floor(s_height / ring_spacing))
602602
s_ring = (np.arange(1, n_stiff + 0.1) - 0.5) * (ring_spacing / s_height)
603-
if s_ring:
603+
if np.any(s_ring):
604604
if not m_shape == 'rect':
605605
d_ring = np.interp(s_ring, s_grid, design['platform']['members'][i]['d'])
606606
else:
@@ -623,7 +623,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
623623
t_cap = t_cap[:-1]
624624
di_cap = di_cap[:-1]
625625
# Combine with ring stiffeners
626-
if s_ring:
626+
if np.any(s_ring):
627627
s_cap = np.r_[s_ring, s_cap]
628628
t_cap = np.r_[inputs[m_name+'ring_t']*np.ones(n_stiff), t_cap]
629629
di_cap = np.r_[d_ring-2*inputs[m_name+'ring_h'], di_cap]

raft/raft_fowt.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import matplotlib.pyplot as plt
55
import numpy as np
6-
from scipy.interpolate import interp1d, interp2d, griddata
6+
from scipy.interpolate import interp1d, RegularGridInterpolator, griddata
77

88
import raft.member2pnl as pnl
99
from raft.helpers import *
@@ -1575,14 +1575,13 @@ def calcQTF_slenderBody(self, waveHeadInd, Xi0=None, verbose=False, iCase=None,
15751575
f_rslb += - rho*v_i * aux
15761576

15771577

1578-
# ----- axial/end effects ------
1578+
# ----- end effects ------
15791579
# note : v_i and a_i work out to zero for non-tapered sections or non-end sections
1580+
# TODO: Should probably skip this part if along the length of the element, i.e. if a_i and v_i are different from zero due to tapering
15801581
if circ:
15811582
v_i = np.pi/12.0 * abs((mem.ds[il]+mem.drs[il])**3 - (mem.ds[il]-mem.drs[il])**3) # volume assigned to this end surface
1582-
a_i = np.pi*mem.ds[il] * mem.drs[il] # signed end area (positive facing down) = mean diameter of strip * radius change of strip
15831583
else:
15841584
v_i = np.pi/12.0 * ((np.mean(mem.ds[il]+mem.drs[il]))**3 - (np.mean(mem.ds[il]-mem.drs[il]))**3) # so far just using sphere eqn and taking mean of side lengths as d
1585-
a_i = (mem.ds[il,0]+mem.drs[il,0])*(mem.ds[il,1]+mem.drs[il,1]) - (mem.ds[il,0]-mem.drs[il,0])*(mem.ds[il,1]-mem.drs[il,1])
15861585

15871586
f_2ndPot += mem.a_i[il]*p_2nd*mem.q # 2nd order pressure
15881587
f_2ndPot += rho*v_i*Ca_End*np.matmul(mem.qMat, acc_2ndPot) # 2nd order axial acceleration
@@ -1593,6 +1592,11 @@ def calcQTF_slenderBody(self, waveHeadInd, Xi0=None, verbose=False, iCase=None,
15931592
p_drop = -2*0.25*0.5*rho*np.dot(np.matmul(mem.p1Mat + mem.p2Mat, u[:,i1, il]-nodeV[:, i1, il]), np.conj(np.matmul(Ca_p1*mem.p1Mat + Ca_p2*mem.p2Mat, u[:,i2, il]-nodeV[:, i2, il])))
15941593
f_conv += mem.a_i[il]*p_drop*mem.q
15951594

1595+
u1_aux = np.matmul(Ca_p1*mem.p1Mat + Ca_p2*mem.p2Mat, u1_aux)
1596+
u2_aux = np.matmul(Ca_p1*mem.p1Mat + Ca_p2*mem.p2Mat, u2_aux)
1597+
f_transv = 0.25*mem.a_i[il]*rho*(np.conj(u1_aux)*nodeV_axial_rel[i2, il] + u2_aux*np.conj(nodeV_axial_rel[i1, il]))
1598+
f_conv += f_transv
1599+
15961600
F_2ndPot += translateForce3to6DOF(f_2ndPot, mem.r[il,:])
15971601
F_conv += translateForce3to6DOF(f_conv, mem.r[il,:])
15981602
F_axdv += translateForce3to6DOF(f_axdv, mem.r[il,:])
@@ -1788,10 +1792,15 @@ def calcHydroForce_2ndOrd(self, beta, S0, iCase=None, iWT=None, interpMode='qtf'
17881792
else:
17891793
f = np.zeros([self.nDOF, self.nw]) # Force amplitude
17901794
for idof in range(0,self.nDOF):
1791-
# Interpolate the QTF matrix to the same frequencies as the wave spectrum. Need to interpolate real and imaginary part separately.
1792-
qtf_interp_Re = interp2d(self.w1_2nd, self.w1_2nd, qtf_interpBeta[:,:, idof].real, bounds_error=False, fill_value=(0))(self.w, self.w)
1793-
qtf_interp_Im = interp2d(self.w1_2nd, self.w1_2nd, qtf_interpBeta[:,:, idof].imag, bounds_error=False, fill_value=(0))(self.w, self.w)
1794-
qtf_interp = qtf_interp_Re + 1j*qtf_interp_Im
1795+
qtf_interp_Re_interpolator = RegularGridInterpolator((self.w1_2nd, self.w1_2nd), qtf_interpBeta[:, :, idof].real, bounds_error=False, fill_value=0)
1796+
qtf_interp_Im_interpolator = RegularGridInterpolator((self.w1_2nd, self.w1_2nd), qtf_interpBeta[:, :, idof].imag, bounds_error=False, fill_value=0)
1797+
1798+
w_mesh = np.meshgrid(self.w, self.w, indexing='ij')
1799+
points = np.array([w_mesh[0].ravel(), w_mesh[1].ravel()]).T
1800+
1801+
qtf_interp_Re = qtf_interp_Re_interpolator(points).reshape(len(self.w), len(self.w))
1802+
qtf_interp_Im = qtf_interp_Im_interpolator(points).reshape(len(self.w), len(self.w))
1803+
qtf_interp = qtf_interp_Re + 1j * qtf_interp_Im
17951804

17961805
for imu in range(1, self.nw): # Loop the difference frequencies
17971806
Saux = np.zeros(self.nw)

raft/raft_member.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,11 @@ def __init__(self, mi, nw, BEM=[], heading=0):
150150
self.cap_stations = []
151151
else:
152152
self.cap_t = getFromDict(mi, 'cap_t' , shape=cap_stations.shape[0]) # thicknesses [m]
153-
self.cap_d_in = getFromDict(mi, 'cap_d_in', shape=cap_stations.shape[0]) # inner diameter (if it isn't a solid plate) [m]
153+
if self.shape == 'circular':
154+
self.cap_d_in = getFromDict(mi, 'cap_d_in', shape=cap_stations.shape[0]) # inner diameter (if it isn't a solid plate) [m]
155+
elif self.shape == 'rectangular':
156+
self.cap_d_in = getFromDict(mi, 'cap_d_in', shape=[cap_stations.shape[0],2]) # inner diameter (if it isn't a solid plate) [m]
157+
154158
self.cap_stations = (cap_stations - st[0])/(st[-1] - st[0])*self.l # calculate station positions along the member axis from 0 to l [m]
155159

156160

@@ -449,7 +453,7 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
449453
v_shell = V_outer-V_inner # volume of hollow frustum with shell thickness [m^3]
450454
m_shell = v_shell*rho_shell # mass of hollow frustum [kg]
451455

452-
hc_shell = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner) # center of volume of hollow frustum with shell thickness [m]
456+
hc_shell = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner) if V_outer-V_inner!=0 else 0 # center of volume of hollow frustum with shell thickness [m]
453457

454458
dBi_fill = (dBi-dAi)*(l_fill/l) + dAi # interpolated inner diameter of frustum that ballast is filled to [m]
455459
v_fill, hc_fill = FrustumVCV(dAi, dBi_fill, l_fill) # volume and center of volume of solid inner frustum that ballast occupies [m^3] [m]
@@ -459,7 +463,7 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
459463
# then the ballast sits on top of the end cap. Depending on the thickness of the end cap, this can affect m_fill, hc_fill, and MoI_fill >>>>>
460464

461465
mass = m_shell + m_fill # total mass of the submember [kg]
462-
hc = ((hc_fill*m_fill) + (hc_shell*m_shell))/mass # total center of mass of the submember from the submember's rA location [m]
466+
hc = ((hc_fill*m_fill) + (hc_shell*m_shell))/mass if mass!=0 else 0 # total center of mass of the submember from the submember's rA location [m]
463467

464468

465469
# MOMENT OF INERTIA
@@ -492,14 +496,14 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
492496
v_shell = V_outer-V_inner # volume of hollow frustum with shell thickness [m^3]
493497
m_shell = v_shell*rho_shell # mass of hollow frustum [kg]
494498

495-
hc_shell = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner) # center of volume of the hollow frustum with shell thickness [m]
499+
hc_shell = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner) if V_outer-V_inner!=0 else 0 # center of volume of the hollow frustum with shell thickness [m]
496500

497501
slBi_fill = (slBi-slAi)*(l_fill/l) + slAi # interpolated side lengths of frustum that ballast is filled to [m]
498502
v_fill, hc_fill = FrustumVCV(slAi, slBi_fill, l_fill) # volume and center of volume of inner frustum that ballast occupies [m^3]
499503
m_fill = v_fill*rho_fill # mass of ballast in the submember [kg]
500504

501505
mass = m_shell + m_fill # total mass of the submember [kg]
502-
hc = ((hc_fill*m_fill) + (hc_shell*m_shell))/mass # total center of mass of the submember from the submember's rA location [m]
506+
hc = ((hc_fill*m_fill) + (hc_shell*m_shell))/mass if mass !=0 else 0 # total center of mass of the submember from the submember's rA location [m]
503507

504508

505509
# MOMENT OF INERTIA
@@ -602,7 +606,7 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
602606
V_inner, hci = FrustumVCV(dAi, dBi, h)
603607
v_cap = V_outer-V_inner
604608
m_cap = v_cap*rho_cap # assume it's made out of the same material as the shell for now (can add in cap density input later if needed)
605-
hc_cap = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner)
609+
hc_cap = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner) if V_outer-V_inner!=0 else 0
606610

607611
I_rad_end_outer, I_ax_outer = FrustumMOI(dA, dB, h, rho_cap)
608612
I_rad_end_inner, I_ax_inner = FrustumMOI(dAi, dBi, h, rho_cap)
@@ -622,11 +626,12 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
622626

623627
if L==self.stations[0]: # if the cap is on the bottom end of the member
624628
slA = sl[0,:]
625-
slB = np.interp(L+h, self.stations, sl)
629+
slB = np.zeros(slA.shape)
630+
slB = np.array([np.interp(L+h, self.stations, sl[:,0]), np.interp(L+h, self.stations, sl[:,1])])
626631
slAi = sl_hole
627632
slBi = slB*(slAi/slA) # keep the same proportion in d_hole from bottom to top
628633
elif L==self.stations[-1]: # if the cap is on the top end of the member
629-
slA = np.interp(L-h, self.stations, sl)
634+
slA = np.array([np.interp(L-h, self.stations, sl[:,0]), np.interp(L-h, self.stations, sl[:,1])])
630635
slB = sl[-1,:]
631636
slAi = slA*(slBi/slB)
632637
slBi = sl_hole
@@ -659,10 +664,10 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
659664
V_inner, hci = FrustumVCV(slAi, slBi, h)
660665
v_cap = V_outer-V_inner
661666
m_cap = v_cap*rho_cap # assume it's made out of the same material as the shell for now (can add in cap density input later if needed)
662-
hc_cap = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner)
667+
hc_cap = ((hco*V_outer)-(hci*V_inner))/(V_outer-V_inner) if V_outer-V_inner!=0 else 0
663668

664-
Ixx_end_outer, Iyy_end_outer, Izz_end_outer = RectangularFrustumMOI(slA, slB, h, rho_cap)
665-
Ixx_end_inner, Iyy_end_inner, Izz_end_inner = RectangularFrustumMOI(slAi, slBi, h, rho_cap)
669+
Ixx_end_outer, Iyy_end_outer, Izz_end_outer = RectangularFrustumMOI(slA[0], slA[1], slB[0], slB[1], h, rho_cap)
670+
Ixx_end_inner, Iyy_end_inner, Izz_end_inner = RectangularFrustumMOI(slAi[0], slAi[1], slBi[0], slBi[1], h, rho_cap)
666671
Ixx_end = Ixx_end_outer-Ixx_end_inner
667672
Iyy_end = Iyy_end_outer-Iyy_end_inner
668673
Izz_end = Izz_end_outer-Izz_end_inner
@@ -699,7 +704,8 @@ def RectangularFrustumMOI(La, Wa, Lb, Wb, H, p):
699704
# translate this submember's local inertia matrix to the PRP and add it to the total member's M_struc matrix
700705
self.M_struc += translateMatrix6to6DOF(Mmat, center_cap) # mass matrix of the member about the PRP
701706

702-
707+
self.mshell = mshell
708+
self.mfill = mfill
703709
mass = self.M_struc[0,0] # total mass of the entire member [kg]
704710
center = mass_center/mass # total center of mass of the entire member from the PRP [m]
705711

raft/raft_model.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,9 @@ def solveStatics(self, case, display=0):
525525

526526
X_initial[6*i:6*i+6] = np.array([fowt.x_ref, fowt.y_ref,0,0,0,0])
527527
fowt.setPosition(X_initial[6*i:6*i+6]) # zero platform offsets
528-
fowt.calcStatics()
528+
if case:
529+
fowt.calcTurbineConstants(case, ptfm_pitch=0) # for turbine forces >>> still need to update to use current fowt pose <<<
530+
fowt.calcStatics() # Recompute statics because turbine heading may have changed due to yaw control
529531

530532
if statics_mod == 0:
531533
K_hydrostatic.append(fowt.C_struc + fowt.C_hydro)
@@ -541,8 +543,7 @@ def solveStatics(self, case, display=0):
541543
if display > 1:
542544
print('Fowt ' + str(i))
543545
print(case)
544-
545-
fowt.calcTurbineConstants(case, ptfm_pitch=0) # for turbine forces >>> still need to update to use current fowt pose <<<
546+
546547
fowt.calcHydroConstants()
547548
F_env_constant[6*i:6*i+6] = np.sum(fowt.f_aero0, axis=1) + fowt.calcCurrentLoads(case)
548549

@@ -639,6 +640,7 @@ def eval_func_equil(X, args):
639640
case['wind_speed'] = caseorig['wind_speed'][i]
640641

641642
fowt.calcTurbineConstants(case, ptfm_pitch=r6[4]) # for turbine forces >>> still need to update to use current fowt pose <<<
643+
fowt.calcStatics() # Recompute statics because turbine heading may have changed due to yaw control
642644
fowt.calcHydroConstants() # prep for drag force and mean drift
643645

644646
Fnet[6*i:6*i+6] += np.sum(fowt.f_aero0, axis=1) # sum mean turbine force across turbines
@@ -789,7 +791,7 @@ def step_func_equil(X, args, Y, oths, Ytarget, err, tol_, iter, maxIter):
789791

790792
for i, fowt in enumerate(self.fowtList):
791793
print(f"Found mean offets of FOWT {i+1} with surge = {fowt.Xi0[0]: .2f} m, sway = {fowt.Xi0[1]: .2f}, and heave = {fowt.Xi0[2]: .2f} m")
792-
print(f" roll = {fowt.Xi0[3]*180/np.pi: .2f} deg, pitch = {fowt.Xi0[4]*180/np.pi: .2f}, and yaw = {fowt.Xi0[5]*180/np.pi: .2f} deg")
794+
print(f" roll = {fowt.Xi0[3]*180/np.pi: .2f} deg, pitch = {fowt.Xi0[4]*180/np.pi: .2f} deg, and yaw = {fowt.Xi0[5]*180/np.pi: .2f} deg")
793795

794796

795797
#dsolvePlot(info) # plot solver convergence trajectories

0 commit comments

Comments
 (0)