"""Component and material data, each number with its unit and its source.Two kinds of data live in data/catalog/:- Thrust tables (CSV, units in the headers): a manufacturer's bench test of one motor and propeller at one voltage. EL01 uses the maximum thrust and the efficiency (thrust per watt) at the hover point, and cross-checks the flight time with the measured current there; EL03 checks them against momentum theory, and EL15 against your own thrust stand.- materials.toml: densities (and, from EL07 on, stiffness and strength).Interpolation is linear between table rows and refuses to extrapolate: outsidethe tested range the table says nothing, and the engine will not pretend."""from__future__importannotationsimporttomllibfromdataclassesimportdataclassfrompathlibimportPathimportnumpyasnpfromhwe.testdataimportTable,read_csvfromhwe.unitsimportQ_,Quantity,parseROOT=Path(__file__).resolve().parents[1]CATALOG=ROOT/"data"/"catalog"@dataclassclassThrustTable:table:Tablesource:str=""@propertydefthrust(self)->Quantity:returnself.table.find("thrust")@propertydefefficiency(self)->Quantity:returnself.table.find("efficiency")@propertydefpower(self)->Quantity:returnself.table.find("power")@propertydefcurrent(self)->Quantity:returnself.table.find("current")defmax_thrust(self)->Quantity:"""The largest thrust in the table (normally the 100% throttle row)."""returnself.thrust.max()defefficiency_at(self,thrust:Quantity)->Quantity:"""Efficiency (thrust per watt) at a given thrust, by linear interpolation."""returnself._at(self.efficiency,"gf/W",thrust)defcurrent_at(self,thrust:Quantity)->Quantity:"""One motor's current at a given thrust, measured at the table's test voltage."""returnself._at(self.current,"A",thrust)def_at(self,column:Quantity,unit:str,thrust:Quantity)->Quantity:x=self.thrust.to("gf").my=column.to(unit).morder=np.argsort(x)x,y=x[order],y[order]want=thrust.to("gf").mifnot(x[0]<=want<=x[-1]):raiseValueError(f"thrust {thrust.to('gf'):.0f~P} is outside the tested range "f"{x[0]:.0f}-{x[-1]:.0f} gf of {self.table.path.name}")returnQ_(float(np.interp(want,x,y)),unit)defthrust_table(relative_path:str,source:str="")->ThrustTable:path=(ROOT/relative_path)ifnotPath(relative_path).is_absolute()elsePath(relative_path)returnThrustTable(read_csv(path),source)defmaterials()->dict:"""{name: {"density": Quantity, "source": str, ...}} from data/catalog/materials.toml."""withopen(CATALOG/"materials.toml","rb")asf:raw=tomllib.load(f)out={}forname,entryinraw.items():out[name]={k:(parse(v)ifknotin("source","note","name")elsev)fork,vinentry.items()}returnout