base_agent/uas/model/params.py

361 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
装备参数基类 + 各域子类
─────────────────────────────────────────────────────────────────
EquipmentParams 公共参数基类dataclass
├── AircraftParams 飞行器专有参数
├── GroundVehicleParams 地面车辆专有参数
├── RobotDogParams 机器狗专有参数
└── RadarParams 雷达专有参数
所有参数类支持:
validate() → 校验合法性,返回错误列表
to_dict() → 序列化为字典
from_dict(d) → 从字典反序列化classmethod
copy_with(**kwargs) → 返回修改了指定字段的副本
─────────────────────────────────────────────────────────────────
"""
from __future__ import annotations
import copy
import dataclasses
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Type, TypeVar
from uas.model.constants import WAYPOINT_CAPTURE_RADIUS
from uas.model.sensor_noise import SensorNoiseParams
from uas.model.wind import WindField
T = TypeVar("T", bound="EquipmentParams")
# ─────────────────────────────────────────────────────────────────
# 装备类型枚举
# ─────────────────────────────────────────────────────────────────
class ModelType(Enum):
AIRCRAFT = "aircraft"
MANNED_AIRCRAFT = "manned_aircraft"
UAV = "controller"
GROUND_VEHICLE = "ground_vehicle"
ROBOT_DOG = "robot_dog"
RADAR = "radar"
CUSTOM = "custom"
# ─────────────────────────────────────────────────────────────────
# 公共参数基类
# ─────────────────────────────────────────────────────────────────
@dataclass
class EquipmentParams:
"""
装备公共参数基类
所有装备类型均包含这些字段。
子类通过新增 dataclass 字段扩展特有参数,无需修改基类。
"""
# ── 标识 ──────────────────────────────────────────────────────
equipment_id: str = field(
default_factory=lambda: str(uuid.uuid4())[:8]
)
name: str = "未命名装备"
model_type: ModelType = ModelType.CUSTOM
description: str = ""
# ── 物理参数 ──────────────────────────────────────────────────
weight_kg: float = 0.0 # 重量(千克)
# ── 运动参数 ──────────────────────────────────────────────────
max_speed: float = 0.0 # 最大速度m/s
min_speed: float = 0.0 # 最小速度m/s0=可静止)
acceleration: float = 0.0 # 最大加速度m/s²
deceleration: float = 0.0 # 最大减速度m/s²正值
# ── 扩展元数据 ────────────────────────────────────────────────
extra: Dict[str, Any] = field(default_factory=dict)
# ── 校验 ──────────────────────────────────────────────────────
def validate(self) -> List[str]:
"""校验参数合法性,返回错误信息列表(空=合法)"""
errors = []
if self.weight_kg < 0:
errors.append(f"weight_kg 不能为负: {self.weight_kg}")
if self.max_speed < 0:
errors.append(f"max_speed 不能为负: {self.max_speed}")
if self.min_speed < 0:
errors.append(f"min_speed 不能为负: {self.min_speed}")
if self.min_speed > self.max_speed and self.max_speed > 0:
errors.append(
f"min_speed({self.min_speed}) > max_speed({self.max_speed})"
)
if self.acceleration < 0:
errors.append(f"acceleration 不能为负: {self.acceleration}")
if self.deceleration < 0:
errors.append(f"deceleration 不能为负: {self.deceleration}")
return errors
# ── 序列化 ────────────────────────────────────────────────────
def to_dict(self) -> Dict[str, Any]:
"""序列化为字典(含枚举值转字符串)"""
d = dataclasses.asdict(self)
# 枚举转字符串
if "model_type" in d:
d["model_type"] = self.model_type.value
return d
@classmethod
def from_dict(cls: Type[T], d: Dict[str, Any]) -> T:
"""从字典反序列化"""
d = dict(d)
if "model_type" in d and isinstance(d["model_type"], str):
d["model_type"] = ModelType(d["model_type"])
# 过滤掉子类不认识的字段
valid_fields = {f.name for f in dataclasses.fields(cls)}
filtered = {k: v for k, v in d.items() if k in valid_fields}
return cls(**filtered)
def copy_with(self: T, **kwargs) -> T:
"""返回修改了指定字段的副本"""
obj = copy.copy(self)
for k, v in kwargs.items():
if hasattr(obj, k):
setattr(obj, k, v)
else:
raise AttributeError(
f"{self.__class__.__name__} 没有参数字段: {k}"
)
return obj
def get(self, key: str, default: Any = None) -> Any:
"""按字段名读取参数值"""
return getattr(self, key, default)
def set(self, key: str, value: Any) -> None:
"""按字段名设置参数值(运行时动态配置)"""
if not hasattr(self, key):
raise AttributeError(
f"{self.__class__.__name__} 没有参数字段: {key}"
)
setattr(self, key, value)
def __repr__(self) -> str:
return (f"{self.__class__.__name__}("
f"id={self.equipment_id}, "
f"name={self.name}, "
f"type={self.model_type.value})")
# ─────────────────────────────────────────────────────────────────
# 飞行器专有参数
# ─────────────────────────────────────────────────────────────────
@dataclass
class AircraftParams(EquipmentParams):
"""飞行器专有参数(兼容有人机 / 无人机)"""
model_type: ModelType = ModelType.AIRCRAFT
# ── 飞行包线 ──────────────────────────────────────────────────
max_altitude: float = 15000.0 # 最大飞行高度(米)
min_altitude: float = 0.0 # 最小飞行高度(米)
max_climb_rate: float = 50.0 # 最大爬升率m/s
max_descent_rate: float = 30.0 # 最大下降率m/s正值
# ── 机动参数 ──────────────────────────────────────────────────
min_turn_radius: float = 500.0 # 最小转弯半径(米)
max_bank_angle: float = 60.0 # 最大坡度角(度)
max_pitch_angle: float = 30.0 # 最大俯仰角(度)
# ── 燃油参数 ──────────────────────────────────────────────────
fuel_capacity: float = 5000.0 # 油箱容量(升/千克)
fuel_consumption_rate: float = 10.0 # 巡航油耗(升/秒)
# ── 载荷参数 ──────────────────────────────────────────────────
payload_capacity_kg: float = 0.0 # 最大载荷(千克)
wingspan_m: float = 0.0 # 翼展(米)
# -- 风向
wind_direction: float = 0.0
wind_speed: float = 0.0
wind_field: Optional[WindField] = WindField()
sensor_noise: Optional[SensorNoiseParams] = None
waypoint_capture: float = WAYPOINT_CAPTURE_RADIUS
seed: int = 0
def validate(self) -> List[str]:
errors = super().validate()
if self.max_altitude < self.min_altitude:
errors.append(
f"max_altitude({self.max_altitude}) < "
f"min_altitude({self.min_altitude})"
)
if self.min_turn_radius <= 0:
errors.append(
f"min_turn_radius 必须为正: {self.min_turn_radius}"
)
if self.max_climb_rate <= 0:
errors.append(
f"max_climb_rate 必须为正: {self.max_climb_rate}"
)
if self.fuel_capacity < 0:
errors.append(f"fuel_capacity 不能为负: {self.fuel_capacity}")
if self.fuel_consumption_rate < 0:
errors.append(
f"fuel_consumption_rate 不能为负: {self.fuel_consumption_rate}"
)
return errors
@dataclass
class MannedAircraftParams(AircraftParams):
"""有人机专有参数F-22 / J-20 等)"""
model_type: ModelType = ModelType.MANNED_AIRCRAFT
# ── 人员参数 ──────────────────────────────────────────────────
crew_count: int = 1 # 机组人数
max_g_force: float = 9.0 # 最大过载g
ejection_seat: bool = True # 是否有弹射座椅
# ── 航电参数 ──────────────────────────────────────────────────
radar_range_km: float = 200.0 # 机载雷达探测距离km
irst_range_km: float = 90.0 # 红外搜索跟踪距离km
rwr_enabled: bool = True # 雷达告警接收机
@dataclass
class UAVParams(AircraftParams):
"""无人机专有参数"""
model_type: ModelType = ModelType.UAV
# ── 任务参数 ──────────────────────────────────────────────────
endurance_hours: float = 24.0 # 续航时间(小时)
communication_range: float = 500_000.0 # 通信距离(米)
autonomous_level: int = 3 # 自主等级1~5
return_on_lost_link: bool = True # 失联自动返航
# ─────────────────────────────────────────────────────────────────
# 地面车辆专有参数
# ─────────────────────────────────────────────────────────────────
@dataclass
class GroundVehicleParams(EquipmentParams):
"""地面车辆专有参数"""
model_type: ModelType = ModelType.GROUND_VEHICLE
# ── 地形参数 ──────────────────────────────────────────────────
max_slope_deg: float = 30.0 # 最大爬坡角度(度)
ground_clearance_m: float = 0.3 # 最小离地间隙(米)
max_ford_depth_m: float = 0.8 # 最大涉水深度(米)
# ── 机动参数 ──────────────────────────────────────────────────
min_turn_radius: float = 5.0 # 最小转弯半径(米)
track_width_m: float = 2.5 # 车辆宽度(米)
length_m: float = 5.0 # 车辆长度(米)
# ── 动力参数 ──────────────────────────────────────────────────
fuel_capacity_l: float = 300.0 # 燃油容量(升)
fuel_consumption_lph: float = 20.0 # 油耗(升/小时)
def validate(self) -> List[str]:
errors = super().validate()
if self.max_slope_deg <= 0 or self.max_slope_deg > 90:
errors.append(
f"max_slope_deg 越界: {self.max_slope_deg}"
)
if self.min_turn_radius <= 0:
errors.append(
f"min_turn_radius 必须为正: {self.min_turn_radius}"
)
return errors
# ─────────────────────────────────────────────────────────────────
# 机器狗专有参数
# ─────────────────────────────────────────────────────────────────
@dataclass
class RobotDogParams(EquipmentParams):
"""机器狗专有参数"""
model_type: ModelType = ModelType.ROBOT_DOG
# ── 腿部参数 ──────────────────────────────────────────────────
leg_count: int = 4 # 腿数
max_step_height_m: float = 0.3 # 最大步高(米)
stride_length_m: float = 0.5 # 步幅(米)
max_slope_deg: float = 45.0 # 最大爬坡角度(度)
# ── 负载参数 ──────────────────────────────────────────────────
payload_kg: float = 20.0 # 最大负载(千克)
battery_capacity_wh: float = 1000.0 # 电池容量Wh
power_consumption_w: float = 500.0 # 运行功耗W
# ── 感知参数 ──────────────────────────────────────────────────
lidar_range_m: float = 50.0 # 激光雷达探测距离(米)
camera_fov_deg: float = 120.0 # 摄像头视场角(度)
def validate(self) -> List[str]:
errors = super().validate()
if self.leg_count not in (4, 6):
errors.append(f"leg_count 应为 4 或 6: {self.leg_count}")
if self.max_step_height_m <= 0:
errors.append(
f"max_step_height_m 必须为正: {self.max_step_height_m}"
)
if self.payload_kg < 0:
errors.append(f"payload_kg 不能为负: {self.payload_kg}")
return errors
# ─────────────────────────────────────────────────────────────────
# 雷达专有参数
# ─────────────────────────────────────────────────────────────────
@dataclass
class RadarParams(EquipmentParams):
"""雷达专有参数"""
model_type: ModelType = ModelType.RADAR
# ── 探测参数 ──────────────────────────────────────────────────
detection_range: float = 300_000.0 # 最大探测距离(米)
min_detection_range: float = 100.0 # 最小探测距离(米)
altitude_coverage: float = 30_000.0 # 高度覆盖(米)
# ── 扫描参数 ──────────────────────────────────────────────────
scan_angle_deg: float = 360.0 # 扫描角度范围(度)
scan_rate_deg_s: float = 6.0 # 扫描速率(度/秒)
beam_width_deg: float = 1.5 # 波束宽度(度)
# ── 电磁参数 ──────────────────────────────────────────────────
frequency_band: str = "X" # 频段X/S/L/C/Ka
peak_power_kw: float = 100.0 # 峰值功率kW
pulse_width_us: float = 1.0 # 脉冲宽度μs
# ── 跟踪参数 ──────────────────────────────────────────────────
max_track_targets: int = 100 # 最大同时跟踪目标数
track_update_rate: float = 1.0 # 跟踪更新率Hz
def validate(self) -> List[str]:
errors = super().validate()
if self.detection_range <= 0:
errors.append(
f"detection_range 必须为正: {self.detection_range}"
)
if self.scan_rate_deg_s <= 0:
errors.append(
f"scan_rate_deg_s 必须为正: {self.scan_rate_deg_s}"
)
if self.frequency_band not in ("X", "S", "L", "C", "Ka", "Ku"):
errors.append(
f"未知频段: {self.frequency_band}"
)
return errors