Source code for simulator.plant

"""連続時間 物理環境モデル (Plant)

衛星の力学系・センサ応答をシミュレートする。
制御ロジックとは独立し、外部から与えられたサーボパルス指令に応じて状態を更新する。
"""

import math
import random


from .environment import Environment


[docs] class SatellitePlant: """衛星の物理モデル""" # 物理パラメータ MOMENT_OF_INERTIA = 0.002 # 衛星本体の慣性モーメント [kg·m²] RW_INERTIA = 0.0002 # リアクションホイールの慣性モーメント [kg·m²] SERVO_TORQUE_GAIN = 5e-6 # サーボパルス偏差 → RWトルク [N·m/μs] FRICTION = 0.008 # 軸受摩擦係数 [N·m·s/rad](減衰時定数 ≈ 0.25s) SERVO_CENTER = 1520 # サーボセンターパルス [μs] def __init__(self): self.env = Environment() self.angular_velocity: float = 0.0 # 衛星の角速度 [rad/s] self.rw_speed: float = 0.0 # RW回転速度 [rad/s] # 太陽方向のドリフト(軌道上で太陽方向がゆっくり変わる模擬) self.sun_drift_rate: float = 0.01 # [rad/s] @property def sun_angle(self) -> float: """太陽方向に対する衛星の角度 [rad] (Environmentから計算)""" return self.env.get_sun_angle_in_body()
[docs] def reset(self): """状態を初期化する""" self.env.set_sat_rotation(0.0) self.angular_velocity = 0.0 self.rw_speed = 0.0
[docs] def step(self, dt: float, servo_pulse_us: int): """物理モデルを dt [s] だけ進める Args: dt: タイムステップ [s] servo_pulse_us: サーボパルス指令 [μs] """ pulse_delta = servo_pulse_us - self.SERVO_CENTER # RWへのトルク rw_torque = pulse_delta * self.SERVO_TORQUE_GAIN # RW角加速度 rw_alpha = rw_torque / self.RW_INERTIA # 反作用トルクが衛星本体に作用(角運動量保存) sat_torque = -rw_torque - self.FRICTION * self.angular_velocity # 衛星角加速度 sat_alpha = sat_torque / self.MOMENT_OF_INERTIA # オイラー積分 self.angular_velocity += sat_alpha * dt self.rw_speed += rw_alpha * dt # 衛星の絶対姿勢を更新 (ドリフト込み) new_rotation = self.env.sat_rotation + (self.angular_velocity + self.sun_drift_rate) * dt self.env.set_sat_rotation(new_rotation) # 環境の状態を更新 (公転・彗星の移動など) self.env.step(dt)
[docs] def get_sensor_readings(self) -> dict: """現在の状態からセンサ値を生成(ADCノイズ含む)""" angle = self.sun_angle # フォトダイオード電圧(4ch、配置角度に依存) photo_diode_volt = [ max(0.0, 1.5 - math.cos(angle + math.radians(135)) + random.gauss(0, 0.02)), max(0.0, 1.5 - math.cos(angle + math.radians(45)) + random.gauss(0, 0.02)), max(0.0, 1.5 + math.cos(angle + math.radians(135)) + random.gauss(0, 0.02)), max(0.0, 1.5 + math.cos(angle + math.radians(45)) + random.gauss(0, 0.02)), ] # フォトリフレクタ(RW回転検出) photo_reflector_volt = 1.0 + 0.5 * math.sin(self.rw_speed * 0.1) + random.gauss(0, 0.05) # 電源電圧 battery_volt = 6.5 + random.gauss(0, 0.05) return { "photo_diode_volt": photo_diode_volt, "photo_reflector_volt": photo_reflector_volt, "battery_volt": battery_volt, "sun_angle_rad": angle, "angular_velocity_rad_per_s": self.angular_velocity, "rw_speed_rad_per_s": self.rw_speed, }