def test_dpresweep1(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dpresweep = dNp_dX[5, :] dTp_dpresweep = dTp_dX[5, :] dNp_dpresweep_fd = np.zeros(self.n) dTp_dpresweep_fd = np.zeros(self.n) for i in range(self.n): ps = np.array(presweep) delta = 1e-6*ps[i] ps[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=ps, presweepTip=presweepTip) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dpresweep_fd[i] = (Npd[i] - Np[i]) / delta dTp_dpresweep_fd[i] = (Tpd[i] - Tp[i]) / delta np.testing.assert_allclose(dNp_dpresweep_fd, dNp_dpresweep, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dTp_dpresweep_fd, dTp_dpresweep, rtol=1e-5, atol=1e-8)
def test_dprecurveTip1(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) pct = float(precurveTip) delta = 1e-6*pct pct += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=precurve, precurveTip=pct) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecurveTip_fd = (Npd - Np) / delta dTp_dprecurveTip_fd = (Tpd - Tp) / delta np.testing.assert_allclose(dNp_dprecurveTip_fd, 0.0, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dTp_dprecurveTip_fd, 0.0, rtol=1e-4, atol=1e-8)
def test_dprecurve1(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecurve_fd = np.zeros((self.n, self.n)) dTp_dprecurve_fd = np.zeros((self.n, self.n)) for i in range(self.n): pc = np.array(precurve) delta = 1e-6*pc[i] pc[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=pc, precurveTip=precurveTip) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecurve_fd[i, :] = (Npd - Np) / delta dTp_dprecurve_fd[i, :] = (Tpd - Tp) / delta np.testing.assert_allclose(dNp_dprecurve_fd, dNp_dprecurve, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dTp_dprecurve_fd, dTp_dprecurve, rtol=3e-4, atol=1e-8)
def test_dhubht1(self): dNp_dhubht = self.dNp_dX[8, :] dTp_dhubht = self.dTp_dX[8, :] hubht = float(self.hubHt) delta = 1e-6*hubht hubht += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, hubht, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dhubht_fd = (Npd - self.Np) / delta dTp_dhubht_fd = (Tpd - self.Tp) / delta np.testing.assert_allclose(dNp_dhubht_fd, dNp_dhubht, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dTp_dhubht_fd, dTp_dhubht, rtol=1e-5, atol=1e-8)
def test_dtheta1(self): dNp_dtheta = self.dNp_dX[2, :] dTp_dtheta = self.dTp_dX[2, :] dNp_dtheta_fd = np.zeros(self.n) dTp_dtheta_fd = np.zeros(self.n) for i in range(self.n): theta = np.array(self.theta) delta = 1e-6*theta[i] theta[i] += delta rotor = CCBlade(self.r, self.chord, theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dtheta_fd[i] = (Npd[i] - self.Np[i]) / delta dTp_dtheta_fd[i] = (Tpd[i] - self.Tp[i]) / delta np.testing.assert_allclose(dNp_dtheta_fd, dNp_dtheta, rtol=1e-6, atol=1e-8) np.testing.assert_allclose(dTp_dtheta_fd, dTp_dtheta, rtol=1e-4, atol=1e-8)
class CCBladeLoads(Component): def __init__(self, naero, npower): super(CCBladeLoads, self).__init__() """blade element momentum code""" # inputs self.add_param('V_load', val=0.0, units='m/s', desc='hub height wind speed') self.add_param('Omega_load', val=0.0, units='rpm', desc='rotor rotation speed') self.add_param('pitch_load', val=0.0, units='deg', desc='blade pitch setting') self.add_param('azimuth_load', val=0.0, units='deg', desc='blade azimuthal location') # outputs self.add_output('loads_r', val=np.zeros(naero), units='m', desc='radial positions along blade going toward tip') self.add_output('loads_Px', val=np.zeros(naero), units='N/m', desc='distributed loads in blade-aligned x-direction') self.add_output('loads_Py', val=np.zeros(naero), units='N/m', desc='distributed loads in blade-aligned y-direction') self.add_output('loads_Pz', val=np.zeros(naero), units='N/m', desc='distributed loads in blade-aligned z-direction') # corresponding setting for loads self.add_output('loads_V', val=0.0, units='m/s', desc='hub height wind speed') self.add_output('loads_Omega', val=0.0, units='rpm', desc='rotor rotation speed') self.add_output('loads_pitch', val=0.0, units='deg', desc='pitch angle') self.add_output('loads_azimuth', val=0.0, units='deg', desc='azimuthal angle') # (potential) variables self.add_param('r', val=np.zeros(naero), units='m', desc='radial locations where blade is defined (should be increasing and not go all the way to hub or tip)') self.add_param('chord', val=np.zeros(naero), units='m', desc='chord length at each section') self.add_param('theta', val=np.zeros(naero), units='deg', desc='twist angle at each section (positive decreases angle of attack)') self.add_param('Rhub', val=0.0, units='m', desc='hub radius') self.add_param('Rtip', val=0.0, units='m', desc='tip radius') self.add_param('hubHt', val=0.0, units='m', desc='hub height') self.add_param('precone', val=0.0, desc='precone angle', units='deg') self.add_param('tilt', val=0.0, desc='shaft tilt', units='deg') self.add_param('yaw', val=0.0, desc='yaw error', units='deg') # TODO: I've not hooked up the gradients for these ones yet. self.add_param('precurve', val=np.zeros(naero), units='m', desc='precurve at each section') self.add_param('precurveTip', val=0.0, units='m', desc='precurve at tip') # parameters self.add_param('airfoils', val=[0]*naero, desc='CCAirfoil instances', pass_by_obj=True) self.add_param('B', val=0, desc='number of blades', pass_by_obj=True) self.add_param('rho', val=0.0, units='kg/m**3', desc='density of air') self.add_param('mu', val=0.0, units='kg/(m*s)', desc='dynamic viscosity of air') self.add_param('shearExp', val=0.0, desc='shear exponent') self.add_param('nSector', val=4, desc='number of sectors to divide rotor face into in computing thrust and power', pass_by_obj=True) self.add_param('tiploss', val=True, desc='include Prandtl tip loss model', pass_by_obj=True) self.add_param('hubloss', val=True, desc='include Prandtl hub loss model', pass_by_obj=True) self.add_param('wakerotation', val=True, desc='include effect of wake rotation (i.e., tangential induction factor is nonzero)', pass_by_obj=True) self.add_param('usecd', val=True, desc='use drag coefficient in computing induction factors', pass_by_obj=True) self.naero = naero self.deriv_options['form'] = 'central' self.deriv_options['step_calc'] = 'relative' def solve_nonlinear(self, params, unknowns, resids): self.r = params['r'] self.chord = params['chord'] self.theta = params['theta'] self.Rhub = params['Rhub'] self.Rtip = params['Rtip'] self.hubHt = params['hubHt'] self.precone = params['precone'] self.tilt = params['tilt'] self.yaw = params['yaw'] self.precurve = params['precurve'] self.precurveTip = params['precurveTip'] self.airfoils = params['airfoils'] self.B = params['B'] self.rho = params['rho'] self.mu = params['mu'] self.shearExp = params['shearExp'] self.nSector = params['nSector'] self.tiploss = params['tiploss'] self.hubloss = params['hubloss'] self.wakerotation = params['wakerotation'] self.usecd = params['usecd'] self.V_load = params['V_load'] self.Omega_load = params['Omega_load'] self.pitch_load = params['pitch_load'] self.azimuth_load = params['azimuth_load'] if len(self.precurve) == 0: self.precurve = np.zeros_like(self.r) # airfoil files n = len(self.airfoils) af = self.airfoils # af = [0]*n # for i in range(n): # af[i] = CCAirfoil.initFromAerodynFile(self.airfoil_files[i]) self.ccblade = CCBlade(self.r, self.chord, self.theta, af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, self.precurve, self.precurveTip, tiploss=self.tiploss, hubloss=self.hubloss, wakerotation=self.wakerotation, usecd=self.usecd, derivatives=True) # distributed loads Np, Tp, self.dNp, self.dTp \ = self.ccblade.distributedAeroLoads(self.V_load, self.Omega_load, self.pitch_load, self.azimuth_load) # concatenate loads at root/tip unknowns['loads_r'] = self.r # conform to blade-aligned coordinate system unknowns['loads_Px'] = Np unknowns['loads_Py'] = -Tp unknowns['loads_Pz'] = 0*Np # return other outputs needed unknowns['loads_V'] = self.V_load unknowns['loads_Omega'] = self.Omega_load unknowns['loads_pitch'] = self.pitch_load unknowns['loads_azimuth'] = self.azimuth_load def list_deriv_vars(self): inputs = ('r', 'chord', 'theta', 'Rhub', 'Rtip', 'hubHt', 'precone', 'tilt', 'yaw', 'V_load', 'Omega_load', 'pitch_load', 'azimuth_load', 'precurve') outputs = ('loads_r', 'loads_Px', 'loads_Py', 'loads_Pz', 'loads_V', 'loads_Omega', 'loads_pitch', 'loads_azimuth') return inputs, outputs def linearize(self, params, unknowns, resids): dNp = self.dNp dTp = self.dTp n = len(self.r) dr_dr = np.vstack([np.zeros(n), np.eye(n), np.zeros(n)]) dr_dRhub = np.zeros(n+2) dr_dRtip = np.zeros(n+2) dr_dRhub[0] = 1.0 dr_dRtip[-1] = 1.0 dV = np.zeros(4*n+10) dV[3*n+6] = 1.0 dOmega = np.zeros(4*n+10) dOmega[3*n+7] = 1.0 dpitch = np.zeros(4*n+10) dpitch[3*n+8] = 1.0 dazimuth = np.zeros(4*n+10) dazimuth[3*n+9] = 1.0 J = {} zero = np.zeros(self.naero) J['loads_r', 'r'] = dr_dr J['loads_r', 'Rhub'] = dr_dRhub J['loads_r', 'Rtip'] = dr_dRtip J['loads_Px', 'r'] = np.vstack([zero, dNp['dr'], zero]) J['loads_Px', 'chord'] = np.vstack([zero, dNp['dchord'], zero]) J['loads_Px', 'theta'] = np.vstack([zero, dNp['dtheta'], zero]) J['loads_Px', 'Rhub'] = np.concatenate([[0.0], np.squeeze(dNp['dRhub']), [0.0]]) J['loads_Px', 'Rtip'] = np.concatenate([[0.0], np.squeeze(dNp['dRtip']), [0.0]]) J['loads_Px', 'hubHt'] = np.concatenate([[0.0], np.squeeze(dNp['dhubHt']), [0.0]]) J['loads_Px', 'precone'] = np.concatenate([[0.0], np.squeeze(dNp['dprecone']), [0.0]]) J['loads_Px', 'tilt'] = np.concatenate([[0.0], np.squeeze(dNp['dtilt']), [0.0]]) J['loads_Px', 'yaw'] = np.concatenate([[0.0], np.squeeze(dNp['dyaw']), [0.0]]) J['loads_Px', 'V_load'] = np.concatenate([[0.0], np.squeeze(dNp['dUinf']), [0.0]]) J['loads_Px', 'Omega_load'] = np.concatenate([[0.0], np.squeeze(dNp['dOmega']), [0.0]]) J['loads_Px', 'pitch_load'] = np.concatenate([[0.0], np.squeeze(dNp['dpitch']), [0.0]]) J['loads_Px', 'azimuth_load'] = np.concatenate([[0.0], np.squeeze(dNp['dazimuth']), [0.0]]) J['loads_Px', 'precurve'] = np.vstack([zero, dNp['dprecurve'], zero]) J['loads_Py', 'r'] = np.vstack([zero, -dTp['dr'], zero]) J['loads_Py', 'chord'] = np.vstack([zero, -dTp['dchord'], zero]) J['loads_Py', 'theta'] = np.vstack([zero, -dTp['dtheta'], zero]) J['loads_Py', 'Rhub'] = np.concatenate([[0.0], -np.squeeze(dTp['dRhub']), [0.0]]) J['loads_Py', 'Rtip'] = np.concatenate([[0.0], -np.squeeze(dTp['dRtip']), [0.0]]) J['loads_Py', 'hubHt'] = np.concatenate([[0.0], -np.squeeze(dTp['dhubHt']), [0.0]]) J['loads_Py', 'precone'] = np.concatenate([[0.0], -np.squeeze(dTp['dprecone']), [0.0]]) J['loads_Py', 'tilt'] = np.concatenate([[0.0], -np.squeeze(dTp['dtilt']), [0.0]]) J['loads_Py', 'yaw'] = np.concatenate([[0.0], -np.squeeze(dTp['dyaw']), [0.0]]) J['loads_Py', 'V_load'] = np.concatenate([[0.0], -np.squeeze(dTp['dUinf']), [0.0]]) J['loads_Py', 'Omega_load'] = np.concatenate([[0.0], -np.squeeze(dTp['dOmega']), [0.0]]) J['loads_Py', 'pitch_load'] = np.concatenate([[0.0], -np.squeeze(dTp['dpitch']), [0.0]]) J['loads_Py', 'azimuth_load'] = np.concatenate([[0.0], -np.squeeze(dTp['dazimuth']), [0.0]]) J['loads_Py', 'precurve'] = np.vstack([zero, -dTp['dprecurve'], zero]) J['loads_V', 'V_load'] = 1.0 J['loads_Omega', 'Omega_load'] = 1.0 J['loads_pitch', 'pitch_load'] = 1.0 J['loads_azimuth', 'azimuth_load'] = 1.0 return J
precone, tilt, 20.0, shearExp, m66_hubHt, nSector) m67_rotor = CCBlade(m67_r, m67_chord, m67_theta, m67_af, m67_Rhub, m67_Rtip, B, m67_rho, m67_mu, precone, tilt, yaw, shearExp, m67_hubHt, nSector) # convert to RPM m1_Omega = m1_Uinf*tsr/m1_Rtip * 30.0/pi m4_Omega = m4_Uinf*tsr/m4_Rtip * 30.0/pi m6_Omega = m6_Uinf*tsr/m6_Rtip * 30.0/pi m66_Omega = m66_Uinf*tsr/m66_Rtip * 30.0/pi m67_Omega = m67_Uinf*tsr/m67_Rtip * 30.0/pi m66yaw_Uinf = 1.2 m66yaw_Omega = m66yaw_Uinf*tsr/m66_Rtip * 30.0/pi # Evaluate the distributed loads at a chosen set of operating conditions. m1_Np, m1_Tp = m1_rotor.distributedAeroLoads(m1_Uinf, m1_Omega, pitch, azimuth) m4_Np, m4_Tp = m4_rotor.distributedAeroLoads(m4_Uinf, m4_Omega, pitch, azimuth) m6_Np, m6_Tp = m6_rotor.distributedAeroLoads(m6_Uinf, m6_Omega, pitch, azimuth) m66_Np, m66_Tp = m66_rotor.distributedAeroLoads(m66_Uinf, m66_Omega, pitch, azimuth) m66yaw20_Np, m66yaw20_Tp = m66yaw20_rotor.distributedAeroLoads(m66yaw_Uinf, m66yaw_Omega, pitch, azimuth) m67_Np, m67_Tp = m67_rotor.distributedAeroLoads(m67_Uinf, m67_Omega, pitch, azimuth) ############################################################################### # Plot the flapwise and lead-lag aerodynamic loading
def get_loads(Uinf, yaw, azimuth, yawcorrection=False, coupled=False, return_bounds=False, return_az_variation=False, station_idx=0): fname = 'H%02d%04d0' % (Uinf, yaw) fid = open(fname + '.HD1', 'r') fid.readline() parts = fid.readline().split(',') nchan = int(parts[0]) nsamp = int(parts[1]) fid.close() th_idx = np.zeros(5) tq_idx = np.zeros(5) q_idx = np.zeros(5) with open(fname + '.HD1', 'r') as inF: for num, line in enumerate(inF): if 'Thrust coefficient at 30% span' in line: th_idx[0] = int(num) - 2 elif 'Thrust coefficient at 47% span' in line: th_idx[1] = int(num) - 2 elif 'Thrust coefficient at 63% span' in line: th_idx[2] = int(num) - 2 elif 'Thrust coefficient at 80% span' in line: th_idx[3] = int(num) - 2 elif 'Thrust coefficient at 95% span' in line: th_idx[4] = int(num) - 2 if 'Torque coefficient at 30% span' in line: tq_idx[0] = int(num) - 2 elif 'Torque coefficient at 47% span' in line: tq_idx[1] = int(num) - 2 elif 'Torque coefficient at 63% span' in line: tq_idx[2] = int(num) - 2 elif 'Torque coefficient at 80% span' in line: tq_idx[3] = int(num) - 2 elif 'Torque coefficient at 95% span' in line: tq_idx[4] = int(num) - 2 elif 'QNORM30' in line: q_idx[0] = int(num) - 2 elif 'QNORM47' in line: q_idx[1] = int(num) - 2 elif 'QNORM63' in line: q_idx[2] = int(num) - 2 elif 'QNORM80' in line: q_idx[3] = int(num) - 2 elif 'QNORM95' in line: q_idx[4] = int(num) - 2 elif 'Blade azimuth angle' in line: az_idx = int(num) - 2 elif 'CONE' in line: cone_idx = int(num) - 2 elif 'RHOTUN' in line: rho_idx = int(num) - 2 elif 'RPM' in line: rpm_idx = int(num) - 2 data = get_eng_data(fname + '.ENG', nsamp, nchan) az_vec = data[:, az_idx] idx = np.nonzero(np.abs(az_vec - azimuth) < 1) cone_vec = data[:, cone_idx] cone = np.mean(cone_vec[idx]) rho_vec = data[:, rho_idx] rho = np.mean(rho_vec[idx]) rpm_vec = data[:, rpm_idx] rpm = np.mean(rpm_vec[idx]) r_exp = [0.3, 0.47, 0.63, 0.8, 0.95] c_exp = [0.711, 0.62492758, 0.54337911, 0.457, 0.381] q_exp = np.zeros(5) cn_exp = np.zeros(5) ct_exp = np.zeros(5) th_mean = np.zeros(5) th_max = np.zeros(5) th_min = np.zeros(5) tq_mean = np.zeros(5) tq_max = np.zeros(5) tq_min = np.zeros(5) th_vec_all = np.zeros((5, nsamp)) tq_vec_all = np.zeros((5, nsamp)) for i in range(5): cth_vec = data[:, th_idx[i]] ctq_vec = data[:, tq_idx[i]] q_vec = data[:, q_idx[i]] th_vec = cth_vec * q_vec * c_exp[i] / np.cos(np.radians(cone_vec)) tq_vec = ctq_vec * q_vec * c_exp[i] / np.cos(np.radians(cone_vec)) th_mean[i] = np.mean(th_vec[idx]) th_max[i] = np.max(th_vec[idx]) th_min[i] = np.min(th_vec[idx]) tq_mean[i] = np.mean(tq_vec[idx]) tq_max[i] = np.max(tq_vec[idx]) tq_min[i] = np.min(tq_vec[idx]) th_vec_all[i, :] = th_vec tq_vec_all[i, :] = tq_vec Np_exp = th_mean Tp_exp = tq_mean Np_min = th_min Tp_min = tq_min Np_max = th_max Tp_max = tq_max # geometry Rhub = 0.508 Rtip = 5.029 # 0.508, # r = np.array([0.660, 0.883, 1.008, 1.067, 1.133, 1.257, 1.343, 1.510, 1.648, # 1.952, 2.257, 2.343, 2.562, 2.867, 3.172, 3.185, 3.476, 3.781, 4.023, 4.086, 4.391, # 4.696, 4.780, 5.000]) # , 5.305, 5.532] # # 0.218, # chord = np.array([0.218, 0.183, 0.349, 0.441, 0.544, 0.737, 0.728, 0.711, 0.697, # 0.666, 0.636, 0.627, 0.605, 0.574, 0.543, 0.542, 0.512, 0.482, 0.457, 0.451, 0.420, # 0.389, 0.381, 0.358]) # , 0.328, 0.305]) # # 0.0, # theta = np.array([0.0, 0.0, 6.7, 9.9, 13.4, 20.04, 18.074, 14.292, 11.909, 7.979, # 5.308, 4.715, 3.425, 2.083, 1.15, 1.115, 0.494, -0.015, -0.381, -0.475, -0.920, # -1.352, -1.469, -1.775]) # , -2.191, -2.5]) # af_idx = [0, 0, 0, 0, 0, 3, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7] r = np.array([ 0.6060, 0.8801, 1.2321, 1.5087, 1.7099, 1.9278, 2.1457, 2.3469, 2.5480, 2.7660, 2.9840, 3.1850, 3.3862, 3.6041, 3.8220, 4.0232, 4.2244, 4.4004, 4.5764, 4.7776, 4.9536 ]) chord = np.array([ 0.219, 0.181, 0.714, 0.711, 0.691, 0.668, 0.647, 0.627, 0.606, 0.584, 0.561, 0.542, 0.522, 0.499, 0.478, 0.457, 0.437, 0.419, 0.401, 0.381, 0.363 ]) theta = np.array([ 0.000, -0.098, 19.423, 14.318, 10.971, 8.244, 6.164, 4.689, 3.499, 2.478, 1.686, 1.115, 0.666, 0.267, -0.079, -0.381, -0.679, -0.933, -1.184, -1.466, -1.711 ]) af_idx = [0, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7] B = 2 # number of blades # # atmosphere # rho = 1.225 # rho = 1.224385 mu = 1.81206e-5 # import os afinit = CCAirfoil.initFromAerodynFile # just for shorthand basepath = 'af_files2' + os.path.sep airfoil_types = [0] * 8 airfoil_types[0] = afinit(basepath + 'cylinder.dat') airfoil_types[1] = afinit(basepath + 'S809_CLN_129.dat') # .648741 airfoil_types[2] = afinit(basepath + 'S809_CLN_185.dat') # .930365 airfoil_types[3] = afinit(basepath + 'S809_CLN_242.dat') # 1.217018 airfoil_types[4] = afinit(basepath + 'S809_CLN_298.dat') # 1.498642 airfoil_types[5] = afinit(basepath + 'S809_CLN_354.dat') # 1.780266 airfoil_types[6] = afinit(basepath + 'S809_CLN_410.dat') # 2.06189 airfoil_types[7] = afinit(basepath + 'S809_CLN_Outboard.dat') # ere is a cylindrical section at the root that extends from 0.508 to 0.883 m. The airfoil transition begins at approximately the 0.883- m radial station. af = [0] * len(r) for i in range(len(r)): af[i] = airfoil_types[af_idx[i]] tilt = 0.0 precone_ccblade = -cone yaw_ccblade = yaw shearExp = 0.05 hubHt = 12.192 nSector = 8 # create CCBlade object aeroanalysis = CCBlade(r, chord, theta, af, Rhub, Rtip, B, rho, mu, precone_ccblade, tilt, yaw_ccblade, shearExp, hubHt, nSector, yawcorrection=yawcorrection, coupled=coupled) # # set conditions # Uinf = 10.0 # tsr = 7.55 pitch = 3.0 Omega = rpm # Uinf*tsr/Rtip * 30.0/pi # convert to RPM # azimuth_ccblade = (360.0 - azimuth) % 360 # opposite definition azimuth_ccblade = azimuth # evaluate distributed loadsp Np, Tp = aeroanalysis.distributedAeroLoads(Uinf, Omega, pitch, azimuth_ccblade) q = 0.5 * rho * (Uinf + Omega * r * 3.14 / 30)**2 q_exp = np.interp(r_exp, r / Rtip, q) # cn = Np / (q * chord) # ct = Tp / (q * chord) # cn_exp = th_mean / (q_exp * c_exp) # ct_exp = tq_mean / (q_exp * c_exp) rstar = r / Rtip if return_bounds: return rstar, Np, Tp, r_exp, Np_exp, Tp_exp, Np_min, Tp_min, Np_max, Tp_max elif return_az_variation: Np_vec_exp = th_vec_all[station_idx, :] Tp_vec_exp = tq_vec_all[station_idx, :] n = 200 az_vec_sim = np.linspace(0, 360, n) Np_vec = np.zeros(n) Tp_vec = np.zeros(n) for iii in range(n): Np, Tp = aeroanalysis.distributedAeroLoads(Uinf, Omega, pitch, az_vec_sim[iii]) Np_vec[iii] = np.interp(r_exp[station_idx], r / Rtip, Np) Tp_vec[iii] = np.interp(r_exp[station_idx], r / Rtip, Tp) return az_vec, Np_vec_exp, Tp_vec_exp, az_vec_sim, Np_vec, Tp_vec else: return rstar, Np, Tp, r_exp, Np_exp, Tp_exp
class CCBlade(AeroBase): """blade element momentum code""" # (potential) variables r = Array( iotype='in', units='m', desc= 'radial locations where blade is defined (should be increasing and not go all the way to hub or tip)' ) chord = Array(iotype='in', units='m', desc='chord length at each section') theta = Array( iotype='in', units='deg', desc='twist angle at each section (positive decreases angle of attack)' ) Rhub = Float(iotype='in', units='m', desc='hub radius') Rtip = Float(iotype='in', units='m', desc='tip radius') hubHt = Float(iotype='in', units='m', desc='hub height') precone = Float(0.0, iotype='in', desc='precone angle', units='deg') tilt = Float(0.0, iotype='in', desc='shaft tilt', units='deg') yaw = Float(0.0, iotype='in', desc='yaw error', units='deg') # TODO: I've not hooked up the gradients for these ones yet. precurve = Array(iotype='in', units='m', desc='precurve at each section') precurveTip = Float(0.0, iotype='in', units='m', desc='precurve at tip') # parameters airfoil_files = List(Str, iotype='in', desc='names of airfoil file') B = Int(3, iotype='in', desc='number of blades') rho = Float(1.225, iotype='in', units='kg/m**3', desc='density of air') mu = Float(1.81206e-5, iotype='in', units='kg/(m*s)', desc='dynamic viscosity of air') shearExp = Float(0.2, iotype='in', desc='shear exponent') nSector = Int( 4, iotype='in', desc= 'number of sectors to divide rotor face into in computing thrust and power' ) tiploss = Bool(True, iotype='in', desc='include Prandtl tip loss model') hubloss = Bool(True, iotype='in', desc='include Prandtl hub loss model') wakerotation = Bool( True, iotype='in', desc= 'include effect of wake rotation (i.e., tangential induction factor is nonzero)' ) usecd = Bool(True, iotype='in', desc='use drag coefficient in computing induction factors') missing_deriv_policy = 'assume_zero' def execute(self): if len(self.precurve) == 0: self.precurve = np.zeros_like(self.r) # airfoil files n = len(self.airfoil_files) af = [0] * n afinit = CCAirfoil.initFromAerodynFile for i in range(n): af[i] = afinit(self.airfoil_files[i]) self.ccblade = CCBlade_PY(self.r, self.chord, self.theta, af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, self.precurve, self.precurveTip, tiploss=self.tiploss, hubloss=self.hubloss, wakerotation=self.wakerotation, usecd=self.usecd, derivatives=True) if self.run_case == 'power': # power, thrust, torque self.P, self.T, self.Q, self.dP, self.dT, self.dQ \ = self.ccblade.evaluate(self.Uhub, self.Omega, self.pitch, coefficient=False) elif self.run_case == 'loads': # distributed loads Np, Tp, self.dNp, self.dTp \ = self.ccblade.distributedAeroLoads(self.V_load, self.Omega_load, self.pitch_load, self.azimuth_load) # concatenate loads at root/tip self.loads.r = np.concatenate([[self.Rhub], self.r, [self.Rtip]]) Np = np.concatenate([[0.0], Np, [0.0]]) Tp = np.concatenate([[0.0], Tp, [0.0]]) # conform to blade-aligned coordinate system self.loads.Px = Np self.loads.Py = -Tp self.loads.Pz = 0 * Np # return other outputs needed self.loads.V = self.V_load self.loads.Omega = self.Omega_load self.loads.pitch = self.pitch_load self.loads.azimuth = self.azimuth_load def list_deriv_vars(self): if self.run_case == 'power': inputs = ('precone', 'tilt', 'hubHt', 'Rhub', 'Rtip', 'yaw', 'Uhub', 'Omega', 'pitch', 'r', 'chord', 'theta', 'precurve', 'precurveTip') outputs = ('P', 'T', 'Q') elif self.run_case == 'loads': inputs = ('r', 'chord', 'theta', 'Rhub', 'Rtip', 'hubHt', 'precone', 'tilt', 'yaw', 'V_load', 'Omega_load', 'pitch_load', 'azimuth_load', 'precurve') outputs = ('loads.r', 'loads.Px', 'loads.Py', 'loads.Pz', 'loads.V', 'loads.Omega', 'loads.pitch', 'loads.azimuth') return inputs, outputs def provideJ(self): if self.run_case == 'power': dP = self.dP dT = self.dT dQ = self.dQ jP = hstack([ dP['dprecone'], dP['dtilt'], dP['dhubHt'], dP['dRhub'], dP['dRtip'], dP['dyaw'], dP['dUinf'], dP['dOmega'], dP['dpitch'], dP['dr'], dP['dchord'], dP['dtheta'], dP['dprecurve'], dP['dprecurveTip'] ]) jT = hstack([ dT['dprecone'], dT['dtilt'], dT['dhubHt'], dT['dRhub'], dT['dRtip'], dT['dyaw'], dT['dUinf'], dT['dOmega'], dT['dpitch'], dT['dr'], dT['dchord'], dT['dtheta'], dT['dprecurve'], dT['dprecurveTip'] ]) jQ = hstack([ dQ['dprecone'], dQ['dtilt'], dQ['dhubHt'], dQ['dRhub'], dQ['dRtip'], dQ['dyaw'], dQ['dUinf'], dQ['dOmega'], dQ['dpitch'], dQ['dr'], dQ['dchord'], dQ['dtheta'], dQ['dprecurve'], dQ['dprecurveTip'] ]) J = vstack([jP, jT, jQ]) elif self.run_case == 'loads': dNp = self.dNp dTp = self.dTp n = len(self.r) dr_dr = vstack([np.zeros(n), np.eye(n), np.zeros(n)]) dr_dRhub = np.zeros(n + 2) dr_dRtip = np.zeros(n + 2) dr_dRhub[0] = 1.0 dr_dRtip[-1] = 1.0 dr = hstack([ dr_dr, np.zeros((n + 2, 2 * n)), dr_dRhub, dr_dRtip, np.zeros((n + 2, 8 + n)) ]) jNp = hstack([ dNp['dr'], dNp['dchord'], dNp['dtheta'], dNp['dRhub'], dNp['dRtip'], dNp['dhubHt'], dNp['dprecone'], dNp['dtilt'], dNp['dyaw'], dNp['dUinf'], dNp['dOmega'], dNp['dpitch'], dNp['dazimuth'], dNp['dprecurve'] ]) jTp = hstack([ dTp['dr'], dTp['dchord'], dTp['dtheta'], dTp['dRhub'], dTp['dRtip'], dTp['dhubHt'], dTp['dprecone'], dTp['dtilt'], dTp['dyaw'], dTp['dUinf'], dTp['dOmega'], dTp['dpitch'], dTp['dazimuth'], dTp['dprecurve'] ]) dPx = vstack([np.zeros(4 * n + 10), jNp, np.zeros(4 * n + 10)]) dPy = vstack([np.zeros(4 * n + 10), -jTp, np.zeros(4 * n + 10)]) dPz = np.zeros((n + 2, 4 * n + 10)) dV = np.zeros(4 * n + 10) dV[3 * n + 6] = 1.0 dOmega = np.zeros(4 * n + 10) dOmega[3 * n + 7] = 1.0 dpitch = np.zeros(4 * n + 10) dpitch[3 * n + 8] = 1.0 dazimuth = np.zeros(4 * n + 10) dazimuth[3 * n + 9] = 1.0 J = vstack([dr, dPx, dPy, dPz, dV, dOmega, dpitch, dazimuth]) return J
# 4 ---------- # set conditions Uinf = 10.0 tsr = 7.55 pitch = 0.0 Omega = Uinf * tsr / Rtip * 30.0 / pi # convert to RPM azimuth = 0.0 # # evaluate distributed loads # Np, Tp = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) # 4 ---------- # 5 ---------- loads, derivs = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) Np = loads['Np'] Tp = loads['Tp'] dNp = derivs['dNp'] dTp = derivs['dTp'] n = len(r) # n x n (diagonal) dNp_dr = dNp['dr'] dNp_dchord = dNp['dchord'] dNp_dtheta = dNp['dtheta'] dNp_dpresweep = dNp['dpresweep'] # n x n (tridiagonal) dNp_dprecurve = dNp['dprecurve']
class RegulatedPowerCurve(Component): # Implicit COMPONENT def __init__(self, naero, n_pc, n_pc_spline, regulation_reg_II5 = True, regulation_reg_III = False): super(RegulatedPowerCurve, self).__init__() # parameters self.add_param('control_Vin', val=0.0, units='m/s', desc='cut-in wind speed') self.add_param('control_Vout', val=0.0, units='m/s', desc='cut-out wind speed') self.add_param('control_ratedPower', val=0.0, units='W', desc='electrical rated power') self.add_param('control_minOmega', val=0.0, units='rpm', desc='minimum allowed rotor rotation speed') self.add_param('control_maxOmega', val=0.0, units='rpm', desc='maximum allowed rotor rotation speed') self.add_param('control_maxTS', val=0.0, units='m/s', desc='maximum allowed blade tip speed') self.add_param('control_tsr', val=0.0, desc='tip-speed ratio in Region 2 (should be optimized externally)') self.add_param('control_pitch', val=0.0, units='deg', desc='pitch angle in region 2 (and region 3 for fixed pitch machines)') self.add_param('drivetrainType', val=DRIVETRAIN_TYPE['GEARED'], pass_by_obj=True) self.add_param('drivetrainEff', val=0.0, desc='overwrite drivetrain model with a given efficiency, used for FAST analysis') self.add_param('r', val=np.zeros(naero), units='m', desc='radial locations where blade is defined (should be increasing and not go all the way to hub or tip)') self.add_param('chord', val=np.zeros(naero), units='m', desc='chord length at each section') self.add_param('theta', val=np.zeros(naero), units='deg', desc='twist angle at each section (positive decreases angle of attack)') self.add_param('Rhub', val=0.0, units='m', desc='hub radius') self.add_param('Rtip', val=0.0, units='m', desc='tip radius') self.add_param('hubHt', val=0.0, units='m', desc='hub height') self.add_param('precone', val=0.0, units='deg', desc='precone angle', ) self.add_param('tilt', val=0.0, units='deg', desc='shaft tilt', ) self.add_param('yaw', val=0.0, units='deg', desc='yaw error', ) self.add_param('precurve', val=np.zeros(naero), units='m', desc='precurve at each section') self.add_param('precurveTip', val=0.0, units='m', desc='precurve at tip') self.add_param('airfoils', val=[0]*naero, desc='CCAirfoil instances', pass_by_obj=True) self.add_param('B', val=0, desc='number of blades', pass_by_obj=True) self.add_param('rho', val=0.0, units='kg/m**3', desc='density of air') self.add_param('mu', val=0.0, units='kg/(m*s)', desc='dynamic viscosity of air') self.add_param('shearExp', val=0.0, desc='shear exponent') self.add_param('nSector', val=4, desc='number of sectors to divide rotor face into in computing thrust and power', pass_by_obj=True) self.add_param('tiploss', val=True, desc='include Prandtl tip loss model', pass_by_obj=True) self.add_param('hubloss', val=True, desc='include Prandtl hub loss model', pass_by_obj=True) self.add_param('wakerotation', val=True, desc='include effect of wake rotation (i.e., tangential induction factor is nonzero)', pass_by_obj=True) self.add_param('usecd', val=True, desc='use drag coefficient in computing induction factors', pass_by_obj=True) # outputs self.add_output('V', val=np.zeros(n_pc), units='m/s', desc='wind vector') self.add_output('Omega', val=np.zeros(n_pc), units='rpm', desc='rotor rotational speed') self.add_output('pitch', val=np.zeros(n_pc), units='deg', desc='rotor pitch schedule') self.add_output('P', val=np.zeros(n_pc), units='W', desc='rotor electrical power') self.add_output('T', val=np.zeros(n_pc), units='N', desc='rotor aerodynamic thrust') self.add_output('Q', val=np.zeros(n_pc), units='N*m', desc='rotor aerodynamic torque') self.add_output('M', val=np.zeros(n_pc), units='N*m', desc='blade root moment') self.add_output('Cp', val=np.zeros(n_pc), desc='rotor electrical power coefficient') self.add_output('V_spline', val=np.zeros(n_pc_spline), units='m/s', desc='wind vector') self.add_output('P_spline', val=np.zeros(n_pc_spline), units='W', desc='rotor electrical power') self.add_output('V_R25', val=0.0, units='m/s', desc='region 2.5 transition wind speed') self.add_output('rated_V', val=0.0, units='m/s', desc='rated wind speed') self.add_output('rated_Omega', val=0.0, units='rpm', desc='rotor rotation speed at rated') self.add_output('rated_pitch', val=0.0, units='deg', desc='pitch setting at rated') self.add_output('rated_T', val=0.0, units='N', desc='rotor aerodynamic thrust at rated') self.add_output('rated_Q', val=0.0, units='N*m', desc='rotor aerodynamic torque at rated') self.add_output('ax_induct_cutin', val=np.zeros(naero), desc='rotor axial induction at cut-in wind speed along blade span') self.add_output('tang_induct_cutin', val=np.zeros(naero), desc='rotor tangential induction at cut-in wind speed along blade span') self.add_output('aoa_cutin', val=np.zeros(naero), desc='angle of attack distribution along blade span at cut-in wind speed') self.naero = naero self.n_pc = n_pc self.n_pc_spline = n_pc_spline self.lock_pitchII = False self.regulation_reg_II5 = regulation_reg_II5 self.regulation_reg_III = regulation_reg_III self.deriv_options['form'] = 'central' self.deriv_options['step_calc'] = 'relative' def solve_nonlinear(self, params, unknowns, resids): self.ccblade = CCBlade(params['r'], params['chord'], params['theta'], params['airfoils'], params['Rhub'], params['Rtip'], params['B'], params['rho'], params['mu'], params['precone'], params['tilt'], params['yaw'], params['shearExp'], params['hubHt'], params['nSector']) Uhub = np.linspace(params['control_Vin'],params['control_Vout'], self.n_pc) P_aero = np.zeros_like(Uhub) Cp_aero = np.zeros_like(Uhub) P = np.zeros_like(Uhub) Cp = np.zeros_like(Uhub) T = np.zeros_like(Uhub) Q = np.zeros_like(Uhub) M = np.zeros_like(Uhub) Omega = np.zeros_like(Uhub) pitch = np.zeros_like(Uhub) + params['control_pitch'] Omega_max = min([params['control_maxTS'] / params['Rtip'], params['control_maxOmega']*np.pi/30.]) # Region II for i in range(len(Uhub)): Omega[i] = Uhub[i] * params['control_tsr'] / params['Rtip'] P_aero, T, Q, M, Cp_aero, _, _, _ = self.ccblade.evaluate(Uhub, Omega * 30. / np.pi, pitch, coefficients=True) P, eff = CSMDrivetrain(P_aero, params['control_ratedPower'], params['drivetrainType']) Cp = Cp_aero*eff # search for Region 2.5 bounds for i in range(len(Uhub)): if Omega[i] > Omega_max and P[i] < params['control_ratedPower']: Omega[i] = Omega_max Uhub[i] = Omega[i] * params['Rtip'] / params['control_tsr'] P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch[i]], coefficients=True) P[i], eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff regionIIhalf = True i_IIhalf_start = i unknowns['V_R25'] = Uhub[i] break if P[i] > params['control_ratedPower']: regionIIhalf = False break def maxPregionIIhalf(pitch, Uhub, Omega): Uhub_i = Uhub Omega_i = Omega pitch = pitch P, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) return -P # Solve for regoin 2.5 pitch options = {} options['disp'] = False options['xatol'] = 1.e-2 if regionIIhalf == True: for i in range(i_IIhalf_start + 1, len(Uhub)): Omega[i] = Omega_max pitch0 = pitch[i-1] bnds = [pitch0 - 10., pitch0 + 10.] pitch_regionIIhalf = minimize_scalar(lambda x: maxPregionIIhalf(x, Uhub[i], Omega[i]), bounds=bnds, method='bounded', options=options)['x'] pitch[i] = pitch_regionIIhalf P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch[i]], coefficients=True) P[i], eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff if P[i] > params['control_ratedPower']: break def constantPregionIII(pitch, Uhub, Omega): Uhub_i = Uhub Omega_i = Omega pitch = pitch P_aero, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) P, eff = CSMDrivetrain(P_aero, params['control_ratedPower'], params['drivetrainType']) return abs(P - params['control_ratedPower']) if regionIIhalf == True: # Rated conditions def min_Uhub_rated_II12(min_params): return min_params[1] def get_Uhub_rated_II12(min_params): Uhub_i = min_params[1] Omega_i = Omega_max pitch = min_params[0] P_aero_i, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) P_i,eff = CSMDrivetrain(P_aero_i, params['control_ratedPower'], params['drivetrainType']) return abs(P_i - params['control_ratedPower']) x0 = [pitch[i] + 2. , Uhub[i]] bnds = [(pitch0, pitch0 + 10.),(Uhub[i-1],Uhub[i+1])] const = {} const['type'] = 'eq' const['fun'] = get_Uhub_rated_II12 params_rated = minimize(min_Uhub_rated_II12, x0, method='SLSQP', tol = 1.e-2, bounds=bnds, constraints=const) U_rated = params_rated.x[1] if not np.isnan(U_rated): Uhub[i] = U_rated pitch[i] = params_rated.x[0] else: print('Regulation trajectory is struggling to find a solution for rated wind speed. Check rotor_aeropower.py') Omega[i] = Omega_max P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch0], coefficients=True) P_i, eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff P[i] = params['control_ratedPower'] else: # Rated conditions def get_Uhub_rated_noII12(pitch, Uhub): Uhub_i = Uhub Omega_i = min([Uhub_i * params['control_tsr'] / params['Rtip'], Omega_max]) pitch_i = pitch P_aero_i, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch_i], coefficients=False) P_i, eff = CSMDrivetrain(P_aero_i, params['control_ratedPower'], params['drivetrainType']) return abs(P_i - params['control_ratedPower']) bnds = [Uhub[i-1], Uhub[i+1]] U_rated = minimize_scalar(lambda x: get_Uhub_rated_noII12(pitch[i], x), bounds=bnds, method='bounded', options=options)['x'] if not np.isnan(U_rated): Uhub[i] = U_rated else: print('Regulation trajectory is struggling to find a solution for rated wind speed. Check rotor_aeropower.py') Omega[i] = min([Uhub[i] * params['control_tsr'] / params['Rtip'], Omega_max]) pitch0 = pitch[i] P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch0], coefficients=True) P[i], eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff for j in range(i + 1,len(Uhub)): Omega[j] = Omega[i] if self.regulation_reg_III == True: pitch0 = pitch[j-1] bnds = [pitch0, pitch0 + 15.] pitch_regionIII = minimize_scalar(lambda x: constantPregionIII(x, Uhub[j], Omega[j]), bounds=bnds, method='bounded', options=options)['x'] pitch[j] = pitch_regionIII P_aero[j], T[j], Q[j], M[j], Cp_aero[j], _, _, _ = self.ccblade.evaluate([Uhub[j]], [Omega[j] * 30. / np.pi], [pitch[j]], coefficients=True) P[j], eff = CSMDrivetrain(P_aero[j], params['control_ratedPower'], params['drivetrainType']) Cp[j] = Cp_aero[j]*eff if abs(P[j] - params['control_ratedPower']) > 1e+4: print('The pitch in region III is not being determined correctly at wind speed ' + str(Uhub[j]) + ' m/s') P[j] = params['control_ratedPower'] T[j] = T[j-1] Q[j] = P[j] / Omega[j] M[j] = M[j-1] pitch[j] = pitch[j-1] Cp[j] = P[j] / (0.5 * params['rho'] * np.pi * params['Rtip']**2 * Uhub[i]**3) P[j] = params['control_ratedPower'] else: P[j] = params['control_ratedPower'] T[j] = 0 Q[j] = Q[i] M[j] = 0 pitch[j] = 0 Cp[j] = P[j] / (0.5 * params['rho'] * np.pi * params['Rtip']**2 * Uhub[i]**3) unknowns['T'] = T unknowns['Q'] = Q unknowns['Omega'] = Omega * 30. / np.pi unknowns['P'] = P unknowns['Cp'] = Cp unknowns['V'] = Uhub unknowns['M'] = M unknowns['pitch'] = pitch self.ccblade.induction_inflow = True a_regII, ap_regII, alpha_regII = self.ccblade.distributedAeroLoads(Uhub[0], Omega[0] * 30. / np.pi, pitch[0], 0.0) # Fit spline to powercurve for higher grid density spline = PchipInterpolator(Uhub, P) V_spline = np.linspace(params['control_Vin'],params['control_Vout'], num=self.n_pc_spline) P_spline = spline(V_spline) # outputs idx_rated = list(Uhub).index(U_rated) unknowns['rated_V'] = U_rated unknowns['rated_Omega'] = Omega[idx_rated] * 30. / np.pi unknowns['rated_pitch'] = pitch[idx_rated] unknowns['rated_T'] = T[idx_rated] unknowns['rated_Q'] = Q[idx_rated] unknowns['V_spline'] = V_spline unknowns['P_spline'] = P_spline unknowns['ax_induct_cutin'] = a_regII unknowns['tang_induct_cutin'] = ap_regII unknowns['aoa_cutin'] = alpha_regII
class TestGradients(unittest.TestCase): def setUp(self): # geometry self.Rhub = 1.5 self.Rtip = 63.0 self.r = np.array([2.8667, 5.6000, 8.3333, 11.7500, 15.8500, 19.9500, 24.0500, 28.1500, 32.2500, 36.3500, 40.4500, 44.5500, 48.6500, 52.7500, 56.1667, 58.9000, 61.6333]) self.chord = np.array([3.542, 3.854, 4.167, 4.557, 4.652, 4.458, 4.249, 4.007, 3.748, 3.502, 3.256, 3.010, 2.764, 2.518, 2.313, 2.086, 1.419]) self.theta = np.array([13.308, 13.308, 13.308, 13.308, 11.480, 10.162, 9.011, 7.795, 6.544, 5.361, 4.188, 3.125, 2.319, 1.526, 0.863, 0.370, 0.106]) self.B = 3 # number of blades # atmosphere self.rho = 1.225 self.mu = 1.81206e-5 afinit = CCAirfoil.initFromAerodynFile # just for shorthand basepath = path.join(path.dirname(path.realpath(__file__)), '5MW_AFFiles') + path.sep # load all airfoils airfoil_types = [0]*8 airfoil_types[0] = afinit(basepath + 'Cylinder1.dat') airfoil_types[1] = afinit(basepath + 'Cylinder2.dat') airfoil_types[2] = afinit(basepath + 'DU40_A17.dat') airfoil_types[3] = afinit(basepath + 'DU35_A17.dat') airfoil_types[4] = afinit(basepath + 'DU30_A17.dat') airfoil_types[5] = afinit(basepath + 'DU25_A17.dat') airfoil_types[6] = afinit(basepath + 'DU21_A17.dat') airfoil_types[7] = afinit(basepath + 'NACA64_A17.dat') # place at appropriate radial stations af_idx = [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7] self.af = [0]*len(self.r) for i in range(len(self.r)): self.af[i] = airfoil_types[af_idx[i]] self.tilt = -5.0 self.precone = 2.5 self.yaw = 0.0 self.shearExp = 0.2 self.hubHt = 80.0 self.nSector = 8 # create CCBlade object self.rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True) # set conditions self.Uinf = 10.0 tsr = 7.55 self.pitch = 0.0 self.Omega = self.Uinf*tsr/self.Rtip * 30.0/pi # convert to RPM self.azimuth = 90 self.Np, self.Tp, self.dNp_dX, self.dTp_dX, self.dNp_dprecurve, self.dTp_dprecurve = \ self.rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) self.P, self.T, self.Q, self.dP_ds, self.dT_ds, self.dQ_ds, self.dP_dv, self.dT_dv, \ self.dQ_dv = self.rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) self.CP, self.CT, self.CQ, self.dCP_ds, self.dCT_ds, self.dCQ_ds, self.dCP_dv, self.dCT_dv, \ self.dCQ_dv = self.rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) self.rotor.derivatives = False self.n = len(self.r) # X = [r, chord, theta, Rhub, Rtip, presweep, precone, tilt, hubHt] # scalars = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip] # vectors = [r, chord, theta, precurve, presweep] def test_dr1(self): dNp_dr = self.dNp_dX[0, :] dTp_dr = self.dTp_dX[0, :] dNp_dr_fd = np.zeros(self.n) dTp_dr_fd = np.zeros(self.n) for i in range(self.n): r = np.array(self.r) delta = 1e-6*r[i] r[i] += delta rotor = CCBlade(r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dr_fd[i] = (Npd[i] - self.Np[i]) / delta dTp_dr_fd[i] = (Tpd[i] - self.Tp[i]) / delta np.testing.assert_allclose(dNp_dr_fd, dNp_dr, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dTp_dr_fd, dTp_dr, rtol=1e-4, atol=1e-8) def test_dr2(self): dT_dr = self.dT_dv[0, 0, :] dQ_dr = self.dQ_dv[0, 0, :] dP_dr = self.dP_dv[0, 0, :] dT_dr_fd = np.zeros(self.n) dQ_dr_fd = np.zeros(self.n) dP_dr_fd = np.zeros(self.n) for i in range(self.n): r = np.array(self.r) delta = 1e-6*r[i] r[i] += delta rotor = CCBlade(r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dr_fd[i] = (Td - self.T) / delta dQ_dr_fd[i] = (Qd - self.Q) / delta dP_dr_fd[i] = (Pd - self.P) / delta np.testing.assert_allclose(dT_dr_fd, dT_dr, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dQ_dr_fd, dQ_dr, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dP_dr_fd, dP_dr, rtol=3e-4, atol=1e-8) def test_dr3(self): dCT_dr = self.dCT_dv[0, 0, :] dCQ_dr = self.dCQ_dv[0, 0, :] dCP_dr = self.dCP_dv[0, 0, :] dCT_dr_fd = np.zeros(self.n) dCQ_dr_fd = np.zeros(self.n) dCP_dr_fd = np.zeros(self.n) for i in range(self.n): r = np.array(self.r) delta = 1e-6*r[i] r[i] += delta rotor = CCBlade(r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dr_fd[i] = (CTd - self.CT) / delta dCQ_dr_fd[i] = (CQd - self.CQ) / delta dCP_dr_fd[i] = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dr_fd, dCT_dr, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dCQ_dr_fd, dCQ_dr, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dCP_dr_fd, dCP_dr, rtol=3e-4, atol=1e-8) def test_dchord1(self): dNp_dchord = self.dNp_dX[1, :] dTp_dchord = self.dTp_dX[1, :] dNp_dchord_fd = np.zeros(self.n) dTp_dchord_fd = np.zeros(self.n) for i in range(self.n): chord = np.array(self.chord) delta = 1e-6*chord[i] chord[i] += delta rotor = CCBlade(self.r, chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dchord_fd[i] = (Npd[i] - self.Np[i]) / delta dTp_dchord_fd[i] = (Tpd[i] - self.Tp[i]) / delta np.testing.assert_allclose(dNp_dchord_fd, dNp_dchord, rtol=1e-6, atol=1e-8) np.testing.assert_allclose(dTp_dchord_fd, dTp_dchord, rtol=5e-5, atol=1e-8) def test_dchord2(self): dT_dchord = self.dT_dv[0, 1, :] dQ_dchord = self.dQ_dv[0, 1, :] dP_dchord = self.dP_dv[0, 1, :] dT_dchord_fd = np.zeros(self.n) dQ_dchord_fd = np.zeros(self.n) dP_dchord_fd = np.zeros(self.n) for i in range(self.n): chord = np.array(self.chord) delta = 1e-6*chord[i] chord[i] += delta rotor = CCBlade(self.r, chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dchord_fd[i] = (Td - self.T) / delta dQ_dchord_fd[i] = (Qd - self.Q) / delta dP_dchord_fd[i] = (Pd - self.P) / delta np.testing.assert_allclose(dT_dchord_fd, dT_dchord, rtol=5e-6, atol=1e-8) np.testing.assert_allclose(dQ_dchord_fd, dQ_dchord, rtol=7e-5, atol=1e-8) np.testing.assert_allclose(dP_dchord_fd, dP_dchord, rtol=7e-5, atol=1e-8) def test_dchord3(self): dCT_dchord = self.dCT_dv[0, 1, :] dCQ_dchord = self.dCQ_dv[0, 1, :] dCP_dchord = self.dCP_dv[0, 1, :] dCT_dchord_fd = np.zeros(self.n) dCQ_dchord_fd = np.zeros(self.n) dCP_dchord_fd = np.zeros(self.n) for i in range(self.n): chord = np.array(self.chord) delta = 1e-6*chord[i] chord[i] += delta rotor = CCBlade(self.r, chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dchord_fd[i] = (CTd - self.CT) / delta dCQ_dchord_fd[i] = (CQd - self.CQ) / delta dCP_dchord_fd[i] = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dchord_fd, dCT_dchord, rtol=5e-6, atol=1e-8) np.testing.assert_allclose(dCQ_dchord_fd, dCQ_dchord, rtol=7e-5, atol=1e-8) np.testing.assert_allclose(dCP_dchord_fd, dCP_dchord, rtol=7e-5, atol=1e-8) def test_dtheta1(self): dNp_dtheta = self.dNp_dX[2, :] dTp_dtheta = self.dTp_dX[2, :] dNp_dtheta_fd = np.zeros(self.n) dTp_dtheta_fd = np.zeros(self.n) for i in range(self.n): theta = np.array(self.theta) delta = 1e-6*theta[i] theta[i] += delta rotor = CCBlade(self.r, self.chord, theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dtheta_fd[i] = (Npd[i] - self.Np[i]) / delta dTp_dtheta_fd[i] = (Tpd[i] - self.Tp[i]) / delta np.testing.assert_allclose(dNp_dtheta_fd, dNp_dtheta, rtol=1e-6, atol=1e-8) np.testing.assert_allclose(dTp_dtheta_fd, dTp_dtheta, rtol=1e-4, atol=1e-8) def test_dtheta2(self): dT_dtheta = self.dT_dv[0, 2, :] dQ_dtheta = self.dQ_dv[0, 2, :] dP_dtheta = self.dP_dv[0, 2, :] dT_dtheta_fd = np.zeros(self.n) dQ_dtheta_fd = np.zeros(self.n) dP_dtheta_fd = np.zeros(self.n) for i in range(self.n): theta = np.array(self.theta) delta = 1e-6*theta[i] theta[i] += delta rotor = CCBlade(self.r, self.chord, theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dtheta_fd[i] = (Td - self.T) / delta dQ_dtheta_fd[i] = (Qd - self.Q) / delta dP_dtheta_fd[i] = (Pd - self.P) / delta np.testing.assert_allclose(dT_dtheta_fd, dT_dtheta, rtol=7e-5, atol=1e-8) np.testing.assert_allclose(dQ_dtheta_fd, dQ_dtheta, rtol=7e-5, atol=1e-8) np.testing.assert_allclose(dP_dtheta_fd, dP_dtheta, rtol=7e-5, atol=1e-8) def test_dtheta3(self): dCT_dtheta = self.dCT_dv[0, 2, :] dCQ_dtheta = self.dCQ_dv[0, 2, :] dCP_dtheta = self.dCP_dv[0, 2, :] dCT_dtheta_fd = np.zeros(self.n) dCQ_dtheta_fd = np.zeros(self.n) dCP_dtheta_fd = np.zeros(self.n) for i in range(self.n): theta = np.array(self.theta) delta = 1e-6*theta[i] theta[i] += delta rotor = CCBlade(self.r, self.chord, theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dtheta_fd[i] = (CTd - self.CT) / delta dCQ_dtheta_fd[i] = (CQd - self.CQ) / delta dCP_dtheta_fd[i] = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dtheta_fd, dCT_dtheta, rtol=5e-6, atol=1e-8) np.testing.assert_allclose(dCQ_dtheta_fd, dCQ_dtheta, rtol=7e-5, atol=1e-8) np.testing.assert_allclose(dCP_dtheta_fd, dCP_dtheta, rtol=7e-5, atol=1e-8) def test_dRhub1(self): dNp_dRhub = self.dNp_dX[3, :] dTp_dRhub = self.dTp_dX[3, :] Rhub = float(self.Rhub) delta = 1e-6*Rhub Rhub += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dRhub_fd = (Npd - self.Np) / delta dTp_dRhub_fd = (Tpd - self.Tp) / delta np.testing.assert_allclose(dNp_dRhub_fd, dNp_dRhub, rtol=1e-5, atol=1e-7) np.testing.assert_allclose(dTp_dRhub_fd, dTp_dRhub, rtol=1e-4, atol=1e-7) def test_dRhub2(self): dT_dRhub = self.dT_ds[0, 3] dQ_dRhub = self.dQ_ds[0, 3] dP_dRhub = self.dP_ds[0, 3] Rhub = float(self.Rhub) delta = 1e-6*Rhub Rhub += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dRhub_fd = (Td - self.T) / delta dQ_dRhub_fd = (Qd - self.Q) / delta dP_dRhub_fd = (Pd - self.P) / delta np.testing.assert_allclose(dT_dRhub_fd, dT_dRhub, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dQ_dRhub_fd, dQ_dRhub, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dP_dRhub_fd, dP_dRhub, rtol=5e-5, atol=1e-8) def test_dRhub3(self): dCT_dRhub = self.dCT_ds[0, 3] dCQ_dRhub = self.dCQ_ds[0, 3] dCP_dRhub = self.dCP_ds[0, 3] Rhub = float(self.Rhub) delta = 1e-6*Rhub Rhub += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dRhub_fd = (CTd - self.CT) / delta dCQ_dRhub_fd = (CQd - self.CQ) / delta dCP_dRhub_fd = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dRhub_fd, dCT_dRhub, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dCQ_dRhub_fd, dCQ_dRhub, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dCP_dRhub_fd, dCP_dRhub, rtol=5e-5, atol=1e-8) def test_dRtip1(self): dNp_dRtip = self.dNp_dX[4, :] dTp_dRtip = self.dTp_dX[4, :] Rtip = float(self.Rtip) delta = 1e-6*Rtip Rtip += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dRtip_fd = (Npd - self.Np) / delta dTp_dRtip_fd = (Tpd - self.Tp) / delta np.testing.assert_allclose(dNp_dRtip_fd, dNp_dRtip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dTp_dRtip_fd, dTp_dRtip, rtol=1e-4, atol=1e-8) def test_dRtip2(self): dT_dRtip = self.dT_ds[0, 4] dQ_dRtip = self.dQ_ds[0, 4] dP_dRtip = self.dP_ds[0, 4] Rtip = float(self.Rtip) delta = 1e-6*Rtip Rtip += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dRtip_fd = (Td - self.T) / delta dQ_dRtip_fd = (Qd - self.Q) / delta dP_dRtip_fd = (Pd - self.P) / delta np.testing.assert_allclose(dT_dRtip_fd, dT_dRtip, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dQ_dRtip_fd, dQ_dRtip, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dP_dRtip_fd, dP_dRtip, rtol=5e-5, atol=1e-8) def test_dRtip3(self): dCT_dRtip = self.dCT_ds[0, 4] dCQ_dRtip = self.dCQ_ds[0, 4] dCP_dRtip = self.dCP_ds[0, 4] Rtip = float(self.Rtip) delta = 1e-6*Rtip Rtip += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dRtip_fd = (CTd - self.CT) / delta dCQ_dRtip_fd = (CQd - self.CQ) / delta dCP_dRtip_fd = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dRtip_fd, dCT_dRtip, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dCQ_dRtip_fd, dCQ_dRtip, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dCP_dRtip_fd, dCP_dRtip, rtol=5e-5, atol=1e-8) def test_dprecone1(self): dNp_dprecone = self.dNp_dX[6, :] dTp_dprecone = self.dTp_dX[6, :] precone = float(self.precone) delta = 1e-6*precone precone += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecone_fd = (Npd - self.Np) / delta dTp_dprecone_fd = (Tpd - self.Tp) / delta np.testing.assert_allclose(dNp_dprecone_fd, dNp_dprecone, rtol=1e-6, atol=1e-8) np.testing.assert_allclose(dTp_dprecone_fd, dTp_dprecone, rtol=1e-6, atol=1e-8) def test_dprecone2(self): dT_dprecone = self.dT_ds[0, 0] dQ_dprecone = self.dQ_ds[0, 0] dP_dprecone = self.dP_ds[0, 0] precone = float(self.precone) delta = 1e-6*precone precone += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dprecone_fd = (Td - self.T) / delta dQ_dprecone_fd = (Qd - self.Q) / delta dP_dprecone_fd = (Pd - self.P) / delta np.testing.assert_allclose(dT_dprecone_fd, dT_dprecone, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dQ_dprecone_fd, dQ_dprecone, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dP_dprecone_fd, dP_dprecone, rtol=5e-5, atol=1e-8) def test_dprecone3(self): dCT_dprecone = self.dCT_ds[0, 0] dCQ_dprecone = self.dCQ_ds[0, 0] dCP_dprecone = self.dCP_ds[0, 0] precone = float(self.precone) delta = 1e-6*precone precone += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dprecone_fd = (CTd - self.CT) / delta dCQ_dprecone_fd = (CQd - self.CQ) / delta dCP_dprecone_fd = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dprecone_fd, dCT_dprecone, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dCQ_dprecone_fd, dCQ_dprecone, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dCP_dprecone_fd, dCP_dprecone, rtol=5e-5, atol=1e-8) def test_dtilt1(self): dNp_dtilt = self.dNp_dX[7, :] dTp_dtilt = self.dTp_dX[7, :] tilt = float(self.tilt) delta = 1e-6*tilt tilt += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dtilt_fd = (Npd - self.Np) / delta dTp_dtilt_fd = (Tpd - self.Tp) / delta np.testing.assert_allclose(dNp_dtilt_fd, dNp_dtilt, rtol=1e-6, atol=1e-8) np.testing.assert_allclose(dTp_dtilt_fd, dTp_dtilt, rtol=1e-5, atol=1e-8) def test_dtilt2(self): dT_dtilt = self.dT_ds[0, 1] dQ_dtilt = self.dQ_ds[0, 1] dP_dtilt = self.dP_ds[0, 1] tilt = float(self.tilt) delta = 1e-6*tilt tilt += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dtilt_fd = (Td - self.T) / delta dQ_dtilt_fd = (Qd - self.Q) / delta dP_dtilt_fd = (Pd - self.P) / delta np.testing.assert_allclose(dT_dtilt_fd, dT_dtilt, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dQ_dtilt_fd, dQ_dtilt, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dP_dtilt_fd, dP_dtilt, rtol=5e-5, atol=1e-8) def test_dtilt3(self): dCT_dtilt = self.dCT_ds[0, 1] dCQ_dtilt = self.dCQ_ds[0, 1] dCP_dtilt = self.dCP_ds[0, 1] tilt = float(self.tilt) delta = 1e-6*tilt tilt += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dtilt_fd = (CTd - self.CT) / delta dCQ_dtilt_fd = (CQd - self.CQ) / delta dCP_dtilt_fd = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dtilt_fd, dCT_dtilt, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dCQ_dtilt_fd, dCQ_dtilt, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dCP_dtilt_fd, dCP_dtilt, rtol=5e-5, atol=1e-8) def test_dhubht1(self): dNp_dhubht = self.dNp_dX[8, :] dTp_dhubht = self.dTp_dX[8, :] hubht = float(self.hubHt) delta = 1e-6*hubht hubht += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, hubht, self.nSector, derivatives=False) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dhubht_fd = (Npd - self.Np) / delta dTp_dhubht_fd = (Tpd - self.Tp) / delta np.testing.assert_allclose(dNp_dhubht_fd, dNp_dhubht, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dTp_dhubht_fd, dTp_dhubht, rtol=1e-5, atol=1e-8) def test_dhubht2(self): dT_dhubht = self.dT_ds[0, 2] dQ_dhubht = self.dQ_ds[0, 2] dP_dhubht = self.dP_ds[0, 2] hubht = float(self.hubHt) delta = 1e-6*hubht hubht += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, hubht, self.nSector, derivatives=False) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dhubht_fd = (Td - self.T) / delta dQ_dhubht_fd = (Qd - self.Q) / delta dP_dhubht_fd = (Pd - self.P) / delta np.testing.assert_allclose(dT_dhubht_fd, dT_dhubht, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dQ_dhubht_fd, dQ_dhubht, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dP_dhubht_fd, dP_dhubht, rtol=5e-5, atol=1e-8) def test_dhubht3(self): dCT_dhubht = self.dCT_ds[0, 2] dCQ_dhubht = self.dCQ_ds[0, 2] dCP_dhubht = self.dCP_ds[0, 2] hubht = float(self.hubHt) delta = 1e-6*hubht hubht += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, hubht, self.nSector, derivatives=False) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dhubht_fd = (CTd - self.CT) / delta dCQ_dhubht_fd = (CQd - self.CQ) / delta dCP_dhubht_fd = (CPd - self.CP) / delta np.testing.assert_allclose(dCT_dhubht_fd, dCT_dhubht, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dCQ_dhubht_fd, dCQ_dhubht, rtol=5e-5, atol=1e-8) np.testing.assert_allclose(dCP_dhubht_fd, dCP_dhubht, rtol=5e-5, atol=1e-8) def test_dprecurve1(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecurve_fd = np.zeros((self.n, self.n)) dTp_dprecurve_fd = np.zeros((self.n, self.n)) for i in range(self.n): pc = np.array(precurve) delta = 1e-6*pc[i] pc[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=pc, precurveTip=precurveTip) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecurve_fd[i, :] = (Npd - Np) / delta dTp_dprecurve_fd[i, :] = (Tpd - Tp) / delta np.testing.assert_allclose(dNp_dprecurve_fd, dNp_dprecurve, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dTp_dprecurve_fd, dTp_dprecurve, rtol=3e-4, atol=1e-8) def test_dprecurve2(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) P, T, Q, dP_ds, dT_ds, dQ_ds, dP_dv, dT_dv, dQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dprecurve = dT_dv[0, 3, :] dQ_dprecurve = dQ_dv[0, 3, :] dP_dprecurve = dP_dv[0, 3, :] dT_dprecurve_fd = np.zeros(self.n) dQ_dprecurve_fd = np.zeros(self.n) dP_dprecurve_fd = np.zeros(self.n) for i in range(self.n): pc = np.array(precurve) delta = 1e-6*pc[i] pc[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=pc, precurveTip=precurveTip) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dprecurve_fd[i] = (Td - T) / delta dQ_dprecurve_fd[i] = (Qd - Q) / delta dP_dprecurve_fd[i] = (Pd - P) / delta np.testing.assert_allclose(dT_dprecurve_fd, dT_dprecurve, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dQ_dprecurve_fd, dQ_dprecurve, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dP_dprecurve_fd, dP_dprecurve, rtol=3e-4, atol=1e-8) def test_dprecurve3(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) CP, CT, CQ, dCP_ds, dCT_ds, dCQ_ds, dCP_dv, dCT_dv, dCQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dprecurve = dCT_dv[0, 3, :] dCQ_dprecurve = dCQ_dv[0, 3, :] dCP_dprecurve = dCP_dv[0, 3, :] dCT_dprecurve_fd = np.zeros(self.n) dCQ_dprecurve_fd = np.zeros(self.n) dCP_dprecurve_fd = np.zeros(self.n) for i in range(self.n): pc = np.array(precurve) delta = 1e-6*pc[i] pc[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=pc, precurveTip=precurveTip) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dprecurve_fd[i] = (CTd - CT) / delta dCQ_dprecurve_fd[i] = (CQd - CQ) / delta dCP_dprecurve_fd[i] = (CPd - CP) / delta np.testing.assert_allclose(dCT_dprecurve_fd, dCT_dprecurve, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dCQ_dprecurve_fd, dCQ_dprecurve, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dCP_dprecurve_fd, dCP_dprecurve, rtol=3e-4, atol=1e-8) def test_dpresweep1(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dpresweep = dNp_dX[5, :] dTp_dpresweep = dTp_dX[5, :] dNp_dpresweep_fd = np.zeros(self.n) dTp_dpresweep_fd = np.zeros(self.n) for i in range(self.n): ps = np.array(presweep) delta = 1e-6*ps[i] ps[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=ps, presweepTip=presweepTip) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dpresweep_fd[i] = (Npd[i] - Np[i]) / delta dTp_dpresweep_fd[i] = (Tpd[i] - Tp[i]) / delta np.testing.assert_allclose(dNp_dpresweep_fd, dNp_dpresweep, rtol=1e-5, atol=1e-8) np.testing.assert_allclose(dTp_dpresweep_fd, dTp_dpresweep, rtol=1e-5, atol=1e-8) def test_dpresweep2(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) P, T, Q, dP_ds, dT_ds, dQ_ds, dP_dv, dT_dv, dQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dpresweep = dT_dv[0, 4, :] dQ_dpresweep = dQ_dv[0, 4, :] dP_dpresweep = dP_dv[0, 4, :] dT_dpresweep_fd = np.zeros(self.n) dQ_dpresweep_fd = np.zeros(self.n) dP_dpresweep_fd = np.zeros(self.n) for i in range(self.n): ps = np.array(presweep) delta = 1e-6*ps[i] ps[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=ps, presweepTip=presweepTip) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dpresweep_fd[i] = (Td - T) / delta dQ_dpresweep_fd[i] = (Qd - Q) / delta dP_dpresweep_fd[i] = (Pd - P) / delta np.testing.assert_allclose(dT_dpresweep_fd, dT_dpresweep, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dQ_dpresweep_fd, dQ_dpresweep, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dP_dpresweep_fd, dP_dpresweep, rtol=3e-4, atol=1e-8) def test_dpresweep3(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) CP, CT, CQ, dCP_ds, dCT_ds, dCQ_ds, dCP_dv, dCT_dv, dCQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dpresweep = dCT_dv[0, 4, :] dCQ_dpresweep = dCQ_dv[0, 4, :] dCP_dpresweep = dCP_dv[0, 4, :] dCT_dpresweep_fd = np.zeros(self.n) dCQ_dpresweep_fd = np.zeros(self.n) dCP_dpresweep_fd = np.zeros(self.n) for i in range(self.n): ps = np.array(presweep) delta = 1e-6*ps[i] ps[i] += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=ps, presweepTip=presweepTip) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dpresweep_fd[i] = (CTd - CT) / delta dCQ_dpresweep_fd[i] = (CQd - CQ) / delta dCP_dpresweep_fd[i] = (CPd - CP) / delta np.testing.assert_allclose(dCT_dpresweep_fd, dCT_dpresweep, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dCQ_dpresweep_fd, dCQ_dpresweep, rtol=3e-4, atol=1e-8) np.testing.assert_allclose(dCP_dpresweep_fd, dCP_dpresweep, rtol=3e-4, atol=1e-8) def test_dprecurveTip1(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) pct = float(precurveTip) delta = 1e-6*pct pct += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=precurve, precurveTip=pct) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dprecurveTip_fd = (Npd - Np) / delta dTp_dprecurveTip_fd = (Tpd - Tp) / delta np.testing.assert_allclose(dNp_dprecurveTip_fd, 0.0, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dTp_dprecurveTip_fd, 0.0, rtol=1e-4, atol=1e-8) def test_dprecurveTip2(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) P, T, Q, dP_ds, dT_ds, dQ_ds, dP_dv, dT_dv, dQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dprecurveTip = dT_ds[0, 5] dQ_dprecurveTip = dQ_ds[0, 5] dP_dprecurveTip = dP_ds[0, 5] pct = float(precurveTip) delta = 1e-6*pct pct += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=precurve, precurveTip=pct) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dprecurveTip_fd = (Td - T) / delta dQ_dprecurveTip_fd = (Qd - Q) / delta dP_dprecurveTip_fd = (Pd - P) / delta np.testing.assert_allclose(dT_dprecurveTip_fd, dT_dprecurveTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dQ_dprecurveTip_fd, dQ_dprecurveTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dP_dprecurveTip_fd, dP_dprecurveTip, rtol=1e-4, atol=1e-8) def test_dprecurveTip3(self): precurve = np.linspace(1, 10, self.n) precurveTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, precurve=precurve, precurveTip=precurveTip) CP, CT, CQ, dCP_ds, dCT_ds, dCQ_ds, dCP_dv, dCT_dv, dCQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dprecurveTip = dCT_ds[0, 5] dCQ_dprecurveTip = dCQ_ds[0, 5] dCP_dprecurveTip = dCP_ds[0, 5] pct = float(precurveTip) delta = 1e-6*pct pct += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, precurve=precurve, precurveTip=pct) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dprecurveTip_fd = (CTd - CT) / delta dCQ_dprecurveTip_fd = (CQd - CQ) / delta dCP_dprecurveTip_fd = (CPd - CP) / delta np.testing.assert_allclose(dCT_dprecurveTip_fd, dCT_dprecurveTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dCQ_dprecurveTip_fd, dCQ_dprecurveTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dCP_dprecurveTip_fd, dCP_dprecurveTip, rtol=1e-4, atol=1e-8) def test_dpresweepTip1(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) Np, Tp, dNp_dX, dTp_dX, dNp_dpresweep, dTp_dpresweep = \ rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) pst = float(presweepTip) delta = 1e-6*pst pst += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=presweep, presweepTip=pst) Npd, Tpd = rotor.distributedAeroLoads(self.Uinf, self.Omega, self.pitch, self.azimuth) dNp_dpresweepTip_fd = (Npd - Np) / delta dTp_dpresweepTip_fd = (Tpd - Tp) / delta np.testing.assert_allclose(dNp_dpresweepTip_fd, 0.0, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dTp_dpresweepTip_fd, 0.0, rtol=1e-4, atol=1e-8) def test_dpresweepTip2(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) P, T, Q, dP_ds, dT_ds, dQ_ds, dP_dv, dT_dv, dQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dpresweepTip = dT_ds[0, 6] dQ_dpresweepTip = dQ_ds[0, 6] dP_dpresweepTip = dP_ds[0, 6] pst = float(presweepTip) delta = 1e-6*pst pst += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=presweep, presweepTip=pst) Pd, Td, Qd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=False) dT_dpresweepTip_fd = (Td - T) / delta dQ_dpresweepTip_fd = (Qd - Q) / delta dP_dpresweepTip_fd = (Pd - P) / delta np.testing.assert_allclose(dT_dpresweepTip_fd, dT_dpresweepTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dQ_dpresweepTip_fd, dQ_dpresweepTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dP_dpresweepTip_fd, dP_dpresweepTip, rtol=1e-4, atol=1e-8) def test_dpresweepTip3(self): presweep = np.linspace(1, 10, self.n) presweepTip = 10.1 precone = 0.0 rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=True, presweep=presweep, presweepTip=presweepTip) CP, CT, CQ, dCP_ds, dCT_ds, dCQ_ds, dCP_dv, dCT_dv, dCQ_dv = \ rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dpresweepTip = dCT_ds[0, 6] dCQ_dpresweepTip = dCQ_ds[0, 6] dCP_dpresweepTip = dCP_ds[0, 6] pst = float(presweepTip) delta = 1e-6*pst pst += delta rotor = CCBlade(self.r, self.chord, self.theta, self.af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, derivatives=False, presweep=presweep, presweepTip=pst) CPd, CTd, CQd = rotor.evaluate([self.Uinf], [self.Omega], [self.pitch], coefficient=True) dCT_dpresweepTip_fd = (CTd - CT) / delta dCQ_dpresweepTip_fd = (CQd - CQ) / delta dCP_dpresweepTip_fd = (CPd - CP) / delta np.testing.assert_allclose(dCT_dpresweepTip_fd, dCT_dpresweepTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dCQ_dpresweepTip_fd, dCQ_dpresweepTip, rtol=1e-4, atol=1e-8) np.testing.assert_allclose(dCP_dpresweepTip_fd, dCP_dpresweepTip, rtol=1e-4, atol=1e-8)
class RegulatedPowerCurve(Component): # Implicit COMPONENT def __init__(self, naero, n_pc, n_pc_spline, regulation_reg_II5 = True, regulation_reg_III = False): super(RegulatedPowerCurve, self).__init__() # parameters self.add_param('control_Vin', val=0.0, units='m/s', desc='cut-in wind speed') self.add_param('control_Vout', val=0.0, units='m/s', desc='cut-out wind speed') self.add_param('control_ratedPower', val=0.0, units='W', desc='electrical rated power') self.add_param('control_minOmega', val=0.0, units='rpm', desc='minimum allowed rotor rotation speed') self.add_param('control_maxOmega', val=0.0, units='rpm', desc='maximum allowed rotor rotation speed') self.add_param('control_maxTS', val=0.0, units='m/s', desc='maximum allowed blade tip speed') self.add_param('control_tsr', val=0.0, desc='tip-speed ratio in Region 2 (should be optimized externally)') self.add_param('control_pitch', val=0.0, units='deg', desc='pitch angle in region 2 (and region 3 for fixed pitch machines)') self.add_param('drivetrainType', val=DRIVETRAIN_TYPE['GEARED'], pass_by_obj=True) self.add_param('drivetrainEff', val=0.0, desc='overwrite drivetrain model with a given efficiency, used for FAST analysis') self.add_param('r', val=np.zeros(naero), units='m', desc='radial locations where blade is defined (should be increasing and not go all the way to hub or tip)') self.add_param('chord', val=np.zeros(naero), units='m', desc='chord length at each section') self.add_param('theta', val=np.zeros(naero), units='deg', desc='twist angle at each section (positive decreases angle of attack)') self.add_param('Rhub', val=0.0, units='m', desc='hub radius') self.add_param('Rtip', val=0.0, units='m', desc='tip radius') self.add_param('hubHt', val=0.0, units='m', desc='hub height') self.add_param('precone', val=0.0, units='deg', desc='precone angle', ) self.add_param('tilt', val=0.0, units='deg', desc='shaft tilt', ) self.add_param('yaw', val=0.0, units='deg', desc='yaw error', ) self.add_param('precurve', val=np.zeros(naero), units='m', desc='precurve at each section') self.add_param('precurveTip', val=0.0, units='m', desc='precurve at tip') self.add_param('airfoils', val=[0]*naero, desc='CCAirfoil instances', pass_by_obj=True) self.add_param('B', val=0, desc='number of blades', pass_by_obj=True) self.add_param('rho', val=0.0, units='kg/m**3', desc='density of air') self.add_param('mu', val=0.0, units='kg/(m*s)', desc='dynamic viscosity of air') self.add_param('shearExp', val=0.0, desc='shear exponent') self.add_param('nSector', val=4, desc='number of sectors to divide rotor face into in computing thrust and power', pass_by_obj=True) self.add_param('tiploss', val=True, desc='include Prandtl tip loss model', pass_by_obj=True) self.add_param('hubloss', val=True, desc='include Prandtl hub loss model', pass_by_obj=True) self.add_param('wakerotation', val=True, desc='include effect of wake rotation (i.e., tangential induction factor is nonzero)', pass_by_obj=True) self.add_param('usecd', val=True, desc='use drag coefficient in computing induction factors', pass_by_obj=True) # outputs self.add_output('V', val=np.zeros(n_pc), units='m/s', desc='wind vector') self.add_output('Omega', val=np.zeros(n_pc), units='rpm', desc='rotor rotational speed') self.add_output('pitch', val=np.zeros(n_pc), units='deg', desc='rotor pitch schedule') self.add_output('P', val=np.zeros(n_pc), units='W', desc='rotor electrical power') self.add_output('T', val=np.zeros(n_pc), units='N', desc='rotor aerodynamic thrust') self.add_output('Q', val=np.zeros(n_pc), units='N*m', desc='rotor aerodynamic torque') self.add_output('M', val=np.zeros(n_pc), units='N*m', desc='blade root moment') self.add_output('Cp', val=np.zeros(n_pc), desc='rotor electrical power coefficient') self.add_output('V_spline', val=np.zeros(n_pc_spline), units='m/s', desc='wind vector') self.add_output('P_spline', val=np.zeros(n_pc_spline), units='W', desc='rotor electrical power') self.add_output('V_R25', val=0.0, units='m/s', desc='region 2.5 transition wind speed') self.add_output('rated_V', val=0.0, units='m/s', desc='rated wind speed') self.add_output('rated_Omega', val=0.0, units='rpm', desc='rotor rotation speed at rated') self.add_output('rated_pitch', val=0.0, units='deg', desc='pitch setting at rated') self.add_output('rated_T', val=0.0, units='N', desc='rotor aerodynamic thrust at rated') self.add_output('rated_Q', val=0.0, units='N*m', desc='rotor aerodynamic torque at rated') self.add_output('ax_induct_cutin', val=np.zeros(naero), desc='rotor axial induction at cut-in wind speed along blade span') self.add_output('tang_induct_cutin', val=np.zeros(naero), desc='rotor tangential induction at cut-in wind speed along blade span') self.add_output('aoa_cutin', val=np.zeros(naero), desc='angle of attack distribution along blade span at cut-in wind speed') self.naero = naero self.n_pc = n_pc self.n_pc_spline = n_pc_spline self.lock_pitchII = False self.regulation_reg_II5 = regulation_reg_II5 self.regulation_reg_III = regulation_reg_III self.deriv_options['form'] = 'central' self.deriv_options['step_calc'] = 'relative' def solve_nonlinear(self, params, unknowns, resids): self.ccblade = CCBlade(params['r'], params['chord'], params['theta'], params['airfoils'], params['Rhub'], params['Rtip'], params['B'], params['rho'], params['mu'], params['precone'], params['tilt'], params['yaw'], params['shearExp'], params['hubHt'], params['nSector']) Uhub = np.linspace(params['control_Vin'],params['control_Vout'], self.n_pc) P_aero = np.zeros_like(Uhub) Cp_aero = np.zeros_like(Uhub) P = np.zeros_like(Uhub) Cp = np.zeros_like(Uhub) T = np.zeros_like(Uhub) Q = np.zeros_like(Uhub) M = np.zeros_like(Uhub) Omega = np.zeros_like(Uhub) pitch = np.zeros_like(Uhub) + params['control_pitch'] Omega_max = min([params['control_maxTS'] / params['Rtip'], params['control_maxOmega']*np.pi/30.]) # Region II for i in range(len(Uhub)): Omega[i] = Uhub[i] * params['control_tsr'] / params['Rtip'] P_aero, T, Q, M, Cp_aero, _, _, _ = self.ccblade.evaluate(Uhub, Omega * 30. / np.pi, pitch, coefficients=True) P, eff = CSMDrivetrain(P_aero, params['control_ratedPower'], params['drivetrainType']) Cp = Cp_aero*eff # search for Region 2.5 bounds for i in range(len(Uhub)): if Omega[i] > Omega_max and P[i] < params['control_ratedPower']: Omega[i] = Omega_max Uhub[i] = Omega[i] * params['Rtip'] / params['control_tsr'] P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch[i]], coefficients=True) P[i], eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff regionIIhalf = True i_IIhalf_start = i unknowns['V_R25'] = Uhub[i] break if P[i] > params['control_ratedPower']: regionIIhalf = False break def maxPregionIIhalf(pitch, Uhub, Omega): Uhub_i = Uhub Omega_i = Omega pitch = pitch P, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) return -P # Solve for regoin 2.5 pitch options = {} options['disp'] = False options['xatol'] = 1.e-2 if regionIIhalf == True: for i in range(i_IIhalf_start + 1, len(Uhub)): Omega[i] = Omega_max pitch0 = pitch[i-1] bnds = [pitch0 - 10., pitch0 + 10.] pitch_regionIIhalf = minimize_scalar(lambda x: maxPregionIIhalf(x, Uhub[i], Omega[i]), bounds=bnds, method='bounded', options=options)['x'] pitch[i] = pitch_regionIIhalf P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch[i]], coefficients=True) P[i], eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff if P[i] > params['control_ratedPower']: break def constantPregionIII(pitch, Uhub, Omega): Uhub_i = Uhub Omega_i = Omega pitch = pitch P_aero, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) P, eff = CSMDrivetrain(P_aero, params['control_ratedPower'], params['drivetrainType']) return abs(P - params['control_ratedPower']) if regionIIhalf == True: # Rated conditions def min_Uhub_rated_II12(min_params): return min_params[1] def get_Uhub_rated_II12(min_params): Uhub_i = min_params[1] Omega_i = Omega_max pitch = min_params[0] P_aero_i, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) P_i,eff = CSMDrivetrain(P_aero_i, params['control_ratedPower'], params['drivetrainType']) return abs(P_i - params['control_ratedPower']) x0 = [pitch[i] + 2. , Uhub[i]] bnds = [(pitch0, pitch0 + 10.),(Uhub[i-1],Uhub[i+1])] const = {} const['type'] = 'eq' const['fun'] = get_Uhub_rated_II12 params_rated = minimize(min_Uhub_rated_II12, x0, method='SLSQP', tol = 1.e-2, bounds=bnds, constraints=const) U_rated = params_rated.x[1] if not np.isnan(U_rated): Uhub[i] = U_rated pitch[i] = params_rated.x[0] else: print('Regulation trajectory is struggling to find a solution for rated wind speed. Check rotor_aeropower.py') Omega[i] = Omega_max P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch0], coefficients=True) P_i, eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff P[i] = params['control_ratedPower'] else: # Rated conditions def get_Uhub_rated_noII12(pitch, Uhub): Uhub_i = Uhub Omega_i = min([Uhub_i * params['control_tsr'] / params['Rtip'], Omega_max]) pitch_i = pitch P_aero_i, _, _, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch_i], coefficients=False) P_i, eff = CSMDrivetrain(P_aero_i, params['control_ratedPower'], params['drivetrainType']) return abs(P_i - params['control_ratedPower']) bnds = [Uhub[i-1], Uhub[i+1]] U_rated = minimize_scalar(lambda x: get_Uhub_rated_noII12(pitch[i], x), bounds=bnds, method='bounded', options=options)['x'] if not np.isnan(U_rated): Uhub[i] = U_rated else: print('Regulation trajectory is struggling to find a solution for rated wind speed. Check rotor_aeropower.py') Omega[i] = min([Uhub[i] * params['control_tsr'] / params['Rtip'], Omega_max]) pitch0 = pitch[i] P_aero[i], T[i], Q[i], M[i], Cp_aero[i], _, _, _ = self.ccblade.evaluate([Uhub[i]], [Omega[i] * 30. / np.pi], [pitch0], coefficients=True) P[i], eff = CSMDrivetrain(P_aero[i], params['control_ratedPower'], params['drivetrainType']) Cp[i] = Cp_aero[i]*eff for j in range(i + 1,len(Uhub)): Omega[j] = Omega[i] if self.regulation_reg_III == True: pitch0 = pitch[j-1] bnds = [pitch0, pitch0 + 15.] pitch_regionIII = minimize_scalar(lambda x: constantPregionIII(x, Uhub[j], Omega[j]), bounds=bnds, method='bounded', options=options)['x'] pitch[j] = pitch_regionIII P_aero[j], T[j], Q[j], M[j], Cp_aero[j], _, _, _ = self.ccblade.evaluate([Uhub[j]], [Omega[j] * 30. / np.pi], [pitch[j]], coefficients=True) P[j], eff = CSMDrivetrain(P_aero[j], params['control_ratedPower'], params['drivetrainType']) Cp[j] = Cp_aero[j]*eff if abs(P[j] - params['control_ratedPower']) > 1e+4: print('The pitch in region III is not being determined correctly at wind speed ' + str(Uhub[j]) + ' m/s') P[j] = params['control_ratedPower'] T[j] = T[j-1] Q[j] = P[j] / Omega[j] M[j] = M[j-1] pitch[j] = pitch[j-1] Cp[j] = P[j] / (0.5 * params['rho'] * np.pi * params['Rtip']**2 * Uhub[i]**3) P[j] = params['control_ratedPower'] else: P[j] = params['control_ratedPower'] T[j] = 0 Q[j] = Q[i] M[j] = 0 pitch[j] = 0 Cp[j] = P[j] / (0.5 * params['rho'] * np.pi * params['Rtip']**2 * Uhub[i]**3) unknowns['T'] = T unknowns['Q'] = Q unknowns['Omega'] = Omega * 30. / np.pi unknowns['P'] = P unknowns['Cp'] = Cp unknowns['V'] = Uhub unknowns['M'] = M unknowns['pitch'] = pitch self.ccblade.induction_inflow = True a_regII, ap_regII, alpha_regII,_, _ = self.ccblade.distributedAeroLoads(Uhub[0], Omega[0] * 30. / np.pi, pitch[0], 0.0) # Fit spline to powercurve for higher grid density spline = PchipInterpolator(Uhub, P) V_spline = np.linspace(params['control_Vin'],params['control_Vout'], num=self.n_pc_spline) P_spline = spline(V_spline) # outputs idx_rated = list(Uhub).index(U_rated) unknowns['rated_V'] = U_rated unknowns['rated_Omega'] = Omega[idx_rated] * 30. / np.pi unknowns['rated_pitch'] = pitch[idx_rated] unknowns['rated_T'] = T[idx_rated] unknowns['rated_Q'] = Q[idx_rated] unknowns['V_spline'] = V_spline unknowns['P_spline'] = P_spline unknowns['ax_induct_cutin'] = a_regII unknowns['tang_induct_cutin'] = ap_regII unknowns['aoa_cutin'] = alpha_regII
# set conditions Uinf = 10.0 tsr = 7.55 pitch = 0.0 Omega = Uinf*tsr/Rtip * 30.0/pi # convert to RPM azimuth = 0.0 # # evaluate distributed loads # Np, Tp = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) # 4 ---------- # 5 ---------- Np, Tp, dNp, dTp \ = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) n = len(r) # n x n (diagonal) dNp_dr = dNp['dr'] dNp_dchord = dNp['dchord'] dNp_dtheta = dNp['dtheta'] dNp_dpresweep = dNp['dpresweep'] # n x n (tridiagonal) dNp_dprecurve = dNp['dprecurve'] # n x 1 dNp_dRhub = dNp['dRhub'] dNp_dRtip = dNp['dRtip']
# set conditions Uinf = 10.0 tsr = 7.55 pitch = 0.0 Omega = Uinf * tsr / Rtip * 30.0 / pi # convert to RPM azimuth = 0.0 # # evaluate distributed loads # Np, Tp = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) # 4 ---------- # 5 ---------- Np, Tp, dNp, dTp \ = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) n = len(r) # n x n (diagonal) dNp_dr = dNp['dr'] dNp_dchord = dNp['dchord'] dNp_dtheta = dNp['dtheta'] dNp_dpresweep = dNp['dpresweep'] # n x n (tridiagonal) dNp_dprecurve = dNp['dprecurve'] # n x 1 dNp_dRhub = dNp['dRhub'] dNp_dRtip = dNp['dRtip']
class CCBlade(AeroBase): """blade element momentum code""" # (potential) variables r = Array(iotype='in', units='m', desc='radial locations where blade is defined (should be increasing and not go all the way to hub or tip)') chord = Array(iotype='in', units='m', desc='chord length at each section') theta = Array(iotype='in', units='deg', desc='twist angle at each section (positive decreases angle of attack)') Rhub = Float(iotype='in', units='m', desc='hub radius') Rtip = Float(iotype='in', units='m', desc='tip radius') hubHt = Float(iotype='in', units='m', desc='hub height') precone = Float(0.0, iotype='in', desc='precone angle', units='deg') tilt = Float(0.0, iotype='in', desc='shaft tilt', units='deg') yaw = Float(0.0, iotype='in', desc='yaw error', units='deg') # TODO: I've not hooked up the gradients for these ones yet. precurve = Array(iotype='in', units='m', desc='precurve at each section') precurveTip = Float(0.0, iotype='in', units='m', desc='precurve at tip') # parameters airfoil_files = List(Str, iotype='in', desc='names of airfoil file') coordinate_files = List(Str, iotype='in', desc='names of airfoil file') B = Int(3, iotype='in', desc='number of blades') rho = Float(1.225, iotype='in', units='kg/m**3', desc='density of air') mu = Float(1.81206e-5, iotype='in', units='kg/(m*s)', desc='dynamic viscosity of air') shearExp = Float(0.2, iotype='in', desc='shear exponent') nSector = Int(4, iotype='in', desc='number of sectors to divide rotor face into in computing thrust and power') tiploss = Bool(True, iotype='in', desc='include Prandtl tip loss model') hubloss = Bool(True, iotype='in', desc='include Prandtl hub loss model') wakerotation = Bool(True, iotype='in', desc='include effect of wake rotation (i.e., tangential induction factor is nonzero)') usecd = Bool(True, iotype='in', desc='use drag coefficient in computing induction factors') af = Array(iotype='in', desc='CCBlade objects') missing_deriv_policy = 'assume_zero' def execute(self): if len(self.precurve) == 0: self.precurve = np.zeros_like(self.r) af = self.af self.ccblade = CCBlade_PY(self.r, self.chord, self.theta, af, self.Rhub, self.Rtip, self.B, self.rho, self.mu, self.precone, self.tilt, self.yaw, self.shearExp, self.hubHt, self.nSector, self.precurve, self.precurveTip, tiploss=self.tiploss, hubloss=self.hubloss, wakerotation=self.wakerotation, usecd=self.usecd, derivatives=True) if self.run_case == 'power': # power, thrust, torque self.P, self.T, self.Q, self.dP, self.dT, self.dQ \ = self.ccblade.evaluate(self.Uhub, self.Omega, self.pitch, coefficient=False) elif self.run_case == 'loads': # distributed loads Np, Tp, self.dNp, self.dTp \ = self.ccblade.distributedAeroLoads(self.V_load, self.Omega_load, self.pitch_load, self.azimuth_load) # concatenate loads at root/tip self.loads.r = np.concatenate([[self.Rhub], self.r, [self.Rtip]]) Np = np.concatenate([[0.0], Np, [0.0]]) Tp = np.concatenate([[0.0], Tp, [0.0]]) # conform to blade-aligned coordinate system self.loads.Px = Np self.loads.Py = -Tp self.loads.Pz = 0*Np # return other outputs needed self.loads.V = self.V_load self.loads.Omega = self.Omega_load self.loads.pitch = self.pitch_load self.loads.azimuth = self.azimuth_load def list_deriv_vars(self): if self.run_case == 'power': inputs = ('precone', 'tilt', 'hubHt', 'Rhub', 'Rtip', 'yaw', 'Uhub', 'Omega', 'pitch', 'r', 'chord', 'theta', 'precurve', 'precurveTip') outputs = ('P', 'T', 'Q') elif self.run_case == 'loads': inputs = ('r', 'chord', 'theta', 'Rhub', 'Rtip', 'hubHt', 'precone', 'tilt', 'yaw', 'V_load', 'Omega_load', 'pitch_load', 'azimuth_load', 'precurve') outputs = ('loads.r', 'loads.Px', 'loads.Py', 'loads.Pz', 'loads.V', 'loads.Omega', 'loads.pitch', 'loads.azimuth') return inputs, outputs def provideJ(self): if self.run_case == 'power': dP = self.dP dT = self.dT dQ = self.dQ jP = hstack([dP['dprecone'], dP['dtilt'], dP['dhubHt'], dP['dRhub'], dP['dRtip'], dP['dyaw'], dP['dUinf'], dP['dOmega'], dP['dpitch'], dP['dr'], dP['dchord'], dP['dtheta'], dP['dprecurve'], dP['dprecurveTip']]) jT = hstack([dT['dprecone'], dT['dtilt'], dT['dhubHt'], dT['dRhub'], dT['dRtip'], dT['dyaw'], dT['dUinf'], dT['dOmega'], dT['dpitch'], dT['dr'], dT['dchord'], dT['dtheta'], dT['dprecurve'], dT['dprecurveTip']]) jQ = hstack([dQ['dprecone'], dQ['dtilt'], dQ['dhubHt'], dQ['dRhub'], dQ['dRtip'], dQ['dyaw'], dQ['dUinf'], dQ['dOmega'], dQ['dpitch'], dQ['dr'], dQ['dchord'], dQ['dtheta'], dQ['dprecurve'], dQ['dprecurveTip']]) J = vstack([jP, jT, jQ]) elif self.run_case == 'loads': dNp = self.dNp dTp = self.dTp n = len(self.r) dr_dr = vstack([np.zeros(n), np.eye(n), np.zeros(n)]) dr_dRhub = np.zeros(n+2) dr_dRtip = np.zeros(n+2) dr_dRhub[0] = 1.0 dr_dRtip[-1] = 1.0 dr = hstack([dr_dr, np.zeros((n+2, 2*n)), dr_dRhub, dr_dRtip, np.zeros((n+2, 8+n))]) jNp = hstack([dNp['dr'], dNp['dchord'], dNp['dtheta'], dNp['dRhub'], dNp['dRtip'], dNp['dhubHt'], dNp['dprecone'], dNp['dtilt'], dNp['dyaw'], dNp['dUinf'], dNp['dOmega'], dNp['dpitch'], dNp['dazimuth'], dNp['dprecurve']]) jTp = hstack([dTp['dr'], dTp['dchord'], dTp['dtheta'], dTp['dRhub'], dTp['dRtip'], dTp['dhubHt'], dTp['dprecone'], dTp['dtilt'], dTp['dyaw'], dTp['dUinf'], dTp['dOmega'], dTp['dpitch'], dTp['dazimuth'], dTp['dprecurve']]) dPx = vstack([np.zeros(4*n+10), jNp, np.zeros(4*n+10)]) dPy = vstack([np.zeros(4*n+10), -jTp, np.zeros(4*n+10)]) dPz = np.zeros((n+2, 4*n+10)) dV = np.zeros(4*n+10) dV[3*n+6] = 1.0 dOmega = np.zeros(4*n+10) dOmega[3*n+7] = 1.0 dpitch = np.zeros(4*n+10) dpitch[3*n+8] = 1.0 dazimuth = np.zeros(4*n+10) dazimuth[3*n+9] = 1.0 J = vstack([dr, dPx, dPy, dPz, dV, dOmega, dpitch, dazimuth]) return J
rotor = CCBlade(r, chord, theta, af, Rhub, Rtip, B, rho, mu, precone, tilt, yaw, shearExp, hubHt, nSector) # 3 ---------- # 4 ---------- # set conditions Uinf = 10.0 tsr = 7.55 pitch = 0.0 Omega = Uinf*tsr/Rtip * 30.0/pi # convert to RPM azimuth = 0.0 # evaluate distributed loads Np, Tp = rotor.distributedAeroLoads(Uinf, Omega, pitch, azimuth) # 4 ---------- # 5 ---------- # plot rstar = (r - Rhub) / (Rtip - Rhub) # append zero at root and tip rstar = np.concatenate([[0.0], rstar, [1.0]]) Np = np.concatenate([[0.0], Np, [0.0]]) Tp = np.concatenate([[0.0], Tp, [0.0]]) plt.plot(rstar, Tp/1e3, label='lead-lag') plt.plot(rstar, Np/1e3, label='flapwise')