← Back to Blog

[Convex Optimization] Satellite Orbit Stabilization with Minimum Energy Control

math > convex optimization

2026-07-043 min read

#math #convex-optimization #control

시뮬레이션 목적

최적화 공부하며 진행한 프로젝트로 간단하게 실험 환경을 구성하였다. 지구 궤도 공전 인공위성 제어 시뮬레이션으로 이체 문제(Two-body problem)에서 최소 에너지(Minimum energy)로 제어하도록 설계하였다.

위성의 운동 방정식 (Newton's Second Law)

위성의 운동은 뉴턴 제2법칙기반으로 개발하였다.

F=ma\vec{F} = m \cdot \vec{a}

여기서:

이를 위성의 위치와 속도로 표현하면 다음과 같다: [ \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)

위성 중심으로 작용하는 중력은 아래 식으로 계산된다.

agravity=GMr3r\vec{a}_{\text{gravity}} = -\frac{G M}{r^3} \vec{r}

필자는 변수 값을 다음과 같이 설정하였다:

ax_gravity = -G * M * x / r**3
ay_gravity = -G * M * y / r**3

제어 입력 (Control Input)

제어 입력 목표는 위성을 특정 궤도에 고정하고 속도 유지하는 것이다. 이를 위해 위치 제어속도 제어를 사용한다.

(1) 위치 제어

위성이 목표 궤도 반지름(rtargetr_{\text{target}})에서 벗어날 경우, 위치 오차 기반으로 제어 입력 생성한다.

aposition=kp(rtargetr)rr\vec{a}_{\text{position}} = k_p \cdot (r_{\text{target}} - r) \cdot \frac{\vec{r}}{r}
ux_position = k_p * (r_target - r) * (x / r)
uy_position = k_p * (r_target - r) * (y / r)

(2) 속도 제어

속도 제어는 현재 속도와 목표 속도 차이를 줄인다. 목표 속도는 원 궤도 유지하기 위해 필요하며 아래 식으로 정의된다.

vcircular=GMrtargetv_{\text{circular}} = \sqrt{\frac{G M}{r_{\text{target}}}}

원 궤도에서 이상적인 속도는 위와 같다. 이를 바탕으로 속도 제어 입력은 아래와 같이 계산된다. [ \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)

외란은 위성 제어 시스템에 영향을 미치는 비선형 요인이다. 제어 모델이 정상 작동 확인을 목적으로 구성하였다. 여기서는 필자는 두 가지 외란을 모델링하였다.

arandom=cN(0,1)\vec{a}_{\text{random}} = c \cdot \mathcal{N}(0, 1)

N(0,1)\mathcal{N}(0, 1)은 평균이 00이고 표준 편차가 11가우시안 난수(Norm distribution)이다.

aperiodic=c[sin(2πtT)cos(2πtT)]\vec{a}_{\text{periodic}} = c \cdot \begin{bmatrix} \sin\left(\frac{2 \pi t}{T}\right) \\ \cos\left(\frac{2 \pi t}{T}\right) \end{bmatrix}

총 외란은 아래와 같다. [ \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)

합력(合力)을 계산한 동역학 방정식은 다음과 같다.

[x˙ y˙ vx˙ vy˙]=[vx vy ax ay]\begin{bmatrix} \dot{x} \ \dot{y} \ \dot{v_x} \ \dot{v_y} \end{bmatrix} = \begin{bmatrix} v_x \ v_y \ a_x \ a_y \end{bmatrix}

여기서 axa_xaya_y는 다음과 같이 정의된다.

ax=agravity,x+acontrol,x+adisturbance,xa_x = a_{\text{gravity},x} + a_{\text{control},x} + a_{\text{disturbance},x} ay=agravity,y+acontrol,y+adisturbance,ya_y = a_{\text{gravity},y} + a_{\text{control},y} + a_{\text{disturbance},y}
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()

image-1