1
+ import numpy as np
2
+
3
+ class fin :
4
+ '''
5
+ Represents a fin for hydrodynamic calculations.
6
+
7
+ INPUTS:
8
+ a: fin area (m^2)
9
+ CL: coefficient of lift (dimensionless)
10
+ x: x distance (m) from center of vehicle (negative for behind COM)
11
+ c: radius (m) from COP on the fin to the COM in the YZ plane
12
+ angle: offset of fin angle around x axis (deg) starting from positive y
13
+ 0 deg: fin on left side looking from front
14
+ 90 deg: fin on bottom
15
+ rho: density of fluid (kg/m^3)
16
+
17
+ Coordinate system: Right-handed, x-forward, y-starboard, z-down
18
+ '''
19
+
20
+ def __init__ (
21
+ self ,
22
+ a ,
23
+ CL ,
24
+ x ,
25
+ c = 0 ,
26
+ angle = 0 ,
27
+ rho = 1026 , # Default density of seawater
28
+ ):
29
+
30
+ self .area = a # Fin area (m^2)
31
+ self .CL = CL # Coefficient of lift (dimensionless)
32
+ self .angle_rad = np .deg2rad (angle )
33
+ self .rho = rho # Fluid density (kg/m^3)
34
+
35
+ self .u_actual_fin = 0.0 #Actual position of the fin (rad)
36
+ self .T_delta = 0.1 # fin time constant (s)
37
+ self .deltaMax = np .deg2rad (15 ) # max rudder angle (rad)
38
+
39
+ # Calculate fin's Center of Pressure (COP) position relative to COB
40
+ y = np .cos (self .angle_rad ) * c # y-component of COP (m)
41
+ z = np .sin (self .angle_rad ) * c # z-component of COP (m)
42
+ self .R = np .array ([x , y , z ]) # Location of COP of the fin relative to COB (m)
43
+
44
+ def velocity_in_rotated_plane (self , nu_r ):
45
+ """
46
+ Calculate velocity magnitude in a plane rotated around the x-axis.
47
+
48
+ Parameters:
49
+ nu_r (numpy array): Velocity vector [vx, vy, vz] (m/s) in ENU frame
50
+
51
+ Returns:
52
+ float: Magnitude of velocity (m/s) in the rotated plane.
53
+ """
54
+ # Extract velocity components
55
+ vx , vy , vz = nu_r # m/s
56
+
57
+ # Rotate y component around x-axis to align with fin plane
58
+ vy_rot = np .sqrt ((vy * np .sin (self .angle_rad ))** 2 + (vz * np .cos (self .angle_rad ))** 2 )
59
+
60
+ # Calculate magnitude in the rotated plane (x, y')
61
+ U_plane = np .sqrt (vx ** 2 + vy_rot ** 2 )
62
+
63
+ return U_plane # m/s
64
+
65
+ def tau (self , nu_r , nu ):
66
+ """
67
+ Calculate force vector generated by the fin.
68
+
69
+ Parameters:
70
+ nu_r (numpy array): Relative velocity [vx, vy, vz, p, q, r]
71
+ (m/s for linear, rad/s for angular)
72
+ nu (numpy array): Velocity [vx, vy, vz, p, q, r]
73
+ (m/s for linear, rad/s for angular)
74
+
75
+ Returns:
76
+ numpy array: tau vector [Fx, Fy, Fz, Tx, Ty, Tz] (N) and (N*m) in body-fixed frame
77
+ """
78
+
79
+ ur = self .velocity_in_rotated_plane (nu_r [:3 ]) # Calulate relative velocity in plane of the fin
80
+
81
+ # Calculate lift force magnitude
82
+ f = 0.5 * self .rho * self .area * self .CL * self .u_actual_fin * ur ** 2 # N
83
+
84
+ # Decompose force into y and z components
85
+ fy = np .sin (self .angle_rad ) * f # N
86
+ fz = - np .cos (self .angle_rad ) * f # N
87
+
88
+ F = np .array ([0 , fy , fz ]) # Force vector (N)
89
+
90
+ # Calculate torque using cross product of force and moment arm
91
+ torque = np .cross (self .R , F ) # N*m
92
+ return np .append (F , torque )
93
+
94
+ def actuate (self , sampleTime , command ):
95
+ # Actuator dynamics
96
+ delta_dot = (command - self .u_actual_fin ) / self .T_delta
97
+ self .u_actual_fin += sampleTime * delta_dot # Euler integration
98
+
99
+ # Amplitude Saturation
100
+ if abs (self .u_actual_fin ) >= self .deltaMax :
101
+ self .u_actual_fin = np .sign (self .u_actual_fin ) * self .deltaMax
102
+
103
+ return self .u_actual_fin
104
+
105
+
106
+
107
+
108
+
109
+ class thruster :
110
+ '''
111
+ Represents a thruster for hydrodynamic calculations.
112
+
113
+ INPUTS:
114
+ rho: density of fluid (kg/m^3)
115
+
116
+ Coordinate system: Right-handed, x-forward, y-starboard, z-down
117
+ '''
118
+ def __init__ (self , rho ):
119
+ # Actuator dynamics
120
+ self .nMax = 1525 # max propeller revolution (rpm)
121
+ self .T_n = 0.1 # propeller time constant (s)
122
+ self .u_actual_n = 0.0 # actual rpm of the thruster
123
+ self .rho = rho
124
+
125
+ def tau (self , nu_r , nu ):
126
+ """
127
+ Calculate force vector generated by the fin.
128
+
129
+ Parameters:
130
+ nu_r (numpy array): Relative velocity [vx, vy, vz, p, q, r]
131
+ (m/s for linear, rad/s for angular)
132
+ nu (numpy array): Velocity [vx, vy, vz, p, q, r]
133
+ (m/s for linear, rad/s for angular)
134
+
135
+ Returns:
136
+ numpy array: tau vector [Fx, Fy, Fz, Tx, Ty, Tz] (N) and (N*m) in body-fixed frame
137
+ """
138
+ U = np .sqrt (nu [0 ]** 2 + nu [1 ]** 2 + nu [2 ]** 2 ) # vehicle speed
139
+
140
+ # Commands and actual control signals
141
+ n = self .u_actual_n # actual propeller revolution (rpm)
142
+
143
+ # Amplitude saturation of the control signals
144
+ if abs (n ) >= self .nMax :
145
+ n = np .sign (n ) * self .nMax
146
+
147
+ # Propeller coeffs. KT and KQ are computed as a function of advance no.
148
+ # Ja = Va/(n*D_prop) where Va = (1-w)*U = 0.944 * U; Allen et al. (2000)
149
+ D_prop = 0.14 # propeller diameter corresponding to 5.5 inches
150
+ t_prop = 0.1 # thrust deduction number
151
+ n_rps = n / 60 # propeller revolution (rps)
152
+ Va = 0.944 * U # advance speed (m/s)
153
+
154
+ # Ja_max = 0.944 * 2.5 / (0.14 * 1525/60) = 0.6632
155
+ Ja_max = 0.6632
156
+
157
+ # Single-screw propeller with 3 blades and blade-area ratio = 0.718.
158
+ # Coffes. are computed using the Matlab MSS toolbox:
159
+ # >> [KT_0, KQ_0] = wageningen(0,1,0.718,3)
160
+ KT_0 = 0.4566
161
+ KQ_0 = 0.0700
162
+ # >> [KT_max, KQ_max] = wageningen(0.6632,1,0.718,3)
163
+ KT_max = 0.1798
164
+ KQ_max = 0.0312
165
+
166
+ # Propeller thrust and propeller-induced roll moment
167
+ # Linear approximations for positive Ja values
168
+ # KT ~= KT_0 + (KT_max-KT_0)/Ja_max * Ja
169
+ # KQ ~= KQ_0 + (KQ_max-KQ_0)/Ja_max * Ja
170
+
171
+ if n_rps > 0 : # forward thrust
172
+
173
+ X_prop = self .rho * pow (D_prop ,4 ) * (
174
+ KT_0 * abs (n_rps ) * n_rps + (KT_max - KT_0 )/ Ja_max *
175
+ (Va / D_prop ) * abs (n_rps ) )
176
+ K_prop = self .rho * pow (D_prop ,5 ) * (
177
+ KQ_0 * abs (n_rps ) * n_rps + (KQ_max - KQ_0 )/ Ja_max *
178
+ (Va / D_prop ) * abs (n_rps ) )
179
+
180
+ else : # reverse thrust (braking)
181
+
182
+ X_prop = self .rho * pow (D_prop ,4 ) * KT_0 * abs (n_rps ) * n_rps
183
+ K_prop = self .rho * pow (D_prop ,5 ) * KQ_0 * abs (n_rps ) * n_rps
184
+
185
+ # Thrust force vector
186
+ # K_Prop scaled down by a factor of 10 to match exp. results
187
+ tau_thrust = np .array ([(1 - t_prop ) * X_prop , 0 , 0 , K_prop / 10 , 0 , 0 ], float )
188
+ return tau_thrust
189
+
190
+ def actuate (self , sampleTime ,command ):
191
+ # Actuator dynamics
192
+ n_dot = (command - self .u_actual_n ) / self .T_n
193
+
194
+ self .u_actual_n += sampleTime * n_dot
195
+
196
+ return self .u_actual_n
197
+
0 commit comments