"""EL01 lab tests for hwe/core/hover.py (steps 1-3).Run with `python labs/el01_hover/check.py N` from the eng-lab folder.Every expected number is worked out by hand in a comment, so a failing testshows you the arithmetic it expected."""importunittestfrom_implimportloadfromhwe.unitsimportG0,Q_,DimensionalityErrorhover=load("hover")G=9.80665# standard gravity, m/s^2 (exact)defclose(test,quantity,expected,unit,rel=1e-9):"""Assert quantity, converted to `unit`, equals expected to a relative tolerance."""value=quantity.to(unit).mtest.assertAlmostEqual(value,expected,delta=abs(expected)*rel+1e-12,msg=f"expected {expected}{unit}, got {value}{unit}")# ----------------------------------------------------------------------classStep1Weight(unittest.TestCase):deftest_weight_of_1_5_kg(self):# 1.5 kg x 9.80665 m/s^2 = 14.709975 Nclose(self,hover.weight(Q_(1.5,"kg")),14.709975,"N")deftest_weight_accepts_grams(self):# 650 g = 0.650 kg; 0.650 x 9.80665 = 6.3743225 Nclose(self,hover.weight(Q_(650,"g")),6.3743225,"N")deftest_weight_uses_the_g_it_is_given(self):# Ingenuity on Mars: 1.8 kg x 3.73 m/s^2 = 6.714 N (same mass, different weight)close(self,hover.weight(Q_(1.8,"kg"),Q_(3.73,"m/s^2")),6.714,"N")deftest_weight_is_a_force(self):self.assertEqual(hover.weight(Q_(1,"kg")).dimensionality,Q_(1,"N").dimensionality)deftest_bare_number_is_refused(self):withself.assertRaises(TypeError):hover.weight(1.5)# ----------------------------------------------------------------------classStep2Newton(unittest.TestCase):deftest_net_force_four_motors(self):# 4 x 4 N = 16 N up; weight 14.709975 N down; net 1.290025 N upclose(self,hover.net_force([Q_(4,"N")]*4,Q_(1.5,"kg")),1.290025,"N")deftest_hover_in_gram_force_is_zero_net_force(self):# 4 x 375 gf = 1500 gf, exactly the weight of 1.5 kg: a hovernet=hover.net_force([Q_(375,"gf")]*4,Q_(1.5,"kg"))self.assertAlmostEqual(net.to("N").m,0.0,delta=1e-9)deftest_unequal_thrusts_all_count(self):# 3 + 4 + 5 + 6 = 18 N; 18 - 1.2 x 9.80665 = 18 - 11.76798 = 6.23202 Nthrusts=[Q_(3,"N"),Q_(4,"N"),Q_(5,"N"),Q_(6,"N")]close(self,hover.net_force(thrusts,Q_(1.2,"kg")),6.23202,"N")deftest_acceleration_climbing(self):# 4 x 5 N = 20 N; weight 11.76798 N; net 8.23202 N; a = 8.23202 / 1.2 = 6.860016667 m/s^2close(self,hover.acceleration([Q_(5,"N")]*4,Q_(1.2,"kg")),6.860016666666667,"m/s^2")deftest_acceleration_sinking_is_negative(self):# 4 x 2 N = 8 N < 11.76798 N: a = (8 - 11.76798) / 1.2 = -3.139983333 m/s^2close(self,hover.acceleration([Q_(2,"N")]*4,Q_(1.2,"kg")),-3.1399833333333333,"m/s^2")deftest_net_force_and_acceleration_use_the_g_they_are_given(self):# On Mars: 16 N up, 1.5 kg x 3.73 m/s^2 = 5.595 N down, net 10.405 N; a = 10.405 / 1.5 = 6.936667mars=Q_(3.73,"m/s^2")close(self,hover.net_force([Q_(4,"N")]*4,Q_(1.5,"kg"),mars),10.405,"N")close(self,hover.acceleration([Q_(4,"N")]*4,Q_(1.5,"kg"),mars),10.405/1.5,"m/s^2")deftest_no_thrust_is_free_fall(self):# With zero thrust only gravity acts: a = -gclose(self,hover.acceleration([Q_(0,"N")]*4,Q_(0.8,"kg")),-G,"m/s^2")deftest_accelerometer_at_rest_reads_plus_g(self):close(self,hover.accelerometer_reading(Q_(0,"m/s^2")),G,"m/s^2")deftest_accelerometer_in_free_fall_reads_zero(self):reading=hover.accelerometer_reading(-G0)self.assertAlmostEqual(reading.to("m/s^2").m,0.0,delta=1e-12)deftest_accelerometer_while_speeding_up_upward(self):# a = +2 m/s^2: reading = 2 + 9.80665 = 11.80665 m/s^2close(self,hover.accelerometer_reading(Q_(2,"m/s^2")),11.80665,"m/s^2")deftest_accelerometer_on_mars_at_rest(self):close(self,hover.accelerometer_reading(Q_(0,"m/s^2"),Q_(3.73,"m/s^2")),3.73,"m/s^2")deftest_accelerometer_reads_thrust_per_unit_mass(self):# The key idea of section 2.6: reading = (sum of thrusts) / mass, whatever the motion.thrusts,m=[Q_(3,"N"),Q_(4,"N"),Q_(5,"N"),Q_(6,"N")],Q_(1.2,"kg")reading=hover.accelerometer_reading(hover.acceleration(thrusts,m))close(self,reading,18/1.2,"m/s^2")# ----------------------------------------------------------------------classStep3ThrustToWeight(unittest.TestCase):deftest_ttw_five_inch_example(self):# 4 x 1500 gf = 6000 gf of thrust; 650 g weighs 650 gf; T/W = 6000 / 650 = 9.230769...ttw=hover.thrust_to_weight(Q_(1500,"gf"),4,Q_(650,"g"))self.assertAlmostEqual(ttw.m,6000/650,delta=1e-9)deftest_ttw_is_a_pure_number(self):ttw=hover.thrust_to_weight(Q_(1500,"gf"),4,Q_(650,"g"))self.assertTrue(ttw.dimensionless)deftest_ttw_in_newtons_example_drone(self):# An example 1.5 kg drone: k = 9.1e-6 N/(rad/s)^2 at 900 rad/s -> 7.371 N per motor;# 4 x 7.371 = 29.484 N; weight 14.709975 N; T/W = 2.004354...ttw=hover.thrust_to_weight(Q_(7.371,"N"),4,Q_(1.5,"kg"))self.assertAlmostEqual(ttw.m,29.484/14.709975,delta=1e-9)deftest_ttw_counts_every_motor(self):# A hexacopter with the same motors has 6/4 the T/W of the quad.quad=hover.thrust_to_weight(Q_(1000,"gf"),4,Q_(1,"kg")).mhexa=hover.thrust_to_weight(Q_(1000,"gf"),6,Q_(1,"kg")).mself.assertAlmostEqual(hexa/quad,1.5,delta=1e-12)deftest_hover_thrust_per_motor(self):# 1.5 kg shared by 4 motors: 14.709975 / 4 = 3.67749375 N eachclose(self,hover.hover_thrust_per_motor(Q_(1.5,"kg"),4),3.67749375,"N")deftest_hover_fraction_is_inverse_of_ttw(self):args=(Q_(7.371,"N"),4,Q_(1.5,"kg"))product=hover.hover_thrust_fraction(*args).m*hover.thrust_to_weight(*args).mself.assertAlmostEqual(product,1.0,delta=1e-12)deftest_hover_fraction_value(self):# 650 gf / 6000 gf = 0.108333...frac=hover.hover_thrust_fraction(Q_(1500,"gf"),4,Q_(650,"g"))self.assertAlmostEqual(frac.m,650/6000,delta=1e-12)deftest_hover_fraction_above_one_is_returned_not_raised(self):# 4 x 100 gf = 400 gf cannot lift 500 g: it would need 500 / 400 = 1.25 of full thrustfrac=hover.hover_thrust_fraction(Q_(100,"gf"),4,Q_(500,"g"))self.assertAlmostEqual(frac.m,1.25,delta=1e-12)deftest_max_climb_acceleration(self):# g (T/W - 1) = 9.80665 x (6000/650 - 1) = 80.71627...a=hover.max_climb_acceleration(Q_(1500,"gf"),4,Q_(650,"g"))close(self,a,G*(6000/650-1),"m/s^2")deftest_max_climb_acceleration_ttw_two_is_one_g(self):# T/W = 2 exactly: the drone can accelerate upward at exactly 1 ga=hover.max_climb_acceleration(Q_(500,"gf"),4,Q_(1000,"g"))close(self,a,G,"m/s^2")deftest_max_climb_acceleration_negative_when_too_heavy(self):a=hover.max_climb_acceleration(Q_(100,"gf"),4,Q_(500,"g"))# T/W = 0.8close(self,a,G*(0.8-1),"m/s^2")deftest_the_family_uses_the_g_it_is_given(self):# Mars, g = 3.73 m/s^2. 6000 gf = 58.8399 N of thrust; 650 g weighs 0.65 x 3.73 = 2.4245 N there.mars=Q_(3.73,"m/s^2")thrust=4*1.5*9.80665# Nself.assertAlmostEqual(hover.thrust_to_weight(Q_(1500,"gf"),4,Q_(650,"g"),mars).m,thrust/(0.65*3.73),delta=1e-9)close(self,hover.hover_thrust_per_motor(Q_(1.8,"kg"),4,mars),1.8*3.73/4,"N")close(self,hover.max_climb_acceleration(Q_(1500,"gf"),4,Q_(650,"g"),mars),(thrust-0.65*3.73)/0.65,"m/s^2")deftest_wrong_unit_for_thrust_is_refused(self):withself.assertRaises(DimensionalityError):hover.thrust_to_weight(Q_(1500,"g"),4,Q_(650,"g"))# grams of MASS, not gram-forceif__name__=="__main__":unittest.main()