@@ -1348,6 +1348,80 @@ def lateral_surface_wind(self):
13481348
13491349 return - wind_u * np .cos (heading_rad ) + wind_v * np .sin (heading_rad )
13501350
1351+ def __get_drag_coefficient (
1352+ self , drag_function , mach , z , velocity_body , freestream_velocity_body
1353+ ):
1354+ """Calculate drag coefficient, handling both 1D and multi-dimensional functions.
1355+
1356+ Parameters
1357+ ----------
1358+ drag_function : Function
1359+ The drag coefficient function (power_on_drag or power_off_drag)
1360+ mach : float
1361+ Mach number
1362+ z : float
1363+ Altitude in meters
1364+ velocity_body : Vector or array-like
1365+ Rocket velocity in body frame [vx_b, vy_b, vz_b]
1366+ freestream_velocity_body : Vector or array-like
1367+ Freestream velocity in body frame [stream_vx_b, stream_vy_b, stream_vz_b]
1368+
1369+ Returns
1370+ -------
1371+ float
1372+ Drag coefficient value
1373+ """
1374+ # Check if drag function is multi-dimensional by examining its inputs
1375+ if hasattr (drag_function , "__inputs__" ) and len (drag_function .__inputs__ ) > 1 :
1376+ # Multi-dimensional drag function - calculate additional parameters
1377+
1378+ # Calculate Reynolds number
1379+ # Re = rho * V * L / mu
1380+ # where L is characteristic length (rocket diameter)
1381+ rho = self .env .density .get_value_opt (z )
1382+ mu = self .env .dynamic_viscosity .get_value_opt (z )
1383+ freestream_speed = np .linalg .norm (freestream_velocity_body )
1384+ characteristic_length = 2 * self .rocket .radius # Diameter
1385+ reynolds = rho * freestream_speed * characteristic_length / mu
1386+
1387+ # Calculate angle of attack
1388+ # Angle between freestream velocity and rocket axis (z-axis in body frame)
1389+ # The z component of freestream velocity in body frame
1390+ if hasattr (freestream_velocity_body , "z" ):
1391+ stream_vz_b = - freestream_velocity_body .z
1392+ else :
1393+ stream_vz_b = - freestream_velocity_body [2 ]
1394+
1395+ # Normalize and calculate angle
1396+ if freestream_speed > 1e-6 :
1397+ cos_alpha = stream_vz_b / freestream_speed
1398+ # Clamp to [-1, 1] to avoid numerical issues
1399+ cos_alpha = np .clip (cos_alpha , - 1.0 , 1.0 )
1400+ alpha_rad = np .arccos (cos_alpha )
1401+ alpha_deg = np .rad2deg (alpha_rad )
1402+ else :
1403+ alpha_deg = 0.0
1404+
1405+ # Determine which parameters to pass based on input names
1406+ input_names = [name .lower () for name in drag_function .__inputs__ ]
1407+ args = []
1408+
1409+ for name in input_names :
1410+ if "mach" in name or name == "m" :
1411+ args .append (mach )
1412+ elif "reynolds" in name or name == "re" :
1413+ args .append (reynolds )
1414+ elif "alpha" in name or name == "a" or "attack" in name :
1415+ args .append (alpha_deg )
1416+ else :
1417+ # Unknown parameter, default to mach
1418+ args .append (mach )
1419+
1420+ return drag_function .get_value_opt (* args )
1421+ else :
1422+ # 1D drag function - only mach number
1423+ return drag_function .get_value_opt (mach )
1424+
13511425 def udot_rail1 (self , t , u , post_processing = False ):
13521426 """Calculates derivative of u state vector with respect to time
13531427 when rocket is flying in 1 DOF motion in the rail.
@@ -1384,7 +1458,37 @@ def udot_rail1(self, t, u, post_processing=False):
13841458 + (vz ) ** 2
13851459 ) ** 0.5
13861460 free_stream_mach = free_stream_speed / self .env .speed_of_sound .get_value_opt (z )
1387- drag_coeff = self .rocket .power_on_drag .get_value_opt (free_stream_mach )
1461+
1462+ # For rail motion, rocket is constrained - velocity mostly along z-axis in body frame
1463+ # Calculate velocity in body frame (simplified for rail)
1464+ a11 = 1 - 2 * (e2 ** 2 + e3 ** 2 )
1465+ a12 = 2 * (e1 * e2 - e0 * e3 )
1466+ a13 = 2 * (e1 * e3 + e0 * e2 )
1467+ a21 = 2 * (e1 * e2 + e0 * e3 )
1468+ a22 = 1 - 2 * (e1 ** 2 + e3 ** 2 )
1469+ a23 = 2 * (e2 * e3 - e0 * e1 )
1470+ a31 = 2 * (e1 * e3 - e0 * e2 )
1471+ a32 = 2 * (e2 * e3 + e0 * e1 )
1472+ a33 = 1 - 2 * (e1 ** 2 + e2 ** 2 )
1473+
1474+ vx_b = a11 * vx + a21 * vy + a31 * vz
1475+ vy_b = a12 * vx + a22 * vy + a32 * vz
1476+ vz_b = a13 * vx + a23 * vy + a33 * vz
1477+
1478+ # Freestream velocity in body frame
1479+ wind_vx = self .env .wind_velocity_x .get_value_opt (z )
1480+ wind_vy = self .env .wind_velocity_y .get_value_opt (z )
1481+ stream_vx_b = a11 * (wind_vx - vx ) + a21 * (wind_vy - vy ) + a31 * (- vz )
1482+ stream_vy_b = a12 * (wind_vx - vx ) + a22 * (wind_vy - vy ) + a32 * (- vz )
1483+ stream_vz_b = a13 * (wind_vx - vx ) + a23 * (wind_vy - vy ) + a33 * (- vz )
1484+
1485+ drag_coeff = self .__get_drag_coefficient (
1486+ self .rocket .power_on_drag ,
1487+ free_stream_mach ,
1488+ z ,
1489+ [vx_b , vy_b , vz_b ],
1490+ [stream_vx_b , stream_vy_b , stream_vz_b ],
1491+ )
13881492
13891493 # Calculate Forces
13901494 pressure = self .env .pressure .get_value_opt (z )
@@ -1552,12 +1656,34 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals
15521656 ) ** 0.5
15531657 free_stream_mach = free_stream_speed / speed_of_sound
15541658
1659+ # Get rocket velocity in body frame (needed for drag calculation)
1660+ vx_b = a11 * vx + a21 * vy + a31 * vz
1661+ vy_b = a12 * vx + a22 * vy + a32 * vz
1662+ vz_b = a13 * vx + a23 * vy + a33 * vz
1663+
1664+ # Calculate freestream velocity in body frame
1665+ stream_vx_b = a11 * (wind_velocity_x - vx ) + a21 * (wind_velocity_y - vy ) + a31 * (- vz )
1666+ stream_vy_b = a12 * (wind_velocity_x - vx ) + a22 * (wind_velocity_y - vy ) + a32 * (- vz )
1667+ stream_vz_b = a13 * (wind_velocity_x - vx ) + a23 * (wind_velocity_y - vy ) + a33 * (- vz )
1668+
15551669 # Determine aerodynamics forces
15561670 # Determine Drag Force
15571671 if t < self .rocket .motor .burn_out_time :
1558- drag_coeff = self .rocket .power_on_drag .get_value_opt (free_stream_mach )
1672+ drag_coeff = self .__get_drag_coefficient (
1673+ self .rocket .power_on_drag ,
1674+ free_stream_mach ,
1675+ z ,
1676+ [vx_b , vy_b , vz_b ],
1677+ [stream_vx_b , stream_vy_b , stream_vz_b ],
1678+ )
15591679 else :
1560- drag_coeff = self .rocket .power_off_drag .get_value_opt (free_stream_mach )
1680+ drag_coeff = self .__get_drag_coefficient (
1681+ self .rocket .power_off_drag ,
1682+ free_stream_mach ,
1683+ z ,
1684+ [vx_b , vy_b , vz_b ],
1685+ [stream_vx_b , stream_vy_b , stream_vz_b ],
1686+ )
15611687 rho = self .env .density .get_value_opt (z )
15621688 R3 = - 0.5 * rho * (free_stream_speed ** 2 ) * self .rocket .area * drag_coeff
15631689 for air_brakes in self .rocket .air_brakes :
@@ -1579,10 +1705,6 @@ def u_dot(self, t, u, post_processing=False): # pylint: disable=too-many-locals
15791705 # Off center moment
15801706 M1 += self .rocket .cp_eccentricity_y * R3
15811707 M2 -= self .rocket .cp_eccentricity_x * R3
1582- # Get rocket velocity in body frame
1583- vx_b = a11 * vx + a21 * vy + a31 * vz
1584- vy_b = a12 * vx + a22 * vy + a32 * vz
1585- vz_b = a13 * vx + a23 * vy + a33 * vz
15861708 # Calculate lift and moment for each component of the rocket
15871709 velocity_in_body_frame = Vector ([vx_b , vy_b , vz_b ])
15881710 w = Vector ([omega1 , omega2 , omega3 ])
@@ -1831,17 +1953,35 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too
18311953 speed_of_sound = self .env .speed_of_sound .get_value_opt (z )
18321954 free_stream_mach = free_stream_speed / speed_of_sound
18331955
1956+ # Get rocket velocity in body frame (needed for drag calculation)
1957+ velocity_in_body_frame = Kt @ v
1958+ # Calculate freestream velocity in body frame
1959+ freestream_velocity = wind_velocity - v
1960+ freestream_velocity_body = Kt @ freestream_velocity
1961+
18341962 if self .rocket .motor .burn_start_time < t < self .rocket .motor .burn_out_time :
18351963 pressure = self .env .pressure .get_value_opt (z )
18361964 net_thrust = max (
18371965 self .rocket .motor .thrust .get_value_opt (t )
18381966 + self .rocket .motor .pressure_thrust (pressure ),
18391967 0 ,
18401968 )
1841- drag_coeff = self .rocket .power_on_drag .get_value_opt (free_stream_mach )
1969+ drag_coeff = self .__get_drag_coefficient (
1970+ self .rocket .power_on_drag ,
1971+ free_stream_mach ,
1972+ z ,
1973+ velocity_in_body_frame ,
1974+ freestream_velocity_body ,
1975+ )
18421976 else :
18431977 net_thrust = 0
1844- drag_coeff = self .rocket .power_off_drag .get_value_opt (free_stream_mach )
1978+ drag_coeff = self .__get_drag_coefficient (
1979+ self .rocket .power_off_drag ,
1980+ free_stream_mach ,
1981+ z ,
1982+ velocity_in_body_frame ,
1983+ freestream_velocity_body ,
1984+ )
18451985 R3 += - 0.5 * rho * (free_stream_speed ** 2 ) * self .rocket .area * drag_coeff
18461986 for air_brakes in self .rocket .air_brakes :
18471987 if air_brakes .deployment_level > 0 :
@@ -1859,8 +1999,6 @@ def u_dot_generalized(self, t, u, post_processing=False): # pylint: disable=too
18591999 R3 = air_brakes_force # Substitutes rocket drag coefficient
18602000 else :
18612001 R3 += air_brakes_force
1862- # Get rocket velocity in body frame
1863- velocity_in_body_frame = Kt @ v
18642002 # Calculate lift and moment for each component of the rocket
18652003 for aero_surface , _ in self .rocket .aerodynamic_surfaces :
18662004 # Component cp relative to CDM in body frame
0 commit comments