"""EL01 lab tests for hwe/core/massbudget.py (step 5)."""
import unittest
from _impl import load
from hwe.design import Item
from hwe.units import Q_
mb = load("massbudget")
ALLOW = {"estimated": 0.20, "catalog": 0.05, "cad": 0.10, "measured": 0.01}
def items():
return [
Item("frame", "structure", Q_(120, "g"), "catalog"),
Item("arms (x4)", "structure", Q_(53.2, "g"), "cad"),
Item("motors (x4)", "propulsion", Q_(134, "g"), "catalog"),
Item("battery", "power", Q_(210, "g"), "measured"),
Item("flight controller and ESC", "avionics", Q_(20, "g"), "estimated"),
]
class Step5MassBudget(unittest.TestCase):
def test_basic_mass_is_the_plain_sum(self):
# 120 + 53.2 + 134 + 210 + 20 = 537.2 g
self.assertAlmostEqual(mb.basic_mass(items()).to("g").m, 537.2, delta=1e-9)
def test_basic_mass_of_nothing_is_zero_grams(self):
# sum() needs a start value with units, or this returns the int 0
empty = mb.basic_mass([])
self.assertAlmostEqual(empty.to("g").m, 0.0)
def test_basic_mass_mixed_units(self):
mixed = [Item("a", "structure", Q_(0.1, "kg"), "catalog"), Item("b", "structure", Q_(50, "g"), "catalog")]
self.assertAlmostEqual(mb.basic_mass(mixed).to("g").m, 150.0, delta=1e-9)
def test_predicted_mass_adds_each_allowance(self):
# 120*1.05 + 53.2*1.10 + 134*1.05 + 210*1.01 + 20*1.20
# = 126 + 58.52 + 140.7 + 212.1 + 24 = 561.32 g
self.assertAlmostEqual(mb.predicted_mass(items(), ALLOW).to("g").m, 561.32, delta=1e-9)
def test_immature_numbers_get_bigger_allowances(self):
# Moving the 20 g estimate to "measured" must lower the prediction by 20 x (0.20 - 0.01) = 3.8 g
before = mb.predicted_mass(items(), ALLOW).to("g").m
better = items()
better[4] = Item("flight controller and ESC", "avionics", Q_(20, "g"), "measured")
after = mb.predicted_mass(better, ALLOW).to("g").m
self.assertAlmostEqual(before - after, 3.8, delta=1e-9)
def test_unknown_maturity_is_an_error(self):
bad = items() + [Item("mystery", "payload", Q_(10, "g"), "guessed")]
with self.assertRaises(KeyError):
mb.predicted_mass(bad, ALLOW)
def test_mass_by_subsystem(self):
# structure 126 + 58.52 = 184.52; propulsion 140.7; power 212.1; avionics 24
split = mb.mass_by_subsystem(items(), ALLOW)
self.assertEqual(set(split), {"structure", "propulsion", "power", "avionics"})
for name, grams in {"structure": 184.52, "propulsion": 140.7, "power": 212.1, "avionics": 24.0}.items():
self.assertAlmostEqual(split[name].to("g").m, grams, delta=1e-9, msg=name)
def test_subsystems_add_up_to_the_total(self):
split = mb.mass_by_subsystem(items(), ALLOW)
total = sum(v.to("g").m for v in split.values())
self.assertAlmostEqual(total, mb.predicted_mass(items(), ALLOW).to("g").m, delta=1e-9)
def test_allowable_mass_from_ttw_requirement(self):
# 4 motors x 1500 gf = 6000 gf; T/W >= 2 allows at most 3000 gf of weight: 3000 g
self.assertAlmostEqual(mb.allowable_mass(Q_(1500, "gf"), 4, 2.0).to("g").m, 3000.0, delta=1e-9)
def test_allowable_mass_in_newtons(self):
# 4 x 7.371 N = 29.484 N; / (2 x 9.80665) = 1.503265... kg
self.assertAlmostEqual(mb.allowable_mass(Q_(7.371, "N"), 4, 2.0).to("kg").m,
29.484 / (2 * 9.80665), delta=1e-12)
def test_mass_margin(self):
# (3000 - 2550) / 3000 = 0.15
self.assertAlmostEqual(mb.mass_margin(Q_(2550, "g"), Q_(3000, "g")).m, 0.15, delta=1e-12)
def test_negative_margin_means_too_heavy(self):
# (3000 - 3300) / 3000 = -0.1
self.assertAlmostEqual(mb.mass_margin(Q_(3.3, "kg"), Q_(3000, "g")).m, -0.1, delta=1e-12)
if __name__ == "__main__":
unittest.main()