【第1章:力学系の記述と制御の基礎】
| 節 | 内容 |
|---|---|
| 1.1 | ニュートン力学・質点系・剛体運動の基礎(制御対象の物理モデル) |
| 1.2 | 解析力学の視点:ラグランジュの定式化と制約系の記述 |
| 1.3 | システム制御工学の概要:制御対象・センサ・アクチュエータ・フィードバック |
| 1.4 | 制御対象の運動方程式と状態空間モデルの導出(1次・2次遅れ系) |
import numpy as np
import matplotlib.pyplot as plt
# -------------------- Parameters --------------------
t = np.linspace(0, 10, 500)
m = 1.0 # 質量 [kg]
k = 1.0 # ばね定数 [N/m]
b = 0.5 # 減衰係数 [Ns/m]
F = 1.0 # 入力(外力) [N]
tau = 2.0 # 一次遅れの時定数
K = 1.0 # ゲイン
# -------------------- 1.1 ニュートン力学(質点+ばね+ダンパ) --------------------
# 2次遅れ系: m*x'' + b*x' + k*x = F
# 状態空間モデル(解析解の代わりに数値解)
omega_n = np.sqrt(k / m)
zeta = b / (2 * np.sqrt(k * m))
x1 = F / k * (1 - np.exp(-zeta * omega_n * t) * (
np.cos(omega_n * np.sqrt(1 - zeta**2) * t) +
(zeta / np.sqrt(1 - zeta**2)) * np.sin(omega_n * np.sqrt(1 - zeta**2) * t)
))
# -------------------- 1.2 ラグランジュ定式化と同じ結果 --------------------
# L = T - V = (1/2)m*x'^2 - (1/2)k*x^2 → Euler-Lagrange equation → 同じ方程式
# -------------------- 1.3 一次遅れ系(1st-order lag) --------------------
# tau * y' + y = Ku
y_lag = K * F * (1 - np.exp(-t / tau))
# -------------------- Plot --------------------
fig, ax = plt.subplots(1, 2, figsize=(14, 5))
# 1.1 & 1.2: 二階系(ばね-ダンパ系)
ax[0].plot(t, x1, label="Mass-Spring-Damper (2nd order)")
ax[0].set_title("1.1 / 1.2: Newtonian & Lagrangian Mechanics")
ax[0].set_xlabel("Time [s]")
ax[0].set_ylabel("Displacement x(t) [m]")
ax[0].legend()
ax[0].grid(True)
# 1.3: 一次遅れ系
ax[1].plot(t, y_lag, label="1st-order Lag System", color='orange')
ax[1].set_title("1.3: First-order Lag Control System")
ax[1].set_xlabel("Time [s]")
ax[1].set_ylabel("Output y(t)")
ax[1].legend()
ax[1].grid(True)
plt.tight_layout()
plt.show()
【第2章:ラグランジアン系と状態空間】
| 節 | 内容 |
|---|---|
| 2.1 | ラグランジアンの定義とオイラー=ラグランジュ方程式の導出 |
| 2.2 | 状態変数としての $q, \dot{q}$ と制御可能性 |
| 2.3 | 電気回路・バネ質量ダンパ系のラグランジュ定式化 |
| 2.4 | 1自由度・多自由度系の状態空間表現との一致と変換 |
import numpy as np
import matplotlib.pyplot as plt
# -------------------- 時間変数とパラメータ --------------------
t = np.linspace(0, 10, 500)
m = 1.0 # mass [kg]
k = 2.0 # spring constant [N/m]
b = 0.5 # damping coefficient [Ns/m]
F = 1.0 # external force [N]
# -------------------- 2.1: ラグランジアン定義と導出(理論) --------------------
# L = T - V = (1/2)*m*q_dot^2 - (1/2)*k*q^2
# → Euler-Lagrange: d/dt(∂L/∂q_dot) - ∂L/∂q = -b*q_dot + F (非保存項加えると制御可能)
# -------------------- 2.2: 状態変数としての q, q_dot --------------------
# 定義: x1 = q, x2 = q_dot とすると状態空間系:
# dx1/dt = x2
# dx2/dt = (1/m)(-k*x1 - b*x2 + F)
# -------------------- 2.3: バネ質量ダンパ系の数値解 --------------------
omega_n = np.sqrt(k / m)
zeta = b / (2 * np.sqrt(k * m))
omega_d = omega_n * np.sqrt(1 - zeta**2)
# 応答(ゼロ初期条件、定常応答まで含む)
x1 = F / k * (1 - np.exp(-zeta * omega_n * t) * (
np.cos(omega_d * t) + (zeta / np.sqrt(1 - zeta**2)) * np.sin(omega_d * t)
))
x2 = np.gradient(x1, t) # x1の時間微分(速度)
# -------------------- 2.4: 状態空間ベクトルと一致 --------------------
# x = [x1, x2] として、状態空間の軌道描写
# -------------------- プロット --------------------
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# x1: 位置(q)
axes[0].plot(t, x1, label="Position $q(t)$")
axes[0].set_title("2.1–2.3: Position from Lagrangian")
axes[0].set_xlabel("Time [s]")
axes[0].set_ylabel("q(t)")
axes[0].legend()
axes[0].grid(True)
# x2: 速度(q_dot)
axes[1].plot(t, x2, label="Velocity $\dot{q}(t)$", color="orange")
axes[1].set_title("2.2: Generalized Velocity")
axes[1].set_xlabel("Time [s]")
axes[1].set_ylabel("dq/dt")
axes[1].legend()
axes[1].grid(True)
# 状態空間軌道(q vs q_dot)
axes[2].plot(x1, x2, label="Phase Space (q, dq/dt)", color="green")
axes[2].set_title("2.4: State-Space Orbit")
axes[2].set_xlabel("q(t)")
axes[2].set_ylabel("dq/dt")
axes[2].legend()
axes[2].grid(True)
plt.tight_layout()
plt.show()
【第3章:ハミルトン形式とエネルギー制御】
| 節 | 内容 |
|---|---|
| 3.1 | ハミルトニアンの定義と正準方程式 |
| 3.2 | エネルギー保存系と非保存系(制御系)との関係 |
| 3.3 | シンプレクティック構造と制御系の位相空間解析 |
| 3.4 | ハミルトン構造に基づく力学系の可観測性と安定性解析 |
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
from scipy.linalg import solve_continuous_are, eigvals
# -------------------- 基本パラメータ --------------------
m = 1.0
k = 1.0
b = 0.5
F = 1.0
A = np.array([[0, 1/m], [-k, 0]])
B = np.array([[0], [1]])
C = np.array([[1, 0]])
Q = np.eye(2)
R = np.array([[1]])
# -------------------- Hamiltonian 系(保存系) --------------------
def hamiltonian_dynamics(t, z):
q, p = z
dqdt = p / m
dpdt = -k * q
return [dqdt, dpdt]
z0 = [1.0, 0.0]
t_eval = np.linspace(0, 10, 500)
sol_H = solve_ivp(hamiltonian_dynamics, [0, 10], z0, t_eval=t_eval)
q_H, p_H = sol_H.y
H_vals = p_H**2 / (2 * m) + 0.5 * k * q_H**2
# -------------------- LQR 最適制御系 --------------------
P = solve_continuous_are(A, B, Q, R)
K = np.linalg.inv(R) @ B.T @ P
def lqr_dynamics(t, x):
u = -K @ x
dxdt = A @ x + B @ u
return dxdt.flatten()
x0 = np.array([1.0, 0.0])
sol_LQR = solve_ivp(lqr_dynamics, [0, 10], x0, t_eval=t_eval)
x1_LQR, x2_LQR = sol_LQR.y
u_LQR = -K @ sol_LQR.y
# -------------------- 可観測性と安定性解析 --------------------
O = np.vstack([C, C @ A])
rank_O = np.linalg.matrix_rank(O)
eigenvals_A = eigvals(A)
# -------------------- 合体プロット --------------------
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
# 1. ハミルトン系の時間発展
axes[0, 0].plot(t_eval, q_H, label="q(t)")
axes[0, 0].plot(t_eval, p_H, label="p(t)")
axes[0, 0].set_title("Hamiltonian Dynamics (q, p)")
axes[0, 0].set_xlabel("Time [s]")
axes[0, 0].set_ylabel("State")
axes[0, 0].legend()
axes[0, 0].grid(True)
# 2. エネルギー保存
axes[0, 1].plot(t_eval, H_vals, label="H(t)", color="orange")
axes[0, 1].set_title("Hamiltonian Energy")
axes[0, 1].set_xlabel("Time [s]")
axes[0, 1].set_ylabel("Energy")
axes[0, 1].legend()
axes[0, 1].grid(True)
# 3. 位相空間(q, p)
axes[0, 2].plot(q_H, p_H, label="Phase Space", color="green")
axes[0, 2].set_title("Hamiltonian Phase Space")
axes[0, 2].set_xlabel("q")
axes[0, 2].set_ylabel("p")
axes[0, 2].legend()
axes[0, 2].grid(True)
# 4. LQR 軌道
axes[1, 0].plot(t_eval, x1_LQR, label="x1(t)")
axes[1, 0].plot(t_eval, x2_LQR, label="x2(t)")
axes[1, 0].set_title("LQR Optimal State Trajectory")
axes[1, 0].set_xlabel("Time [s]")
axes[1, 0].set_ylabel("State")
axes[1, 0].legend()
axes[1, 0].grid(True)
# 5. LQR 制御入力
axes[1, 1].plot(t_eval, u_LQR[0], label="u(t)", color="purple")
axes[1, 1].set_title("LQR Control Input")
axes[1, 1].set_xlabel("Time [s]")
axes[1, 1].set_ylabel("Control")
axes[1, 1].legend()
axes[1, 1].grid(True)
# 6. 固有値解析(安定性と可観測性)
axes[1, 2].axhline(0, color='gray', linestyle='--')
axes[1, 2].plot(np.real(eigenvals_A), np.imag(eigenvals_A), 'ro', label='Eigenvalues')
axes[1, 2].set_title(f"Eigenvalue Analysis\nObservability Rank = {rank_O}")
axes[1, 2].set_xlabel("Real Part")
axes[1, 2].set_ylabel("Imaginary Part")
axes[1, 2].legend()
axes[1, 2].grid(True)
plt.tight_layout()
plt.show()
【第4章:変分原理と最適制御】
| 節 | 内容 |
|---|---|
| 4.1 | 変分法による制御問題の定式化(最小作用・最小エネルギー) |
| 4.2 | ポンテャギンの最大原理とハミルトン=ヤコビ=ベルマン(HJB)方程式 |
| 4.3 | LQR(線形二次レギュレータ)と作用汎関数の関係 |
| 4.4 | スタートアップ時間最小・燃料最小の最適制御軌道の解析例 |
# 修正: np.gradient の第2引数 t_eval は1次元の長さが一致する必要があるため省略し、デフォルト間隔で使用
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
from scipy.linalg import solve_continuous_are
# -------------------- 基本パラメータ --------------------
A = np.array([[0, 1], [0, 0]])
B = np.array([[0], [1]])
Q = np.eye(2)
R = np.array([[1]])
x0 = np.array([1.0, 0.0])
t_eval = np.linspace(0, 10, 500)
# -------------------- 4.1: LQR最適制御問題(最小エネルギー) --------------------
P = solve_continuous_are(A, B, Q, R)
K = np.linalg.inv(R) @ B.T @ P
def lqr_dynamics(t, x):
u = -K @ x
return (A @ x + B @ u).flatten()
sol_lqr = solve_ivp(lqr_dynamics, [0, 10], x0, t_eval=t_eval)
x1_lqr, x2_lqr = sol_lqr.y
u_lqr = -K @ sol_lqr.y
# -------------------- 4.2: HJBに対応する値関数とその時間微分 --------------------
V = np.einsum('ij,jk,ik->i', sol_lqr.y.T, P, sol_lqr.y.T) # V(x) = xᵀPx
Vdot = np.gradient(V) # 修正済み
# -------------------- 4.4: Bang-bang制御(最短時間) --------------------
umax = 1.0
def bang_bang_dynamics(t, x):
u = -umax if x[0] > 0 else umax
return (A @ x + B.flatten() * u)
sol_bb = solve_ivp(bang_bang_dynamics, [0, 10], x0, t_eval=t_eval)
x1_bb, x2_bb = sol_bb.y
u_bb = np.where(x1_bb > 0, -umax, umax)
# -------------------- プロット --------------------
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
axes[0, 0].plot(t_eval, x1_lqr, label="x1(t)")
axes[0, 0].plot(t_eval, x2_lqr, label="x2(t)")
axes[0, 0].set_title("4.1 LQR: Minimum Energy Trajectory")
axes[0, 0].set_xlabel("Time [s]")
axes[0, 0].legend()
axes[0, 0].grid(True)
axes[0, 1].plot(t_eval, u_lqr[0], label="u(t)", color='orange')
axes[0, 1].set_title("4.1 LQR: Optimal Control Input")
axes[0, 1].set_xlabel("Time [s]")
axes[0, 1].legend()
axes[0, 1].grid(True)
axes[0, 2].plot(t_eval, V, label="Value Function V(x)", color='green')
axes[0, 2].plot(t_eval, Vdot, label="dV/dt", linestyle='--', color='red')
axes[0, 2].set_title("4.2 HJB: Value Function & dV/dt")
axes[0, 2].set_xlabel("Time [s]")
axes[0, 2].legend()
axes[0, 2].grid(True)
axes[1, 0].plot(t_eval, x1_bb, label="x1(t)")
axes[1, 0].plot(t_eval, x2_bb, label="x2(t)")
axes[1, 0].set_title("4.4 Bang-Bang: Min-Time Trajectory")
axes[1, 0].set_xlabel("Time [s]")
axes[1, 0].legend()
axes[1, 0].grid(True)
axes[1, 1].plot(t_eval, u_bb, label="u(t)", color='purple')
axes[1, 1].set_title("4.4 Bang-Bang: Control Input")
axes[1, 1].set_xlabel("Time [s]")
axes[1, 1].legend()
axes[1, 1].grid(True)
axes[1, 2].plot(x1_lqr, x2_lqr, label="LQR", color='blue')
axes[1, 2].plot(x1_bb, x2_bb, label="Bang-Bang", color='red', linestyle='--')
axes[1, 2].set_title("4.3–4.4: Phase Space Comparison")
axes[1, 2].set_xlabel("x1")
axes[1, 2].set_ylabel("x2")
axes[1, 2].legend()
axes[1, 2].grid(True)
plt.tight_layout()
plt.show()
【第5章:制御系の安定性と力学的視点】
| 節 | 内容 |
|---|---|
| 5.1 | Lyapunov関数とラグランジアンとの関係 |
| 5.2 | 受動性(Passivity)とエネルギー関数による安定性 |
| 5.3 | ハミルトン系の安定解析とベクトル場の幾何構造 |
| 5.4 | モデル予測制御(MPC)におけるラグランジュ乗数の意味 |
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
# -------------------- 5.1 Lyapunov関数とラグランジアン --------------------
# 系: dq/dt = p/m, dp/dt = -kq(保存系)
m = 1.0
k = 1.0
def hamiltonian_dynamics(t, z):
q, p = z
dqdt = p / m
dpdt = -k * q
return [dqdt, dpdt]
# Lyapunov関数としてのエネルギー(L = T - V)
def energy_function(q, p):
return p**2 / (2 * m) + 0.5 * k * q**2
# -------------------- 5.2 Passivity 系(減衰あり) --------------------
# dq/dt = p/m, dp/dt = -kq - b*p(非保存系)
b = 0.2
def passive_dynamics(t, z):
q, p = z
dqdt = p / m
dpdt = -k * q - b * p
return [dqdt, dpdt]
# -------------------- 5.3 ハミルトンベクトル場と位相軌道 --------------------
z0 = [1.0, 0.0]
t_eval = np.linspace(0, 20, 800)
sol_conservative = solve_ivp(hamiltonian_dynamics, [0, 20], z0, t_eval=t_eval)
sol_dissipative = solve_ivp(passive_dynamics, [0, 20], z0, t_eval=t_eval)
q_cons, p_cons = sol_conservative.y
q_diss, p_diss = sol_dissipative.y
H_cons = energy_function(q_cons, p_cons)
H_diss = energy_function(q_diss, p_diss)
# -------------------- 5.4 MPCとラグランジュ乗数の直感(図示) --------------------
# 最適解が制約に張り付く場合のラグランジュ乗数の意味(速度制約 u in [-1, 1])
u_max = 1.0
def mpc_dynamics(t, x):
u = -u_max if x[0] > 0 else u_max
return [x[1], u]
x0 = [1.0, 0.0]
sol_mpc = solve_ivp(mpc_dynamics, [0, 10], x0, t_eval=np.linspace(0, 10, 500))
x1_mpc, x2_mpc = sol_mpc.y
u_mpc = np.where(x1_mpc > 0, -u_max, u_max)
lagrange_multiplier = np.abs(u_mpc) # 速度制約が有効な区間では乗数≠0
# -------------------- Plot --------------------
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
# Conserved vs Dissipated Energy
axes[0, 0].plot(t_eval, H_cons, label="Conservative")
axes[0, 0].plot(t_eval, H_diss, label="Dissipative", linestyle='--')
axes[0, 0].set_title("5.1–5.2: Energy as Lyapunov / Passivity")
axes[0, 0].set_xlabel("Time [s]")
axes[0, 0].set_ylabel("Energy")
axes[0, 0].legend()
axes[0, 0].grid(True)
# Phase Space Comparison
axes[0, 1].plot(q_cons, p_cons, label="Conservative")
axes[0, 1].plot(q_diss, p_diss, label="Dissipative", linestyle='--')
axes[0, 1].set_title("5.3: Phase Space Structure")
axes[0, 1].set_xlabel("q")
axes[0, 1].set_ylabel("p")
axes[0, 1].legend()
axes[0, 1].grid(True)
# Vector Field of Conservative System
Q, P = np.meshgrid(np.linspace(-2, 2, 20), np.linspace(-2, 2, 20))
dQ = P / m
dP = -k * Q
axes[0, 2].quiver(Q, P, dQ, dP)
axes[0, 2].set_title("5.3: Hamiltonian Vector Field")
axes[0, 2].set_xlabel("q")
axes[0, 2].set_ylabel("p")
axes[0, 2].grid(True)
# MPC States
axes[1, 0].plot(sol_mpc.t, x1_mpc, label="x1(t)")
axes[1, 0].plot(sol_mpc.t, x2_mpc, label="x2(t)")
axes[1, 0].set_title("5.4 MPC: State Trajectory")
axes[1, 0].set_xlabel("Time [s]")
axes[1, 0].legend()
axes[1, 0].grid(True)
# MPC Control Input
axes[1, 1].plot(sol_mpc.t, u_mpc, label="u(t)", color='purple')
axes[1, 1].set_title("5.4 MPC: Control Input")
axes[1, 1].set_xlabel("Time [s]")
axes[1, 1].legend()
axes[1, 1].grid(True)
# Lagrange Multiplier Intuition
axes[1, 2].plot(sol_mpc.t, lagrange_multiplier, label="|λ(t)| (Active Constraint)", color='green')
axes[1, 2].set_title("5.4: Lagrange Multiplier Activity")
axes[1, 2].set_xlabel("Time [s]")
axes[1, 2].legend()
axes[1, 2].grid(True)
plt.tight_layout()
plt.show()