# 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']
af[i] = CCAirfoil(np.rad2deg(aoa), [1e+6], cl[i, :, 0, 0], cd[i, :, 0, 0], cm[i, :, 0, 0]) ########## Run CCBlade ########## get_cp_cm = CCBlade(r, chord, twist, af, hub_r, Rtip, B, rho, mu, cone, tilt, 0., shearExp, hub_height, nSector, presweep, precurve[-1], presweep, presweep[-1], tiploss, hubloss, wakerotation, usecd) # get_cp_cm.induction = True Omega = Uhub * tsr / Rtip * 30.0 / np.pi # Rotor speed myout, derivs = get_cp_cm.evaluate([Uhub], [Omega], [pitch], coefficients=True) P, T, Q, M, CP, CT, CQ, CM = [ myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM'] ] get_cp_cm.induction_inflow = True loads, deriv = get_cp_cm.distributedAeroLoads([Uhub], Omega, pitch, 0.) ########## Post Process Output ########## # Compute forces in the airfoil coordinate system, pag 21 of https://www.nrel.gov/docs/fy13osti/58819.pdf P_b = DirectionVector(loads['Np'], -loads['Tp'], 0) P_af = P_b.bladeToAirfoil(twist) # Compute lift and drag forces F = P_b.bladeToAirfoil(twist + loads['alpha'] + pitch) # Print output .dat file with the quantities distributed along span np.savetxt( os.path.join(output_folder, 'distributed_quantities.dat'), np.array([ s, r, loads['alpha'], loads['a'], loads['ap'], loads['Cl'], loads['Cd'], F.x, F.y, loads['Np'], -loads['Tp'], P_af.x, P_af.y ]).T, header=
class ComputePowerCurve(ExplicitComponent): """ Iteratively call CCBlade to compute the power curve. """ def initialize(self): self.options.declare('modeling_options') def setup(self): modeling_options = self.options['modeling_options'] self.n_span = n_span = modeling_options['blade']['n_span'] # self.n_af = n_af = af_init_options['n_af'] # Number of airfoils self.n_aoa = n_aoa = modeling_options['airfoils'][ 'n_aoa'] # Number of angle of attacks self.n_Re = n_Re = modeling_options['airfoils'][ 'n_Re'] # Number of Reynolds, so far hard set at 1 self.n_tab = n_tab = modeling_options['airfoils'][ 'n_tab'] # Number of tabulated data. For distributed aerodynamic control this could be > 1 # self.n_xy = n_xy = af_init_options['n_xy'] # Number of coordinate points to describe the airfoil geometry self.regulation_reg_III = modeling_options['servose'][ 'regulation_reg_III'] # n_span = self.n_span = self.options['n_span'] self.n_pc = modeling_options['servose']['n_pc'] self.n_pc_spline = modeling_options['servose']['n_pc_spline'] # n_aoa = self.options['n_aoa'] # n_re = self.options['n_re'] # parameters self.add_input('v_min', val=0.0, units='m/s', desc='cut-in wind speed') self.add_input('v_max', val=0.0, units='m/s', desc='cut-out wind speed') self.add_input('rated_power', val=0.0, units='W', desc='electrical rated power') self.add_input('omega_min', val=0.0, units='rpm', desc='minimum allowed rotor rotation speed') self.add_input('omega_max', val=0.0, units='rpm', desc='maximum allowed rotor rotation speed') self.add_input('control_maxTS', val=0.0, units='m/s', desc='maximum allowed blade tip speed') self.add_input( 'tsr_operational', val=0.0, desc='tip-speed ratio in Region 2 (should be optimized externally)' ) self.add_input( 'control_pitch', val=0.0, units='deg', desc= 'pitch angle in region 2 (and region 3 for fixed pitch machines)') self.add_discrete_input('drivetrainType', val='GEARED') self.add_input('gearbox_efficiency', val=0.0, desc='Gearbox efficiency') self.add_input('generator_efficiency', val=0.0, desc='Generator efficiency') self.add_input( 'r', val=np.zeros(n_span), units='m', desc= 'radial locations where blade is defined (should be increasing and not go all the way to hub or tip)' ) self.add_input('chord', val=np.zeros(n_span), units='m', desc='chord length at each section') self.add_input( 'theta', val=np.zeros(n_span), units='deg', desc= 'twist angle at each section (positive decreases angle of attack)') self.add_input('Rhub', val=0.0, units='m', desc='hub radius') self.add_input('Rtip', val=0.0, units='m', desc='tip radius') self.add_input('hub_height', val=0.0, units='m', desc='hub height') self.add_input( 'precone', val=0.0, units='deg', desc='precone angle', ) self.add_input( 'tilt', val=0.0, units='deg', desc='shaft tilt', ) self.add_input( 'yaw', val=0.0, units='deg', desc='yaw error', ) self.add_input('precurve', val=np.zeros(n_span), units='m', desc='precurve at each section') self.add_input('precurveTip', val=0.0, units='m', desc='precurve at tip') self.add_input('presweep', val=np.zeros(n_span), units='m', desc='presweep at each section') self.add_input('presweepTip', val=0.0, units='m', desc='presweep at tip') # self.add_discrete_input('airfoils', val=[0]*n_span, desc='CCAirfoil instances') self.add_input('airfoils_cl', val=np.zeros((n_span, n_aoa, n_Re, n_tab)), desc='lift coefficients, spanwise') self.add_input('airfoils_cd', val=np.zeros((n_span, n_aoa, n_Re, n_tab)), desc='drag coefficients, spanwise') self.add_input('airfoils_cm', val=np.zeros((n_span, n_aoa, n_Re, n_tab)), desc='moment coefficients, spanwise') self.add_input('airfoils_aoa', val=np.zeros((n_aoa)), units='deg', desc='angle of attack grid for polars') self.add_input('airfoils_Re', val=np.zeros((n_Re)), desc='Reynolds numbers of polars') self.add_discrete_input('nBlades', val=0, desc='number of blades') self.add_input('rho', val=1.225, units='kg/m**3', desc='density of air') self.add_input('mu', val=1.81e-5, units='kg/(m*s)', desc='dynamic viscosity of air') self.add_input('shearExp', val=0.0, desc='shear exponent') self.add_discrete_input( 'nSector', val=4, desc= 'number of sectors to divide rotor face into in computing thrust and power' ) self.add_discrete_input('tiploss', val=True, desc='include Prandtl tip loss model') self.add_discrete_input('hubloss', val=True, desc='include Prandtl hub loss model') self.add_discrete_input( 'wakerotation', val=True, desc= 'include effect of wake rotation (i.e., tangential induction factor is nonzero)' ) self.add_discrete_input( 'usecd', val=True, desc='use drag coefficient in computing induction factors') # outputs self.add_output('V', val=np.zeros(self.n_pc), units='m/s', desc='wind vector') self.add_output('Omega', val=np.zeros(self.n_pc), units='rpm', desc='rotor rotational speed') self.add_output('pitch', val=np.zeros(self.n_pc), units='deg', desc='rotor pitch schedule') self.add_output('P', val=np.zeros(self.n_pc), units='W', desc='rotor electrical power') self.add_output('T', val=np.zeros(self.n_pc), units='N', desc='rotor aerodynamic thrust') self.add_output('Q', val=np.zeros(self.n_pc), units='N*m', desc='rotor aerodynamic torque') self.add_output('M', val=np.zeros(self.n_pc), units='N*m', desc='blade root moment') self.add_output('Cp', val=np.zeros(self.n_pc), desc='rotor electrical power coefficient') self.add_output('Cp_aero', val=np.zeros(self.n_pc), desc='rotor aerodynamic power coefficient') self.add_output('Ct_aero', val=np.zeros(self.n_pc), desc='rotor aerodynamic thrust coefficient') self.add_output('Cq_aero', val=np.zeros(self.n_pc), desc='rotor aerodynamic torque coefficient') self.add_output('Cm_aero', val=np.zeros(self.n_pc), desc='rotor aerodynamic moment coefficient') 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_regII', val=np.zeros(n_span), desc='rotor axial induction at cut-in wind speed along blade span') self.add_output( 'tang_induct_regII', val=np.zeros(n_span), desc= 'rotor tangential induction at cut-in wind speed along blade span') self.add_output( 'aoa_regII', val=np.zeros(n_span), units='deg', desc= 'angle of attack distribution along blade span at cut-in wind speed' ) self.add_output('Cp_regII', val=0.0, desc='power coefficient at cut-in wind speed') self.add_output( 'cl_regII', val=np.zeros(n_span), desc= 'lift coefficient distribution along blade span at cut-in wind speed' ) self.add_output( 'cd_regII', val=np.zeros(n_span), desc= 'drag coefficient distribution along blade span at cut-in wind speed' ) # self.declare_partials('*', '*', method='fd', form='central', step=1e-6) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Create Airfoil class instances af = [None] * self.n_span for i in range(self.n_span): if self.n_tab > 1: ref_tab = int(np.floor(self.n_tab / 2)) af[i] = CCAirfoil(inputs['airfoils_aoa'], inputs['airfoils_Re'], inputs['airfoils_cl'][i, :, :, ref_tab], inputs['airfoils_cd'][i, :, :, ref_tab], inputs['airfoils_cm'][i, :, :, ref_tab]) else: af[i] = CCAirfoil(inputs['airfoils_aoa'], inputs['airfoils_Re'], inputs['airfoils_cl'][i, :, :, 0], inputs['airfoils_cd'][i, :, :, 0], inputs['airfoils_cm'][i, :, :, 0]) self.ccblade = CCBlade( inputs['r'], inputs['chord'], inputs['theta'], af, inputs['Rhub'], inputs['Rtip'], discrete_inputs['nBlades'], inputs['rho'], inputs['mu'], inputs['precone'], inputs['tilt'], inputs['yaw'], inputs['shearExp'], inputs['hub_height'], discrete_inputs['nSector'], inputs['precurve'], inputs['precurveTip'], inputs['presweep'], inputs['presweepTip'], discrete_inputs['tiploss'], discrete_inputs['hubloss'], discrete_inputs['wakerotation'], discrete_inputs['usecd']) # JPJ: what is this grid for? Seems to be a special distribution of velocities # for the hub grid0 = np.cumsum( np.abs( np.diff( np.cos(np.linspace(-np.pi / 4., np.pi / 2., self.n_pc + 1))))) grid1 = (grid0 - grid0[0]) / (grid0[-1] - grid0[0]) Uhub = grid1 * (inputs['v_max'] - inputs['v_min']) + inputs['v_min'] P_aero = np.zeros(Uhub.shape) Cp_aero = np.zeros(Uhub.shape) Ct_aero = np.zeros(Uhub.shape) Cq_aero = np.zeros(Uhub.shape) Cm_aero = np.zeros(Uhub.shape) P = np.zeros(Uhub.shape) Cp = np.zeros(Uhub.shape) T = np.zeros(Uhub.shape) Q = np.zeros(Uhub.shape) M = np.zeros(Uhub.shape) pitch = np.zeros(Uhub.shape) + inputs['control_pitch'] # Unpack variables P_rated = inputs['rated_power'] R_tip = inputs['Rtip'] tsr = inputs['tsr_operational'] driveType = discrete_inputs['drivetrainType'] driveEta = inputs['gearbox_efficiency'] * inputs['generator_efficiency'] # Set rotor speed based on TSR Omega_tsr = Uhub * tsr / R_tip # Determine maximum rotor speed (rad/s)- either by TS or by control input Omega_max = min([ inputs['control_maxTS'] / R_tip, inputs['omega_max'] * np.pi / 30. ]) # Apply maximum and minimum rotor speed limits Omega = np.maximum(np.minimum(Omega_tsr, Omega_max), inputs['omega_min'] * np.pi / 30.) Omega_rpm = Omega * 30. / np.pi # Set baseline power production myout, derivs = self.ccblade.evaluate(Uhub, Omega_rpm, pitch, coefficients=True) P_aero, T, Q, M, Cp_aero, Ct_aero, Cq_aero, Cm_aero = [ myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM'] ] P, eff = compute_P_and_eff(P_aero, P_rated, driveType, driveEta) Cp = Cp_aero * eff # Find Region 3 index region_bool = np.nonzero(P >= P_rated)[0] if len(region_bool) == 0: i_3 = self.n_pc region3 = False else: i_3 = region_bool[0] + 1 region3 = True # Guess at Region 2.5, but we will do a more rigorous search below if Omega_max < Omega_tsr[-1]: U_2p5 = np.interp(Omega[-1], Omega_tsr, Uhub) outputs['V_R25'] = U_2p5 else: U_2p5 = Uhub[-1] i_2p5 = np.nonzero(U_2p5 <= Uhub)[0][0] # Find rated index and guess at rated speed if P_aero[-1] > P_rated: U_rated = np.interp(P_rated, P_aero * eff, Uhub) else: U_rated = Uhub[-1] i_rated = np.nonzero(U_rated <= Uhub)[0][0] # Function to be used inside of power maximization until Region 3 def maximizePower(pitch, Uhub, Omega_rpm): myout, _ = self.ccblade.evaluate([Uhub], [Omega_rpm], [pitch], coefficients=False) return -myout['P'] # Maximize power until Region 3 region2p5 = False for i in range(i_3): # No need to optimize if already doing well if Omega[i] == Omega_tsr[i]: continue # Find pitch value that gives highest power rating pitch0 = pitch[i] if i == 0 else pitch[i - 1] bnds = [pitch0 - 10., pitch0 + 10.] pitch[i] = minimize_scalar( lambda x: maximizePower(x, Uhub[i], Omega_rpm[i]), bounds=bnds, method='bounded', options={ 'disp': False, 'xatol': 1e-2, 'maxiter': 40 })['x'] # Find associated power myout, _ = self.ccblade.evaluate([Uhub[i]], [Omega_rpm[i]], [pitch[i]], coefficients=True) P_aero[i], T[i], Q[i], M[i], Cp_aero[i], Ct_aero[i], Cq_aero[ i], Cm_aero[i] = [ myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM'] ] P[i], eff = compute_P_and_eff(P_aero[i], P_rated, driveType, driveEta) Cp[i] = Cp_aero[i] * eff # Note if we find Region 2.5 if ((not region2p5) and (Omega[i] == Omega_max) and (P[i] < P_rated)): region2p5 = True i_2p5 = i # Stop if we find Region 3 early if P[i] > P_rated: i_3 = i + 1 i_rated = i break # Solve for rated velocity # JPJ: why rename i_rated to i here? It removes clarity in the following 50 lines that we're looking at the rated properties i = i_rated if i < self.n_pc - 1: def const_Urated(x): pitch = x[0] Uhub_i = x[1] Omega_i = min([Uhub_i * tsr / R_tip, Omega_max]) myout, _ = self.ccblade.evaluate([Uhub_i], [Omega_i * 30. / np.pi], [pitch], coefficients=False) P_aero_i = myout['P'] P_i, eff = compute_P_and_eff(P_aero_i.flatten(), P_rated, driveType, driveEta) return (P_i - P_rated) if region2p5: # Have to search over both pitch and speed x0 = [0.0, Uhub[i]] bnds = [ np.sort([pitch[i - 1], pitch[i + 1]]), [Uhub[i - 1], Uhub[i + 1]] ] const = {} const['type'] = 'eq' const['fun'] = const_Urated params_rated = minimize(lambda x: x[1], x0, method='slsqp', bounds=bnds, constraints=const, tol=1e-3) if params_rated.success and not np.isnan(params_rated.x[1]): U_rated = params_rated.x[1] pitch[i] = params_rated.x[0] else: U_rated = U_rated # Use guessed value earlier pitch[i] = 0.0 else: # Just search over speed pitch[i] = 0.0 try: U_rated = brentq(lambda x: const_Urated([0.0, x]), Uhub[i - 1], Uhub[i + 1], xtol=1e-4, rtol=1e-5, maxiter=40, disp=False) except ValueError: U_rated = minimize_scalar( lambda x: np.abs(const_Urated([0.0, x])), bounds=[Uhub[i - 1], Uhub[i + 1]], method='bounded', options={ 'disp': False, 'xatol': 1e-3, 'maxiter': 40 })['x'] Omega_rated = min([U_rated * tsr / R_tip, Omega_max]) Omega[i:] = np.minimum( Omega[i:], Omega_rated) # Stay at this speed if hit rated too early Omega_rpm = Omega * 30. / np.pi myout, _ = self.ccblade.evaluate([U_rated], [Omega_rpm[i]], [pitch[i]], coefficients=True) P_aero[i], T[i], Q[i], M[i], Cp_aero[i], Ct_aero[i], Cq_aero[ i], Cm_aero[i] = [ myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM'] ] P[i], eff = compute_P_and_eff(P_aero[i], P_rated, driveType, driveEta) Cp[i] = Cp_aero[i] * eff P[i] = P_rated # Store rated speed in array Uhub[i_rated] = U_rated # Store outputs outputs['rated_V'] = np.float64(U_rated) outputs['rated_Omega'] = Omega_rpm[i] outputs['rated_pitch'] = pitch[i] outputs['rated_T'] = T[i] outputs['rated_Q'] = Q[i] # JPJ: this part can be converted into a BalanceComp with a solver. # This will be less expensive and allow us to get derivatives through the process. if region3: # Function to be used to stay at rated power in Region 3 def rated_power_dist(pitch, Uhub, Omega_rpm): myout, _ = self.ccblade.evaluate([Uhub], [Omega_rpm], [pitch], coefficients=False) P_aero = myout['P'] P, eff = compute_P_and_eff(P_aero, P_rated, driveType, driveEta) return (P - P_rated) # Solve for Region 3 pitch options = {'disp': False} if self.regulation_reg_III: for i in range(i_3, self.n_pc): pitch0 = pitch[i - 1] try: pitch[i] = brentq(lambda x: rated_power_dist( x, Uhub[i], Omega_rpm[i]), pitch0, pitch0 + 10., xtol=1e-4, rtol=1e-5, maxiter=40, disp=False) except ValueError: pitch[i] = minimize_scalar( lambda x: np.abs( rated_power_dist(x, Uhub[i], Omega_rpm[i])), bounds=[pitch0 - 5., pitch0 + 15.], method='bounded', options={ 'disp': False, 'xatol': 1e-3, 'maxiter': 40 })['x'] myout, _ = self.ccblade.evaluate([Uhub[i]], [Omega_rpm[i]], [pitch[i]], coefficients=True) P_aero[i], T[i], Q[i], M[i], Cp_aero[i], Ct_aero[ i], Cq_aero[i], Cm_aero[i] = [ myout[key] for key in ['P', 'T', 'Q', 'M', 'CP', 'CT', 'CQ', 'CM'] ] P[i], eff = compute_P_and_eff(P_aero[i], P_rated, driveType, driveEta) Cp[i] = Cp_aero[i] * eff #P[i] = P_rated else: P[i_3:] = P_rated T[i_3:] = 0 Q[i_3:] = P[i_3:] / Omega[i_3:] M[i_3:] = 0 pitch[i_3:] = 0 Cp[i_3:] = P[i_3:] / (0.5 * inputs['rho'] * np.pi * R_tip**2 * Uhub[i_3:]**3) Ct_aero[i_3:] = 0 Cq_aero[i_3:] = 0 Cm_aero[i_3:] = 0 outputs['T'] = T outputs['Q'] = Q outputs['Omega'] = Omega_rpm outputs['P'] = P outputs['Cp'] = Cp outputs['Cp_aero'] = Cp_aero outputs['Ct_aero'] = Ct_aero outputs['Cq_aero'] = Cq_aero outputs['Cm_aero'] = Cm_aero outputs['V'] = Uhub outputs['M'] = M outputs['pitch'] = pitch self.ccblade.induction_inflow = True tsr_vec = Omega_rpm / 30. * np.pi * R_tip / Uhub id_regII = np.argmin(abs(tsr_vec - inputs['tsr_operational'])) loads, derivs = self.ccblade.distributedAeroLoads( Uhub[id_regII], Omega_rpm[id_regII], pitch[id_regII], 0.0) # outputs outputs['ax_induct_regII'] = loads['a'] outputs['tang_induct_regII'] = loads['ap'] outputs['aoa_regII'] = loads['alpha'] outputs['cl_regII'] = loads['Cl'] outputs['cd_regII'] = loads['Cd'] outputs['Cp_regII'] = Cp_aero[id_regII]
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): r = inputs['r'] chord = inputs['chord'] theta = inputs['theta'] Rhub = inputs['Rhub'] Rtip = inputs['Rtip'] hub_height = inputs['hub_height'] precone = inputs['precone'] tilt = inputs['tilt'] yaw = inputs['yaw'] precurve = inputs['precurve'] precurveTip = inputs['precurveTip'] # airfoils = discrete_inputs['airfoils'] B = discrete_inputs['nBlades'] rho = inputs['rho'] mu = inputs['mu'] shearExp = inputs['shearExp'] nSector = discrete_inputs['nSector'] tiploss = discrete_inputs['tiploss'] hubloss = discrete_inputs['hubloss'] wakerotation = discrete_inputs['wakerotation'] usecd = discrete_inputs['usecd'] V_load = inputs['V_load'] Omega_load = inputs['Omega_load'] pitch_load = inputs['pitch_load'] azimuth_load = inputs['azimuth_load'] if len(precurve) == 0: precurve = np.zeros_like(r) # airfoil files af = [None] * self.n_span for i in range(self.n_span): af[i] = CCAirfoil(inputs['airfoils_aoa'], inputs['airfoils_Re'], inputs['airfoils_cl'][i, :, :, 0], inputs['airfoils_cd'][i, :, :, 0], inputs['airfoils_cm'][i, :, :, 0]) ccblade = CCBlade(r, chord, theta, af, Rhub, Rtip, B, rho, mu, precone, tilt, yaw, shearExp, hub_height, nSector, precurve, precurveTip, tiploss=tiploss, hubloss=hubloss, wakerotation=wakerotation, usecd=usecd, derivatives=True) # distributed loads loads, self.derivs = ccblade.distributedAeroLoads( V_load, Omega_load, pitch_load, azimuth_load) Np = loads['Np'] Tp = loads['Tp'] # concatenate loads at root/tip outputs['loads_r'] = r # conform to blade-aligned coordinate system outputs['loads_Px'] = Np outputs['loads_Py'] = -Tp outputs['loads_Pz'] = 0 * Np
class CCBladeLoads(ExplicitComponent): def initialize(self): self.options.declare('naero') self.options.declare('npower') self.options.declare('n_aoa_grid') self.options.declare('n_Re_grid') def setup(self): self.naero = naero = self.options['naero'] npower = self.options['npower'] n_aoa_grid = self.options['n_aoa_grid'] n_Re_grid = self.options['n_Re_grid'] """blade element momentum code""" # inputs self.add_input('V_load', val=0.0, units='m/s', desc='hub height wind speed') self.add_input('Omega_load', val=0.0, units='rpm', desc='rotor rotation speed') self.add_input('pitch_load', val=0.0, units='deg', desc='blade pitch setting') self.add_input('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_input( '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_input('chord', val=np.zeros(naero), units='m', desc='chord length at each section') self.add_input( 'theta', val=np.zeros(naero), units='deg', desc= 'twist angle at each section (positive decreases angle of attack)') self.add_input('Rhub', val=0.0, units='m', desc='hub radius') self.add_input('Rtip', val=0.0, units='m', desc='tip radius') self.add_input('hub_height', val=0.0, units='m', desc='hub height') self.add_input('precone', val=0.0, desc='precone angle', units='deg') self.add_input('tilt', val=0.0, desc='shaft tilt', units='deg') self.add_input('yaw', val=0.0, desc='yaw error', units='deg') # TODO: I've not hooked up the gradients for these ones yet. self.add_input('precurve', val=np.zeros(naero), units='m', desc='precurve at each section') self.add_input('precurveTip', val=0.0, units='m', desc='precurve at tip') # parameters # self.add_discrete_input('airfoils', val=[0]*naero, desc='CCAirfoil instances') self.add_input('airfoils_cl', val=np.zeros((n_aoa_grid, naero, n_Re_grid)), desc='lift coefficients, spanwise') self.add_input('airfoils_cd', val=np.zeros((n_aoa_grid, naero, n_Re_grid)), desc='drag coefficients, spanwise') self.add_input('airfoils_cm', val=np.zeros((n_aoa_grid, naero, n_Re_grid)), desc='moment coefficients, spanwise') self.add_input('airfoils_aoa', val=np.zeros((n_aoa_grid)), units='deg', desc='angle of attack grid for polars') self.add_input('airfoils_Re', val=np.zeros((n_Re_grid)), desc='Reynolds numbers of polars') self.add_discrete_input('nBlades', val=0, desc='number of blades') self.add_input('rho', val=0.0, units='kg/m**3', desc='density of air') self.add_input('mu', val=0.0, units='kg/(m*s)', desc='dynamic viscosity of air') self.add_input('shearExp', val=0.0, desc='shear exponent') self.add_discrete_input( 'nSector', val=4, desc= 'number of sectors to divide rotor face into in computing thrust and power' ) self.add_discrete_input('tiploss', val=True, desc='include Prandtl tip loss model') self.add_discrete_input('hubloss', val=True, desc='include Prandtl hub loss model') self.add_discrete_input( 'wakerotation', val=True, desc= 'include effect of wake rotation (i.e., tangential induction factor is nonzero)' ) self.add_discrete_input( 'usecd', val=True, desc='use drag coefficient in computing induction factors') #self.declare_partials('loads_r', ['r', 'Rhub', 'Rtip']) #self.declare_partials(['loads_Px', 'loads_Py'], # ['r', 'chord', 'theta', 'Rhub', 'Rtip', 'hub_height', 'precone', 'tilt', # 'yaw', 'V_load', 'Omega_load', 'pitch_load', 'azimuth_load', 'precurve']) #self.declare_partials('loads_V', 'V_load') #self.declare_partials('loads_Omega', 'Omega_load') #self.declare_partials('loads_pitch', 'pitch_load') #self.declare_partials('loads_azimuth', 'azimuth_load') def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): self.r = inputs['r'] self.chord = inputs['chord'] self.theta = inputs['theta'] self.Rhub = inputs['Rhub'] self.Rtip = inputs['Rtip'] self.hub_height = inputs['hub_height'] self.precone = inputs['precone'] self.tilt = inputs['tilt'] self.yaw = inputs['yaw'] self.precurve = inputs['precurve'] self.precurveTip = inputs['precurveTip'] # self.airfoils = discrete_inputs['airfoils'] self.B = discrete_inputs['nBlades'] self.rho = inputs['rho'] self.mu = inputs['mu'] self.shearExp = inputs['shearExp'] self.nSector = discrete_inputs['nSector'] self.tiploss = discrete_inputs['tiploss'] self.hubloss = discrete_inputs['hubloss'] self.wakerotation = discrete_inputs['wakerotation'] self.usecd = discrete_inputs['usecd'] self.V_load = inputs['V_load'] self.Omega_load = inputs['Omega_load'] self.pitch_load = inputs['pitch_load'] self.azimuth_load = inputs['azimuth_load'] if len(self.precurve) == 0: self.precurve = np.zeros_like(self.r) # airfoil files # n = len(self.airfoils) af = [None] * self.naero for i in range(self.naero): af[i] = CCAirfoil(inputs['airfoils_aoa'], inputs['airfoils_Re'], inputs['airfoils_cl'][:, i, :], inputs['airfoils_cd'][:, i, :], inputs['airfoils_cm'][:, i, :]) # af = self.airfoils 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.hub_height, 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 outputs['loads_r'] = self.r # conform to blade-aligned coordinate system outputs['loads_Px'] = Np outputs['loads_Py'] = -Tp outputs['loads_Pz'] = 0 * Np # return other outputs needed outputs['loads_V'] = self.V_load outputs['loads_Omega'] = self.Omega_load outputs['loads_pitch'] = self.pitch_load outputs['loads_azimuth'] = self.azimuth_load '''