최종 속도가 설정된 최적유도법칙(https://pasus.tistory.com/293)과 최종 속도가 설정되지 않은 최적유도법칙(https://pasus.tistory.com/294)을 '유도'해 보았다. 편의상 표적이 고정된 경우와 표적이 등속 운동을 하는 경우를 분리하여 각각의 최적유도법칙을 다시 써 보겠다.
먼저 표적이 정지 고정된 경우의 비행체의 운동방정식과 최적 유도법칙은 다음과 같다.
여기서

표적이 등속 운동하는 경우의 비행체와 표적 간의 상대 운동방정식과 최적 유도법칙은 다음과 같다.
여기서

만약
가 된다. 여기서
상대 운동방정식 (4)와 최적 유도법칙 (6), (8)에서 표적 위치벡터와 속도벡터를
이제 최적 유도법칙과 전통적인 비례항법유도(PNG, proportional navigation guidance) 법칙을 비교해 보고자 한다.
중력 가속도가
여기서
LOS 각속도 벡터는 다음과 같이 계산된다.
이 된다. 여기서
이 된다. 그런데 만약 다음 관계식이 성립한다면,
식 (12)는 다음과 같이 된다.
식 (14)에 의하면 중력 가속도가
이제 몇가지 시나리오에 대해서 시뮬레이션을 통하여 유도법칙 (2), (3), (6), (8), (15)를 검증해보자. 사용한 TPNG 코드는 다음과 같다.
def tpng(N, OmL, Vc, LOS_hat):
a_cmd = N * Vc * np.cross(OmL, LOS_hat) - np.array([0,0,-g])
a_cmd_mag = np.sqrt(a_cmd.dot(a_cmd))
if a_cmd_mag > (g * ng_limit):
a_cmd = (g * ng_limit) * a_cmd / a_cmd_mag
return a_cmd
비교를 위해서 PPNG (pure PNG)도 적용했는데 PPNG 법칙은 다음과 같다.
해당코드는 다음과 같다.
def ppng(N, OmL, vM):
vM_hat = vM / np.sqrt(vM.dot(vM))
gp = np.array([0,0,-g]) - (np.array([0,0,-g]).dot(vM_hat))*vM_hat
a_cmd = N * np.cross(OmL, vM) - gp
a_cmd_mag = np.sqrt(a_cmd.dot(a_cmd))
if a_cmd_mag > (g * ng_limit):
a_cmd = (g * ng_limit) * a_cmd / a_cmd_mag
return a_cmd
최종 속도가 미설정된 최적유도법칙인 식 (6)을 구현한 코드는 다음과 같다.
def optg(N, r, v, Vc, R, t):
#t_go = R/Vc
t_go = tf-t
a_cmd = N/(t_go*t_go) * (r + t_go*v) - np.array([0,0,-g])
a_cmd_mag = np.sqrt(a_cmd.dot(a_cmd))
if a_cmd_mag > (g * ng_limit):
a_cmd = (g * ng_limit) * a_cmd / a_cmd_mag
return a_cmd
최종 속도가 설정된 최적유도법칙인 식 (8)을 구현한 코드는 다음과 같다.
def optg_ivc(N, r, v, Vc, R, vF, vM, t):
#t_go = R/Vc
t_go = tf-t
a_cmd = N/(t_go*t_go) * (r + t_go*v) - (2/t_go) * (vF-vM) - np.array([0,0,-g])
a_cmd_mag = np.sqrt(a_cmd.dot(a_cmd))
if a_cmd_mag > (g * ng_limit):
a_cmd = (g * ng_limit) * a_cmd / a_cmd_mag
return a_cmd
첫번째 시나리오는 표적이 위치
아래 그림의 첫번째 줄은 TPNG, PPNG를 적용한 결과다. 비행시간은 대략 TPNG 42초, PPNG 38초가 나왔다. 그림의 두번째 줄은 최적유도법칙 (6)을 적용한 결과다. 잔여시간(time-to-go)을 식 (13)으로 계산하면 비행시간이 대략 최적유도 40초 정도가 나오는데 왼쪽 그림이다. 최종 임무시간을 임의로 70초로 설정하여 계산한 결과는 오른쪽에 있다.

두번째 시나리오는 첫번째 시나리오와 동일하지만 표적을 최종 속도
잔여시간을 식 (13)으로 계산하면 발산하였다. 위 식의 두번째 항의 영향으로 식 (13)의 근사식이 성립하지 않기 때문이다. 그림은 비행시간을 40초로 설정하고 잔여시간을 계산했을 때의 결과다. 표적을 타격했으나 타격각은 만족하지 못했다. 반면, 최적유도법칙을 적용했을 때는 오른쪽 그림과 같이 설정한 대로 정확히 x축 방향으로 타격했다.

세번째 시나리오는 표적이 시나리오 1과 동일한 위치에서 등속력

네번째 시나리오는 세번째 시나리오와 동일하지만 표적을 최종 속도

다음은 시커(seeker)의 기능을 코드로 구현한 것이다. 시커 모델에서는 LOS 각속도, LOS 방향벡터, 표적까지의 거리, 접근속도 등을 계산한다.
# seeker measurements or estimations
# coded by st.watermelon
from config import *
import numpy as np
def seeker_meas(rT, vT, rM, vM):
""" relative position and velocity vector """
rTM = rT - rM
vTM = vT - vM
R2 = rTM.dot(rTM)
R = np.sqrt(R2)
""" LOS rate """
OmL = np.cross(rTM, vTM)/R2
""" approaching speed """
Vc = -rTM.dot(vTM) / R
LOS_hat = rTM / R # LOS direction vector
return OmL, LOS_hat, R, Vc
다음은 표적과 비행체의 질점 운동모델을 코드로 구현한 것이다.
# 3DOF Dynamics
# coded by st.watermelon
from config import *
import numpy as np
class Dof3Dynamics:
def __init__(self):
pass
""" initialization """
def reset(self, pos, v_mag, azimuth, elevation):
self.p_vec = np.array(pos).reshape(3, )
vx0 = v_mag * np.cos(elevation) * np.cos(azimuth)
vy0 = v_mag * np.cos(elevation) * np.sin(azimuth)
vz0 = v_mag * np.sin(elevation)
self.v_vec = np.array([vx0, vy0, vz0]).reshape(3, )
return np.copy(self.p_vec), np.copy(self.v_vec) # shape (3,)
def step(self, a_cmd, option):
""" compute numerical integration(RK4) """
xx = np.append(self.p_vec, self.v_vec)
k1 = dt * self.fn(xx, a_cmd, option)
k2 = dt * self.fn(xx + dt / 2 * k1, a_cmd, option)
k3 = dt * self.fn(xx + dt / 2 * k2, a_cmd, option)
k4 = dt * self.fn(xx + dt * k3, a_cmd, option)
k = (k1 + 2 * k2 + 2 * k3 + k4) / 6
xx = xx + k
self.p_vec = xx[0:3]
self.v_vec = xx[3:6]
return np.copy(self.p_vec), np.copy(self.v_vec)
""" 3DOF dynamics """
def fn(self, xx, ctrl, option):
if option == 'target':
with_g = 0
else:
with_g = 1
v = xx[3:6]
p_dot = v
v_dot = ctrl + with_g*np.array(([0, 0, -g]))
xx_dot = np.append(p_dot, v_dot)
return xx_dot
다음은 시나리오 실행 코드이다.
# test guidance laws
# coded by st.watermelon
from config import *
from guidance import *
from dof3_dynamics import *
from seeker_meas import *
import numpy as np
import matplotlib.pyplot as plt
""" create target objects """
target = Dof3Dynamics()
# set initial position and velocity of target
posT = [10000, 10000, 3000]
v_magT = 300
azimuthT = 0*np.pi/180
elevationT = 0*np.pi/180
rT0, vT0 = target.reset(posT, v_magT, azimuthT, elevationT)
""" create missile objects """
missile = Dof3Dynamics()
# set position and velocity of missile
posM = [0, 0, 0]
v_magM = 400
azimuthM = 30*np.pi/180
elevationM = 30*np.pi/180
impact_dir = np.array([0, 1, 0])
impact_dir = impact_dir / np.sqrt(impact_dir.dot(impact_dir))
vF = v_magM*impact_dir
rM0, vM0 = missile.reset(posM, v_magM, azimuthM, elevationM)
""" processing """
# save the results
pos_target = []
pos_missile = []
vel_missile = []
sp_missile = []
rel_distance = []
command = []
look_angle = []
run_time = []
for k in range(100000):
t = k*dt
print(f't= {t:.3f}')
""" seeker measurement """
OmL, LOS_hat, R, Vc = seeker_meas(rT0, vT0, rM0, vM0)
""" guidance law """
a_cmd = optg(3, rT0-rM0, vT0-vM0, Vc, R, t)
#a_cmd = optg_ivc(6, rT0 - rM0, vT0 - vM0, Vc, R, vF, vM0, t)
#a_cmd = tpng(3, OmL, Vc, LOS_hat)
#a_cmd = ppng(3, OmL, vM0)
""" missile dynamics """
rM, vM = missile.step(a_cmd, 'missile')
""" target dynamics """
a_cmd_target = np.array([0, 0, 0])
rT, vT = target.step(a_cmd_target, 'target')
""" collections """
pos_target.append(rT0)
pos_missile.append(rM0)
vel_missile.append(vM0)
sp_missile.append(np.sqrt(vM.dot(vM)))
rel_distance.append(R)
command.append(a_cmd)
run_time.append(t)
""" for the next step """
rM0 = rM
vM0 = vM
rT0 = rT
vT0 = vT
""" stop condition """
if R < 5:
break
print(f'simulation done at time: {t:.3f} sec, and R: {R:.3f} m')
# convert lists to numpy arrays
pos_target = np.array(pos_target)
pos_missile = np.array(pos_missile)
vel_missile = np.array(vel_missile)
rel_distance = np.array(rel_distance)
command = np.array(command)
run_time = np.array(run_time)
# plot target and missile positions
fig1, ax1 = plt.subplots(subplot_kw={'projection': '3d'})
ax1.plot(pos_target[:, 0]/1000, pos_target[:, 1]/1000, pos_target[:, 2]/1000, label='Target')
ax1.plot(pos_missile[:, 0]/1000, pos_missile[:, 1]/1000, pos_missile[:, 2]/1000, label='Missile')
ax1.set_xlabel('x (km)')
ax1.set_ylabel('y (km)')
ax1.set_zlabel('z (km)')
ax1.legend()
ax1.set_title('Target and Missile Positions')
# plot relative distance
fig2, ax2 = plt.subplots()
ax2.plot(run_time, rel_distance)
ax2.set_xlabel('Time [s]')
ax2.set_ylabel('Relative Distance [m]')
ax2.set_title('Relative Distance vs Time')
# plot control commands
fig3, ax3 = plt.subplots()
ax3.plot(run_time, command[:, 0], label='a_x')
ax3.plot(run_time, command[:, 1], label='a_y')
ax3.plot(run_time, command[:, 2], label='a_z')
ax3.set_xlabel('Time [s]')
ax3.set_ylabel('Control Commands (a_cmd)')
ax3.legend()
ax3.set_title('Control Commands a_cmd vs Time')
# plot missile velocity
fig4, ax4 = plt.subplots()
ax4.plot(run_time, vel_missile[:, 0], label='vx')
ax4.plot(run_time, vel_missile[:, 1], label='vy')
ax4.plot(run_time, vel_missile[:, 2], label='vz')
ax4.set_xlabel('Time [s]')
ax4.set_ylabel('missile velocity (m/s)')
ax4.legend()
ax4.set_title('Missile Velocity')
# plot missile speed
fig5, ax5 = plt.subplots()
ax5.plot(run_time, sp_missile)
ax5.set_xlabel('Time [s]')
ax5.set_ylabel('missile velocity (m/s)')
ax5.set_title('Missile Velocity')
# display all plots
plt.show()
'유도항법제어 > 유도항법' 카테고리의 다른 글
[INS] 관성항법시스템 오차 방정식 (INS Error Equations) (0) | 2024.03.15 |
---|---|
[INS] 관성항법 방정식 (Kinematics of Inertial Navigation) (0) | 2024.03.03 |
최적유도법칙 (Optimal Guidance Law): 최종 속도 미설정 (0) | 2023.09.17 |
최적 유도법칙 (Optimal Guidance Law): 최종 속도 설정 (0) | 2023.09.16 |
비례항법유도 (Proportional Navigation Guidance) (0) | 2023.03.11 |
댓글