104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
from dataclasses import dataclass
|
|
from functools import cache, cached_property
|
|
import math
|
|
import re
|
|
from typing import Collection, Mapping, Self, Sequence
|
|
|
|
from utils import inrange, lin_fun_interpolate, with_ops_scalar, with_ops_unary, with_ops_vector
|
|
|
|
MPH = 0.44704
|
|
RPM = 2 * math.pi / 60
|
|
|
|
@with_ops_unary('neg')
|
|
@with_ops_vector('sub', 'add')
|
|
@with_ops_scalar('mul', 'truediv')
|
|
@dataclass(frozen=True)
|
|
class PropOppoint:
|
|
speed: float
|
|
thrust: float
|
|
torque: float
|
|
rpm: float
|
|
|
|
@cached_property
|
|
def rot_speed(self) -> float:
|
|
return self.rpm * RPM
|
|
|
|
def __neg__(self) -> Self: ...
|
|
|
|
def __truediv__(self, other: float) -> Self: ...
|
|
def __mul__(self, other: float) -> Self: ...
|
|
|
|
def __add__(self, other: Self) -> Self: ...
|
|
def __sub__(self, other: Self) -> Self: ...
|
|
|
|
@dataclass(frozen=True)
|
|
class ApcPerfdata:
|
|
oppoints: Collection[PropOppoint]
|
|
|
|
@cached_property
|
|
def rpms(self) -> Sequence[float]:
|
|
return sorted({ op.rpm for op in self.oppoints })
|
|
|
|
def speed_range_at_rpm(self, rpm: float) -> tuple[float, float]:
|
|
speeds = [op.speed for op in self.rpm_series[rpm]]
|
|
return min(speeds), max(speeds)
|
|
|
|
def rpms_at_speed(self, speed: float):
|
|
return [rpm for rpm in self.rpms if inrange(speed, *self.speed_range_at_rpm(rpm))]
|
|
|
|
def rpm_range_at_speed(self, speed: float) -> tuple[float, float]:
|
|
rpms = self.rpms_at_speed(speed)
|
|
return min(rpms), max(rpms)
|
|
|
|
def _rpm_serie(self, rpm: float) -> Sequence[PropOppoint]:
|
|
return sorted([op for op in self.oppoints if op.rpm == rpm], key=lambda op: op.speed)
|
|
|
|
@cached_property
|
|
def rpm_series(self) -> Mapping[float, Sequence[PropOppoint]]:
|
|
return { rpm: self._rpm_serie(rpm) for rpm in self.rpms }
|
|
|
|
def get_op_interpspeed(self, rpm: float, speed: float) -> PropOppoint:
|
|
s = self.rpm_series[rpm]
|
|
def mk_op(op):
|
|
return lambda: op
|
|
return lin_fun_interpolate([op.speed for op in s], [mk_op(op) for op in s], speed)
|
|
|
|
def get_op_interp(self, rpm: float, speed: float) -> PropOppoint:
|
|
loc_rpms = self.rpms_at_speed(speed)
|
|
def mk_op(_rpm):
|
|
return lambda: self.get_op_interpspeed(_rpm, speed)
|
|
return lin_fun_interpolate(loc_rpms, [mk_op(_rpm) for _rpm in loc_rpms], rpm)
|
|
|
|
|
|
@classmethod
|
|
def from_file(cls, path: str):
|
|
with open(path, 'r') as file:
|
|
cont = file.read()
|
|
data = parse_apc_data(cont)
|
|
return cls(data)
|
|
|
|
|
|
def parse_apc_data(raw_data: str) -> Collection[PropOppoint]:
|
|
data_points = []
|
|
current_rpm = None
|
|
|
|
for line in raw_data.splitlines():
|
|
if 'PROP RPM' in line:
|
|
current_rpm = float(line.split()[-1])
|
|
elif current_rpm is None:
|
|
continue
|
|
try:
|
|
vals = list(map(float, line.split()))
|
|
except ValueError:
|
|
continue
|
|
if len(vals) != 15:
|
|
continue
|
|
|
|
speed = vals[0] * MPH
|
|
torque = vals[9]
|
|
thrust = vals[10]
|
|
data_points.append(PropOppoint(speed, thrust, torque, current_rpm))
|
|
|
|
return data_points
|
|
|