def evaluate(self, Uinf, Omega, pitch, coefficient=False): """Run the aerodynamic analysis at the specified conditions. Parameters ---------- Uinf : array_like (m/s) hub height wind speed Omega : array_like (RPM) rotor rotation speed pitch : array_like (deg) blade pitch setting coefficient : bool, optional if True, results are returned in nondimensional form Returns ------- P or CP : ndarray (W) power or power coefficient T or CT : ndarray (N) thrust or thrust coefficient (magnitude) Q or CQ : ndarray (N*m) torque or torque coefficient (magnitude) dP_ds or dCP_ds : ndarray, shape=(npts, 7) derivative of power or power coefficient w.r.t. scalar quantities thus dP_ds[i, 4] = dP_dRtip for input condition i dT_ds or dCT_ds : ndarray, shape=(npts, 7) derivative of thrust or thrust coefficient w.r.t. scalar quantities thus dT_ds[i, 4] = dT_dRtip for input condition i dQ_ds or dCQ_ds : ndarray, shape=(npts, 7) derivative of torque or torque coefficient w.r.t. scalar quantities thus dQ_ds[i, 4] = dQ_dRtip for input condition i dP_dv or dCP_dv : ndarray, shape=(npts, 5, n) derivative of power or power coefficient w.r.t. vector quantities thus dP_dv[i, 1, j] = dP_dchord_j for input condition i dT_dv or dCT_dv : ndarray, shape=(npts, 5, n) derivative of thrust or thrust coefficient w.r.t. vector quantities thus dT_dv[i, 1, j] = dT_dchord_j for input condition i dQ_dv or dCQ_dv : ndarray, shape=(npts, 5, n) derivative of torque or torque coefficient w.r.t. vector quantities thus dQ_dv[i, 1, j] = dQ_dchord_j for input condition i Notes ----- CP = P / (q * Uinf * A) CT = T / (q * A) CQ = Q / (q * A * R) The rotor radius R, may not actually be Rtip if precone and precurve are both nonzero ``R = Rtip*cos(precone) + precurveTip*sin(precone)`` order of scalar quantities is: s = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip] order of vector quantities is: v = [r, chord, theta, precurve, presweep] """ # rename args = (self.r, self.precurve, self.presweep, self.precone, self.Rhub, self.Rtip, self.precurveTip, self.presweepTip) nsec = self.nSector # initialize Uinf = np.array(Uinf) Omega = np.array(Omega) pitch = np.array(pitch) npts = len(Uinf) T = np.zeros(npts) Q = np.zeros(npts) P = np.zeros(npts) if self.derivatives: dT_ds = np.zeros((npts, 7)) dQ_ds = np.zeros((npts, 7)) dT_dv = np.zeros((npts, 5, len(self.r))) dQ_dv = np.zeros((npts, 5, len(self.r))) for i in range(npts): # iterate across conditions for j in range(nsec): # integrate across azimuth azimuth = 360.0*float(j)/nsec if not self.derivatives: # contribution from this azimuthal location Np, Tp = self.distributedAeroLoads(Uinf[i], Omega[i], pitch[i], azimuth) else: Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve = \ self.distributedAeroLoads(Uinf[i], Omega[i], pitch[i], azimuth) dT_ds_sub, dQ_ds_sub, dT_dv_sub, dQ_dv_sub = \ self.__thrustTorqueDeriv(Np, Tp, dNp_dX, dTp_dX, dNp_dprecurve, dTp_dprecurve, *args) dT_ds[i, :] += self.B * dT_ds_sub / nsec dQ_ds[i, :] += self.B * dQ_ds_sub / nsec dT_dv[i, :, :] += self.B * dT_dv_sub / nsec dQ_dv[i, :, :] += self.B * dQ_dv_sub / nsec Tsub, Qsub = _bem.thrusttorque(Np, Tp, *args) T[i] += self.B * Tsub / nsec Q[i] += self.B * Qsub / nsec # Power P = Q * Omega*pi/30.0 # RPM to rad/s # normalize if necessary if coefficient: q = 0.5 * self.rho * Uinf**2 A = pi * self.rotorR**2 CP = P / (q * A * Uinf) CT = T / (q * A) CQ = Q / (q * self.rotorR * A) if self.derivatives: dR_ds = np.array([-self.Rtip*sin(self.precone)*pi/180.0 + self.precurveTip*cos(self.precone)*pi/180.0, 0.0, 0.0, 0.0, cos(self.precone), sin(self.precone), 0.0]) dCT_ds = dT_ds / (q*A) - dR_ds * 2.0*CT/self.rotorR dCT_dv = dT_dv / (q*A) dCQ_ds = dQ_ds / (q*self.rotorR*A) - dR_ds * 3.0*CQ/self.rotorR dCQ_dv = dQ_dv / (q*self.rotorR*A) dCP_ds = dQ_ds * CP/Q - dR_ds * 2.0*CP/self.rotorR dCP_dv = dQ_dv * CP/Q return CP, CT, CQ, dCP_ds, dCT_ds, dCQ_ds, dCP_dv, dCT_dv, dCQ_dv else: return CP, CT, CQ if self.derivatives: # scalars = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip] # vectors = [r, chord, theta, precurve, presweep] dP_ds = dQ_ds * Omega*pi/30.0 dP_dv = dQ_dv * Omega*pi/30.0 return P, T, Q, dP_ds, dT_ds, dQ_ds, dP_dv, dT_dv, dQ_dv else: return P, T, Q
def evaluate(self, Uinf, Omega, pitch, coefficient=False): """Run the aerodynamic analysis at the specified conditions. Parameters ---------- Uinf : array_like (m/s) hub height wind speed Omega : array_like (RPM) rotor rotation speed pitch : array_like (deg) blade pitch setting coefficient : bool, optional if True, results are returned in nondimensional form Returns ------- P or CP : ndarray (W) power or power coefficient T or CT : ndarray (N) thrust or thrust coefficient (magnitude) Q or CQ : ndarray (N*m) torque or torque coefficient (magnitude) dP or dCP : dictionary of arrays (present only if derivatives==True) derivatives of power or power coefficient. Each item in dictionary is a Jacobian. The array sizes and keys are below npts is the number of conditions (len(Uinf)), n = number of stations along blade (len(r)) npts x 1: 'dprecone', 'dtilt', 'dhubHt', 'dRhub', 'dRtip', 'dprecurveTip', 'dpresweepTip', 'dyaw' npts x npts: 'dUinf', 'dOmega', 'dpitch' npts x n: 'dr', 'dchord', 'dtheta', 'dprecurve', 'dpresweep' for example dP_dr = dP['dr'] (where dP_dr is an npts x n array) and dP_dr[i, j] = dP_i / dr_j dT or dCT : dictionary of arrays (present only if derivatives==True) derivative of thrust or thrust coefficient. Same format as dP and dCP dQ or dCQ : dictionary of arrays (present only if derivatives==True) derivative of torque or torque coefficient. Same format as dP and dCP Notes ----- CP = P / (q * Uinf * A) CT = T / (q * A) CQ = Q / (q * A * R) The rotor radius R, may not actually be Rtip if precone and precurve are both nonzero ``R = Rtip*cos(precone) + precurveTip*sin(precone)`` """ # rename args = (self.r, self.precurve, self.presweep, self.precone, self.Rhub, self.Rtip, self.precurveTip, self.presweepTip) nsec = self.nSector # initialize Uinf = np.array(Uinf) Omega = np.array(Omega) pitch = np.array(pitch) npts = len(Uinf) T = np.zeros(npts) Q = np.zeros(npts) P = np.zeros(npts) if self.derivatives: dT_ds = np.zeros((npts, 11)) dQ_ds = np.zeros((npts, 11)) dT_dv = np.zeros((npts, 5, len(self.r))) dQ_dv = np.zeros((npts, 5, len(self.r))) for i in range(npts): # iterate across conditions for j in range(nsec): # integrate across azimuth azimuth = 360.0 * float(j) / nsec if not self.derivatives: # contribution from this azimuthal location Np, Tp = self.distributedAeroLoads(Uinf[i], Omega[i], pitch[i], azimuth) else: Np, Tp, dNp, dTp = self.distributedAeroLoads( Uinf[i], Omega[i], pitch[i], azimuth) dT_ds_sub, dQ_ds_sub, dT_dv_sub, dQ_dv_sub = self.__thrustTorqueDeriv( Np, Tp, self._dNp_dX, self._dTp_dX, self._dNp_dprecurve, self._dTp_dprecurve, *args) dT_ds[i, :] += self.B * dT_ds_sub / nsec dQ_ds[i, :] += self.B * dQ_ds_sub / nsec dT_dv[i, :, :] += self.B * dT_dv_sub / nsec dQ_dv[i, :, :] += self.B * dQ_dv_sub / nsec Tsub, Qsub = _bem.thrusttorque(Np, Tp, *args) T[i] += self.B * Tsub / nsec Q[i] += self.B * Qsub / nsec # Power P = Q * Omega * pi / 30.0 # RPM to rad/s # normalize if necessary if coefficient: q = 0.5 * self.rho * Uinf**2 A = pi * self.rotorR**2 CP = P / (q * A * Uinf) CT = T / (q * A) CQ = Q / (q * self.rotorR * A) if self.derivatives: # s = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip, yaw, Uinf, Omega, pitch] dR_ds = np.array([ -self.Rtip * sin(self.precone) * pi / 180.0 + self.precurveTip * cos(self.precone) * pi / 180.0, 0.0, 0.0, 0.0, cos(self.precone), sin(self.precone), 0.0, 0.0, 0.0, 0.0, 0.0 ]) dR_ds = np.dot(np.ones( (npts, 1)), np.array([dR_ds ])) # same for each operating condition dA_ds = 2 * pi * self.rotorR * dR_ds dU_ds = np.zeros((npts, 11)) dU_ds[:, 8] = 1.0 dOmega_ds = np.zeros((npts, 11)) dOmega_ds[:, 9] = 1.0 dq_ds = (self.rho * Uinf * dU_ds.T).T dCT_ds = (CT * (dT_ds.T / T - dA_ds.T / A - dq_ds.T / q)).T dCT_dv = (dT_dv.T / (q * A)).T dCQ_ds = (CQ * (dQ_ds.T / Q - dA_ds.T / A - dq_ds.T / q - dR_ds.T / self.rotorR)).T dCQ_dv = (dQ_dv.T / (q * self.rotorR * A)).T dCP_ds = (CP * (dQ_ds.T / Q + dOmega_ds.T / Omega - dA_ds.T / A - dq_ds.T / q - dU_ds.T / Uinf)).T dCP_dv = (dQ_dv.T * CP / Q).T # pack derivatives into dictionary dCT, dCQ, dCP = self.__thrustTorqueDictionary( dCT_ds, dCQ_ds, dCP_ds, dCT_dv, dCQ_dv, dCP_dv, npts) return CP, CT, CQ, dCP, dCT, dCQ else: return CP, CT, CQ if self.derivatives: # scalars = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip, yaw, Uinf, Omega, pitch] # vectors = [r, chord, theta, precurve, presweep] dP_ds = (dQ_ds.T * Omega * pi / 30.0).T dP_ds[:, 9] += Q * pi / 30.0 dP_dv = (dQ_dv.T * Omega * pi / 30.0).T # pack derivatives into dictionary dT, dQ, dP = self.__thrustTorqueDictionary(dT_ds, dQ_ds, dP_ds, dT_dv, dQ_dv, dP_dv, npts) return P, T, Q, dP, dT, dQ else: return P, T, Q
def evaluate(self, Uinf, Omega, pitch, coefficient=False): """Run the aerodynamic analysis at the specified conditions. Parameters ---------- Uinf : array_like (m/s) hub height wind speed Omega : array_like (RPM) rotor rotation speed pitch : array_like (deg) blade pitch setting coefficient : bool, optional if True, results are returned in nondimensional form Returns ------- P or CP : ndarray (W) power or power coefficient T or CT : ndarray (N) thrust or thrust coefficient (magnitude) Q or CQ : ndarray (N*m) torque or torque coefficient (magnitude) dP or dCP : dictionary of arrays (present only if derivatives==True) derivatives of power or power coefficient. Each item in dictionary is a Jacobian. The array sizes and keys are below npts is the number of conditions (len(Uinf)), n = number of stations along blade (len(r)) npts x 1: 'dprecone', 'dtilt', 'dhubHt', 'dRhub', 'dRtip', 'dprecurveTip', 'dpresweepTip', 'dyaw' npts x npts: 'dUinf', 'dOmega', 'dpitch' npts x n: 'dr', 'dchord', 'dtheta', 'dprecurve', 'dpresweep' for example dP_dr = dP['dr'] (where dP_dr is an npts x n array) and dP_dr[i, j] = dP_i / dr_j dT or dCT : dictionary of arrays (present only if derivatives==True) derivative of thrust or thrust coefficient. Same format as dP and dCP dQ or dCQ : dictionary of arrays (present only if derivatives==True) derivative of torque or torque coefficient. Same format as dP and dCP Notes ----- CP = P / (q * Uinf * A) CT = T / (q * A) CQ = Q / (q * A * R) The rotor radius R, may not actually be Rtip if precone and precurve are both nonzero ``R = Rtip*cos(precone) + precurveTip*sin(precone)`` """ # rename args = ( self.r, self.precurve, self.presweep, self.precone, self.Rhub, self.Rtip, self.precurveTip, self.presweepTip, ) nsec = self.nSector # initialize Uinf = np.array(Uinf) Omega = np.array(Omega) pitch = np.array(pitch) npts = len(Uinf) T = np.zeros(npts) Q = np.zeros(npts) P = np.zeros(npts) if self.derivatives: dT_ds = np.zeros((npts, 11)) dQ_ds = np.zeros((npts, 11)) dT_dv = np.zeros((npts, 5, len(self.r))) dQ_dv = np.zeros((npts, 5, len(self.r))) for i in range(npts): # iterate across conditions for j in range(nsec): # integrate across azimuth azimuth = 360.0 * float(j) / nsec if not self.derivatives: # contribution from this azimuthal location Np, Tp = self.distributedAeroLoads(Uinf[i], Omega[i], pitch[i], azimuth) else: Np, Tp, dNp, dTp = self.distributedAeroLoads(Uinf[i], Omega[i], pitch[i], azimuth) dT_ds_sub, dQ_ds_sub, dT_dv_sub, dQ_dv_sub = self.__thrustTorqueDeriv( Np, Tp, self._dNp_dX, self._dTp_dX, self._dNp_dprecurve, self._dTp_dprecurve, *args ) dT_ds[i, :] += self.B * dT_ds_sub / nsec dQ_ds[i, :] += self.B * dQ_ds_sub / nsec dT_dv[i, :, :] += self.B * dT_dv_sub / nsec dQ_dv[i, :, :] += self.B * dQ_dv_sub / nsec Tsub, Qsub = _bem.thrusttorque(Np, Tp, *args) T[i] += self.B * Tsub / nsec Q[i] += self.B * Qsub / nsec # Power P = Q * Omega * pi / 30.0 # RPM to rad/s # normalize if necessary if coefficient: q = 0.5 * self.rho * Uinf ** 2 A = pi * self.rotorR ** 2 CP = P / (q * A * Uinf) CT = T / (q * A) CQ = Q / (q * self.rotorR * A) if self.derivatives: # s = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip, yaw, Uinf, Omega, pitch] dR_ds = np.array( [ -self.Rtip * sin(self.precone) * pi / 180.0 + self.precurveTip * cos(self.precone) * pi / 180.0, 0.0, 0.0, 0.0, cos(self.precone), sin(self.precone), 0.0, 0.0, 0.0, 0.0, 0.0, ] ) dR_ds = np.dot(np.ones((npts, 1)), np.array([dR_ds])) # same for each operating condition dA_ds = 2 * pi * self.rotorR * dR_ds dU_ds = np.zeros((npts, 11)) dU_ds[:, 8] = 1.0 dOmega_ds = np.zeros((npts, 11)) dOmega_ds[:, 9] = 1.0 dq_ds = (self.rho * Uinf * dU_ds.T).T dCT_ds = (CT * (dT_ds.T / T - dA_ds.T / A - dq_ds.T / q)).T dCT_dv = (dT_dv.T / (q * A)).T dCQ_ds = (CQ * (dQ_ds.T / Q - dA_ds.T / A - dq_ds.T / q - dR_ds.T / self.rotorR)).T dCQ_dv = (dQ_dv.T / (q * self.rotorR * A)).T dCP_ds = (CP * (dQ_ds.T / Q + dOmega_ds.T / Omega - dA_ds.T / A - dq_ds.T / q - dU_ds.T / Uinf)).T dCP_dv = (dQ_dv.T * CP / Q).T # pack derivatives into dictionary dCT, dCQ, dCP = self.__thrustTorqueDictionary(dCT_ds, dCQ_ds, dCP_ds, dCT_dv, dCQ_dv, dCP_dv, npts) return CP, CT, CQ, dCP, dCT, dCQ else: return CP, CT, CQ if self.derivatives: # scalars = [precone, tilt, hubHt, Rhub, Rtip, precurvetip, presweeptip, yaw, Uinf, Omega, pitch] # vectors = [r, chord, theta, precurve, presweep] dP_ds = (dQ_ds.T * Omega * pi / 30.0).T dP_ds[:, 9] += Q * pi / 30.0 dP_dv = (dQ_dv.T * Omega * pi / 30.0).T # pack derivatives into dictionary dT, dQ, dP = self.__thrustTorqueDictionary(dT_ds, dQ_ds, dP_ds, dT_dv, dQ_dv, dP_dv, npts) return P, T, Q, dP, dT, dQ else: return P, T, Q