diff --git a/apc_perf.py b/apc_perf.py index 6520cb1..f26210b 100644 --- a/apc_perf.py +++ b/apc_perf.py @@ -1,11 +1,13 @@ from dataclasses import dataclass from functools import cache, cached_property +import math import re from typing import Collection, Mapping, Self, Sequence -from utils import lin_fun_interpolate, with_ops_scalar, with_ops_unary, with_ops_vector +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') @@ -17,6 +19,10 @@ class PropOppoint: 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: ... @@ -30,8 +36,19 @@ class ApcPerfdata: oppoints: Collection[PropOppoint] @cached_property - def rpms(self) -> Collection[float]: - return { op.rpm for op in self.oppoints } + 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) @@ -46,6 +63,12 @@ class ApcPerfdata: 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): @@ -72,8 +95,8 @@ def parse_apc_data(raw_data: str) -> Collection[PropOppoint]: continue speed = vals[0] * MPH - thrust = vals[9] - torque = vals[10] + torque = vals[9] + thrust = vals[10] data_points.append(PropOppoint(speed, thrust, torque, current_rpm)) return data_points diff --git a/mot.py b/mot.py new file mode 100644 index 0000000..4159b20 --- /dev/null +++ b/mot.py @@ -0,0 +1,84 @@ +from dataclasses import dataclass +from functools import cached_property +import math +from scipy import optimize + +from apc_perf import RPM, ApcPerfdata + +VOLT_DUTY_MAX = 0.97 + +@dataclass(frozen=True) +class MotorOppoint: + voltage: float + current: float + rpm: float + torque: float + + @cached_property + def rot_speed(self) -> float: + return self.rpm * RPM + + @property + def power_in(self) -> float: + return self.voltage * self.current + + @property + def power_out(self) -> float: + return self.torque * self.rot_speed + + @property + def efficiency(self) -> float: + return self.power_out/self.power_in + +@dataclass(frozen=True) +class MotorData: + mot_kv: float + mot_ir: float + mot_idle_i: float + + @cached_property + def mot_kv_si(self) -> float: + return self.mot_kv * RPM + + @property + def effective_ir(self) -> float: + return self.mot_ir*1.5 + return self.mot_ir + + def mot_full_pizda(self, rpm: float, voltage: float) -> MotorOppoint: + rotspd = rpm * RPM + voltage = VOLT_DUTY_MAX * voltage + u_active = (rotspd / self.mot_kv_si) + u_heat = voltage - u_active + current = u_heat / self.effective_ir + torq = (current-self.mot_idle_i) / self.mot_kv_si + + return MotorOppoint( + voltage = voltage, + current = current, + rpm = rpm, + torque = torq, + ) + + + +def op_full_pizda(mot: MotorData, prop: ApcPerfdata, speed: float, volt: float): + + def f(rpm): + return prop.get_op_interp(rpm, speed).torque - mot.mot_full_pizda(rpm, volt).torque + + opt = optimize.root_scalar(f, bracket = prop.rpm_range_at_speed(speed)) + # print(opt) + + rpm = opt.root + + p_op = prop.get_op_interp(rpm, speed) + m_op = mot.mot_full_pizda(rpm, volt) + # print(p_op) + # print(m_op) + + return p_op, m_op + + + + diff --git a/test1.py b/test1.py new file mode 100644 index 0000000..8f95d4d --- /dev/null +++ b/test1.py @@ -0,0 +1,37 @@ +from apc_perf import ApcPerfdata +from mot import MotorData, op_full_pizda + + +at2826_900 = MotorData( + mot_kv = 900, + mot_ir = 0.035, + mot_idle_i =2.5 + ) + +at2826_1100 = MotorData( + mot_kv = 900, + mot_ir = 0.11, + mot_idle_i = 2.5 + ) + +rcf = MotorData( + mot_kv = 1100, + mot_ir = 0.02, + mot_idle_i = 5 + ) +m_at100c = MotorData( + mot_kv = 90, + mot_ir = 0.02, + mot_idle_i=4, + ) + +prop_12x6 = ApcPerfdata.from_file('data/PERFILES2/PER3_12x6E.dat') + +# Tutaj symulacja +op_prop, op_mot = op_full_pizda(mot= at2826_900, + prop= prop_12x6, + speed= 10, + volt= 14.6) + +print(op_prop) +print(op_mot) diff --git a/utils.py b/utils.py index 446c12c..c593ad2 100644 --- a/utils.py +++ b/utils.py @@ -7,16 +7,22 @@ class Interpolable(Protocol): def __mul__(self: Self, other: float, /) -> Self: ... def __neg__(self: Self, /) -> Self: ... +def inrange(x: float, xmin: float, xmax: float) -> bool: + return xmin <= x <= xmax + EXTRAPOLATE_NONE = 0 EXTRAPOLATE_CONST = 1 EXTRAPOLATE_LIN = 2 +class InterpolationRangeError(ValueError): + pass + T1 = TypeVar('T1', bound=Interpolable) def lin_fun_interpolate(x_values: Sequence[float], y_values: Sequence[Callable[[], T1]], x: float, extr_down = EXTRAPOLATE_NONE, extr_up = EXTRAPOLATE_NONE) -> T1: # x0,x1,y0,y1 = None if x < x_values[0]: if extr_down == EXTRAPOLATE_NONE: - raise ValueError("x is outside the interpolation range!") + raise InterpolationRangeError("x is outside the interpolation range!") elif extr_down == EXTRAPOLATE_CONST: return y_values[0]() elif extr_down == EXTRAPOLATE_LIN: @@ -28,11 +34,11 @@ def lin_fun_interpolate(x_values: Sequence[float], y_values: Sequence[Callable[[ x0, x1 = x_values[i-1], x_values[i] y0, y1 = y_values[i-1](), y_values[i]() - if x_values[i] > x: + if x_values[i] >= x: break else: if extr_up == EXTRAPOLATE_NONE: - raise ValueError("x is outside the interpolation range!") + raise InterpolationRangeError("x is outside the interpolation range!") elif extr_up == EXTRAPOLATE_CONST: return y1 elif extr_up == EXTRAPOLATE_LIN: