시뮬레이션 목적
최적화 공부하며 진행한 프로젝트로 간단하게 실험 환경을 구성하였다. 지구 궤도 공전 인공위성 제어 시뮬레이션으로 이체 문제(Two-body problem)에서 최소 에너지(Minimum energy)로 제어하도록 설계하였다.
- 위성 궤도 유지 제어 위성을 지정된 궤도(500 km 고도)에서 유지하기 위해 설계된 제어 알고리즘 설계한다.
- 제어 입력 최소화(Minimum energy) 에너지 효율적인 방식으로 위성을 안정적으로 제어하도록 설계한다.
- 외란 환경에서 안정성 검증 외란(난수 외란 및 주기 외란)이 추가된 상황에서도 제어 알고리즘이 궤도 유지하도록 설계한다.
위성의 운동 방정식 (Newton's Second Law)
위성의 운동은 뉴턴 제2법칙기반으로 개발하였다.
여기서:
- 는 위성에 작용하는 모든 힘의 합(중력, 제어 입력, 외란)이다.
- 은 위성의 질량이다.
- 는 위성의 가속도이다.
이를 위성의 위치와 속도로 표현하면 다음과 같다: [ \vec{a} = \frac{\vec{F}}{m} = \vec{a}{\text{gravity}} + \vec{a}{\text{control}} + \vec{a}_{\text{disturbance}} ]
위성의 가속도는 중력, 제어 입력, 외란에 의해 결정된다.
def orbital_dynamics(t, X):
x, y, vx, vy = X
r = np.sqrt(x**2 + y**2)
# Gravitational acceleration
ax_gravity = -G * M * x / r**3
ay_gravity = -G * M * y / r**3
# Control input
ux, uy = control_input(t, X)
# Disturbance (random and periodic)
random_disturbance = 100e-4 * np.random.randn(2)
periodic_disturbance = 100e-4 * np.array([
np.sin(2 * np.pi * t / 1000),
np.cos(2 * np.pi * t / 1000)
])
disturbance = random_disturbance + periodic_disturbance
# Total acceleration
ax = ax_gravity + ux + disturbance[0]
ay = ay_gravity + uy + disturbance[1]
return [vx, vy, ax, ay]
중력 가속도 (Gravitational Acceleration)
위성 중심으로 작용하는 중력은 아래 식으로 계산된다.
필자는 변수 값을 다음과 같이 설정하였다:
- : 중력 상수 ()이다.
- : 지구 질량 ()이다.
- : 위성 중심에서 지구 중심까지 거리 ()이다.
- : 위성 위치 벡터 ()이다.
ax_gravity = -G * M * x / r**3
ay_gravity = -G * M * y / r**3
제어 입력 (Control Input)
제어 입력 목표는 위성을 특정 궤도에 고정하고 속도 유지하는 것이다. 이를 위해 위치 제어와 속도 제어를 사용한다.
(1) 위치 제어
위성이 목표 궤도 반지름()에서 벗어날 경우, 위치 오차 기반으로 제어 입력 생성한다.
- : 위치 제어 게인이다.
- : 목표 반지름과 현재 반지름의 차이이다.
- : 단위 방향 벡터(중심 방향)이다.
ux_position = k_p * (r_target - r) * (x / r)
uy_position = k_p * (r_target - r) * (y / r)
(2) 속도 제어
속도 제어는 현재 속도와 목표 속도 차이를 줄인다. 목표 속도는 원 궤도 유지하기 위해 필요하며 아래 식으로 정의된다.
원 궤도에서 이상적인 속도는 위와 같다. 이를 바탕으로 속도 제어 입력은 아래와 같이 계산된다. [ \vec{a}{\text{velocity}} = k_v \cdot \left( \vec{v}{\text{desired}} - \vec{v} \right) ]
- : 속도 제어 게인이다.
- : 목표 속도와 현재 속도 차이이다.
vx_desired = -v_circular * (y / r_target)
vy_desired = v_circular * (x / r_target)
ux_velocity = k_v * (vx_desired - vx)
uy_velocity = k_v * (vy_desired - vy)
(3) 제어 입력의 합
최종 제어 입력은 위치 제어, 속도 제어, 중력 보정을 포함한다. [ \vec{a}{\text{control}} = \vec{a}{\text{position}} + \vec{a}{\text{velocity}} - \vec{a}{\text{gravity}} ] 여기서 중력 보정은 제어 입력이 중력을 상쇄한다.
ux = ux_position + ux_velocity - ax_gravity
uy = uy_position + uy_velocity - ay_gravity
return np.array([ux, uy])
외란 (Disturbance)
외란은 위성 제어 시스템에 영향을 미치는 비선형 요인이다. 제어 모델이 정상 작동 확인을 목적으로 구성하였다. 여기서는 필자는 두 가지 외란을 모델링하였다.
- 난수 외란:
은 평균이 이고 표준 편차가 인 가우시안 난수(Norm distribution)이다.
- 주기 외란:
- 이는 주기적인 진동을 모델링한 것이다.
총 외란은 아래와 같다. [ \vec{a}{\text{disturbance}} = \vec{a}{\text{random}} + \vec{a}_{\text{periodic}} ]
random_disturbance = 100e-4 * np.random.randn(2)
periodic_disturbance = 100e-4 * np.array([
np.sin(2 * np.pi * t / 1000),
np.cos(2 * np.pi * t / 1000)
])
disturbance = random_disturbance + periodic_disturbance
동역학 방정식 (Orbital Dynamics)
합력(合力)을 계산한 동역학 방정식은 다음과 같다.
여기서 와 는 다음과 같이 정의된다.
def orbital_dynamics(t, X):
x, y, vx, vy = X
r = np.sqrt(x**2 + y**2)
ax_gravity = -G * M * x / r**3
ay_gravity = -G * M * y / r**3
ux, uy = control_input(t, X)
random_disturbance = 100e-4 * np.random.randn(2)
periodic_disturbance = 100e-4 * np.array([
np.sin(2 * np.pi * t / 1000),
np.cos(2 * np.pi * t / 1000)
])
disturbance = random_disturbance + periodic_disturbance
ax = ax_gravity + ux + disturbance[0]
ay = ay_gravity + uy + disturbance[1]
return [vx, vy, ax, ay]
전체 코드
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
# Constants
G = 6.67430e-11 # Gravitational constant (m^3 kg^-1 s^-2)
M = 5.972e24 # Mass of the Earth (kg)
R = 6.371e6 # Earth's radius (m)
m = 500 # Satellite mass (kg)
# Initial conditions
r0 = R + 500e3 # Initial altitude (500 km above Earth's surface)
v0 = 7.8e3 # Approximate orbital velocity (m/s)
x0, y0 = r0, 0 # Initial position (on the x-axis)
vx0, vy0 = 0, v0 # Initial velocity (perpendicular to position)
# State vector: [x, y, vx, vy]
X0 = [x0, y0, vx0, vy0]
# Define control input function
def control_input(t, X):
"""
Compute the control input with position and velocity correction.
Args:
t (float): Current time.
X (array): Current state vector [x, y, vx, vy].
Returns:
array: Control acceleration [ux, uy].
"""
x, y, vx, vy = X
r = np.sqrt(x**2 + y**2)
# Target orbit parameters
r_target = R + 500e3 # Target altitude (500 km above surface)
v_circular = np.sqrt(G * M / r_target) # Circular velocity at target orbit
vx_desired = -v_circular * (y / r_target)
vy_desired = v_circular * (x / r_target)
# Control gains
k_p = 0.01 # Position control gain
k_v = 0.1 # Velocity control gain
# Position control
ux_position = k_p * (r_target - r) * (x / r)
uy_position = k_p * (r_target - r) * (y / r)
# Velocity control
ux_velocity = k_v * (vx_desired - vx)
uy_velocity = k_v * (vy_desired - vy)
# Gravitational compensation
ax_gravity = -G * M * x / r**3
ay_gravity = -G * M * y / r**3
# Total control input
ux = ux_position + ux_velocity - ax_gravity
uy = uy_position + uy_velocity - ay_gravity
return np.array([ux, uy])
# Define orbital dynamics with control and disturbance
def orbital_dynamics(t, X):
"""
Orbital dynamics with control input and disturbance.
Args:
t (float): Current time.
X (array): Current state vector [x, y, vx, vy].
Returns:
array: Time derivative of the state vector [vx, vy, ax, ay].
"""
x, y, vx, vy = X
r = np.sqrt(x**2 + y**2)
# Gravitational acceleration
ax_gravity = -G * M * x / r**3
ay_gravity = -G * M * y / r**3
# Control input
ux, uy = control_input(t, X)
# Disturbance (random and periodic)
random_disturbance = 100e-4 * np.random.randn(2) # Small random disturbance
periodic_disturbance = 100e-4 * np.array([np.sin(2 * np.pi * t / 1000), np.cos(2 * np.pi * t / 1000)]) # Periodic disturbance
disturbance = random_disturbance + periodic_disturbance
# Total acceleration
ax = ax_gravity + ux + disturbance[0]
ay = ay_gravity + uy + disturbance[1]
return [vx, vy, ax, ay]
# Solve the dynamics
T = 100000 # Simulation time in seconds
t_eval = np.linspace(0, T, 1000) # Time points
sol = solve_ivp(orbital_dynamics, [0, T], X0, t_eval=t_eval, rtol=1e-8, atol=1e-8)
# Extract results
x, y = sol.y[0], sol.y[1]
vx, vy = sol.y[2], sol.y[3]
control_inputs = np.array([control_input(t, sol.y[:, i]) for i, t in enumerate(sol.t)])
control_magnitudes = np.linalg.norm(control_inputs, axis=1) # Compute control magnitude
# Plot results
plt.figure(figsize=(12, 12))
# 1. Orbit plot
plt.subplot(3, 1, 1)
plt.plot(x / 1e3, y / 1e3, label="Orbit with Control and Disturbance", linewidth=2)
plt.plot(0, 0, 'o', label="Earth", color='blue', markersize=10)
plt.xlabel("X Position (km)")
plt.ylabel("Y Position (km)")
plt.title("Orbit with Minimum Energy Control and Disturbance")
plt.legend()
plt.axis('equal')
plt.grid()
# 2. Velocity plot
plt.subplot(3, 1, 2)
plt.plot(sol.t, vx, label="Vx (m/s)", color="orange", linewidth=2)
plt.plot(sol.t, vy, label="Vy (m/s)", color="green", linewidth=2)
plt.xlabel("Time (s)")
plt.ylabel("Velocity (m/s)")
plt.title("Velocity Over Time")
plt.legend()
plt.grid()
# 3. Control input plot
plt.subplot(3, 1, 3)
plt.plot(sol.t, control_magnitudes, label="Control Magnitude (m/s²)", color="red", linewidth=2)
plt.xlabel("Time (s)")
plt.ylabel("Control Magnitude (m/s²)")
plt.title("Control Input Over Time")
plt.legend()
plt.grid()
plt.tight_layout()
plt.show()
