class TestDebugLvl(unittest.TestCase): def setUp(self): self.sandbox = SandBox('check_cases', 'orbit') def tearDown(self): self.sandbox.erase() def testDebugLvl(self): fdm = CreateFDM(self.sandbox) fdm.load_script( self.sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() ExecuteUntil(fdm, 1000.) ref, current = Table(), Table() ref.ReadCSV(self.sandbox('BallOut.csv')) del fdm os.environ["JSBSIM_DEBUG"] = str(0) fdm = CreateFDM(self.sandbox) fdm.load_script( self.sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() ExecuteUntil(fdm, 1000.) current.ReadCSV(self.sandbox('BallOut.csv')) diff = ref.compare(current) self.longMessage = True self.assertTrue(diff.empty(), msg='\n' + repr(diff))
class TestOrbitCheckCase(unittest.TestCase): def setUp(self): self.sandbox = SandBox('check_cases', 'orbit') def tearDown(self): self.sandbox.erase() def testOrbitCheckCase(self): os.environ['JSBSIM_DEBUG'] = str(0) fdm = CreateFDM(self.sandbox) fdm.load_script( self.sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() while fdm.run(): pass ref, current = Table(), Table() ref.ReadCSV( self.sandbox.elude( self.sandbox.path_to_jsbsim_file('logged_data', 'BallOut.csv'))) current.ReadCSV(self.sandbox('BallOut.csv')) diff = ref.compare(current) self.longMessage = True self.assertTrue(diff.empty(), msg='\n' + repr(diff))
class TestGustReset(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_gust_reset(self): fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'c172_cruise_8K.xml')) fdm.set_property_value('simulation/randomseed', 0.0) fdm.set_output_directive(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.run_ic() ExecuteUntil(fdm, 15.5) ref, current = Table(), Table() ref.ReadCSV(self.sandbox('output.csv')) fdm.set_property_value('simulation/randomseed', 0.0) fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 15.5) current.ReadCSV(self.sandbox('output_0.csv')) diff = ref.compare(current) self.longMessage = True self.assertTrue(diff.empty(), msg='\n'+repr(diff))
class TestDebugLvl(unittest.TestCase): def setUp(self): self.sandbox = SandBox('check_cases', 'orbit') def tearDown(self): self.sandbox.erase() def testDebugLvl(self): fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() ExecuteUntil(fdm, 1000.) ref, current = Table(), Table() ref.ReadCSV(self.sandbox('BallOut.csv')) del fdm os.environ["JSBSIM_DEBUG"]=str(0) fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() ExecuteUntil(fdm, 1000.) current.ReadCSV(self.sandbox('BallOut.csv')) diff = ref.compare(current) self.longMessage = True self.assertTrue(diff.empty(), msg='\n'+repr(diff))
class CheckScripts(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.scripts = 0 def tearDown(self): print "Tested %g scripts" % (self.scripts, ) self.sandbox.erase() def testScripts(self): script_path = self.sandbox.path_to_jsbsim_file('scripts') for f in os.listdir(self.sandbox.elude(script_path)): fullpath = os.path.join(self.sandbox.elude(script_path), f) # Does f contains a JSBSim script ? if not CheckXMLFile(fullpath, 'runscript'): continue fdm = CreateFDM(self.sandbox) self.assertTrue(fdm.load_script(os.path.join(script_path, f)), msg="Failed to load script %s" % (fullpath, )) fdm.run_ic() self.scripts += 1 del fdm
class CheckScripts(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.scripts = 0 def tearDown(self): print "Tested %g scripts" % (self.scripts,) self.sandbox.erase() def testScripts(self): script_path = self.sandbox.path_to_jsbsim_file('scripts') for f in os.listdir(self.sandbox.elude(script_path)): fullpath = os.path.join(self.sandbox.elude(script_path), f) # Does f contains a JSBSim script ? if not CheckXMLFile(fullpath, 'runscript'): continue fdm = CreateFDM(self.sandbox) self.assertTrue(fdm.load_script(os.path.join(script_path, f)), msg="Failed to load script %s" % (fullpath,)) fdm.run_ic() self.scripts += 1 del fdm
class TestGustReset(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_gust_reset(self): fdm = CreateFDM(self.sandbox) fdm.load_script( self.sandbox.path_to_jsbsim_file('scripts', 'c172_cruise_8K.xml')) fdm.set_property_value('simulation/randomseed', 0.0) fdm.set_output_directive( self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.run_ic() ExecuteUntil(fdm, 15.5) ref, current = Table(), Table() ref.ReadCSV(self.sandbox('output.csv')) fdm.set_property_value('simulation/randomseed', 0.0) fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 15.5) current.ReadCSV(self.sandbox('output_0.csv')) diff = ref.compare(current) self.longMessage = True self.assertTrue(diff.empty(), msg='\n' + repr(diff))
class TestScriptOutput(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml') def tearDown(self): self.sandbox.erase() def test_no_output(self): fdm = CreateFDM(self.sandbox) fdm.load_script(self.script_path) fdm.run_ic() ExecuteUntil(fdm, 10.) self.assertFalse(self.sandbox.exists('output.csv'), msg="Results have unexpectedly been written to 'output.csv'") def test_output_from_file(self): tree = et.parse(self.sandbox.elude(self.script_path)) output_tag = et.SubElement(tree.getroot(), 'output') output_tag.attrib['file'] = self.sandbox.elude(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) tree.write(self.sandbox('c1722_0.xml')) fdm = CreateFDM(self.sandbox) fdm.load_script('c1722_0.xml') fdm.run_ic() ExecuteUntil(fdm, 10.) self.assertTrue(self.sandbox.exists('output.csv'), msg="The file 'output.csv' has not been created") def test_output(self): tree = et.parse(self.sandbox.elude(self.script_path)) output_tag = et.SubElement(tree.getroot(), 'output') output_tag.attrib['name'] = 'test.csv' output_tag.attrib['type'] = 'CSV' output_tag.attrib['rate'] = '10' property_tag = et.SubElement(output_tag, 'property') property_tag.text = 'position/vrp-radius-ft' tree.write(self.sandbox('c1722_0.xml')) fdm = CreateFDM(self.sandbox) fdm.load_script('c1722_0.xml') fdm.run_ic() ExecuteUntil(fdm, 10.) self.assertTrue(self.sandbox.exists(output_tag.attrib['name']), msg="The file 'output.csv' has not been created") orig = pd.read_csv(self.sandbox('JSBout172B.csv')) test = pd.read_csv(self.sandbox('test.csv')) self.assertEqual(np.max(orig['Time']-test['Time']), 0.0) pname = '/fdm/jsbsim/' + property_tag.text self.assertEqual(np.max(orig[pname]-test[pname]), 0.0)
class TestCosineGust(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def testMagnitude(self): fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'c172_cruise_8K.xml')) fdm.run_ic() t = 0.0 startup_duration = 5.0 steady_duration = 1.0 end_duration = 5.0 start_time = 10.0 magnitude = 30.0 end_time = start_time + startup_duration + steady_duration + end_duration while fdm.get_property_value('simulation/run_id') == 0: fdm.run() wn = fdm.get_property_value('atmosphere/total-wind-north-fps') we = fdm.get_property_value('atmosphere/total-wind-east-fps') wd = fdm.get_property_value('atmosphere/total-wind-down-fps') if t >= start_time and t <= end_time: wmag = math.sqrt(wn*wn + we*we + wd*wd) t -= start_time if t <= startup_duration: self.assertAlmostEqual(0.5 * magnitude * (1.0 - math.cos(math.pi*t/startup_duration)), wmag, delta=1E-3) else: t -= startup_duration if t <= steady_duration: self.assertAlmostEqual(magnitude, wmag, delta=1E-8) else: t -= steady_duration if t <= end_duration: self.assertAlmostEqual(0.5 * magnitude * (1.0 + math.cos(math.pi*t/end_duration)), wmag, delta=1E-3) t = fdm.get_property_value('simulation/sim-time-sec')
class CheckAircrafts(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def testAircrafts(self): aircraft_path = self.sandbox.elude( self.sandbox.path_to_jsbsim_file('aircraft')) for d in os.listdir(aircraft_path): fullpath = os.path.join(aircraft_path, d) # Is d a directory ? if not os.path.isdir(fullpath): continue f = os.path.join(aircraft_path, d, append_xml(d)) # Is f an aircraft definition file ? if not CheckXMLFile(f, 'fdm_config'): continue if d in ('blank'): continue fdm = CreateFDM(self.sandbox) self.assertTrue(fdm.load_model(d), msg='Failed to load aircraft %s' % (d, )) for f in os.listdir(fullpath): f = os.path.join(aircraft_path, d, f) if CheckXMLFile(f, 'initialize'): self.assertTrue( fdm.load_ic(f, False), msg='Failed to load IC %s for aircraft %s' % (f, d)) try: fdm.run_ic() except RuntimeError: self.fail('Failed to run IC %s for aircraft %s' % (f, d)) break del fdm
class CheckTrim(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_trim_doesnt_ignite_rockets(self): # Run a longitudinal trim with a rocket equipped with solid propellant # boosters (aka SRBs). The trim algorithm will try to reach a vertical # equilibrium by tweaking the throttle but since the rocket is nose up, # the trim cannot converge. As a result the algorithm will set full # throttle which will result in the SRBs ignition if the integration is # not suspended. This bug has been reported in FlightGear and this test # is checking that there is no regression. fdm = CreateFDM(self.sandbox) fdm.load_model('J246') aircraft_path = self.sandbox.elude(self.sandbox.path_to_jsbsim_file('aircraft')) fdm.load_ic(os.path.join(aircraft_path, 'J246', 'LC39'), False) fdm.run_ic() # Check that the SRBs are not ignited self.assertEqual(fdm['propulsion/engine[0]/thrust-lbs'], 0.0) self.assertEqual(fdm['propulsion/engine[1]/thrust-lbs'], 0.0) try: fdm['simulation/do_simple_trim'] = 1 except RuntimeError as e: # The trim cannot succeed. Just make sure that the raised exception # is due to the trim failure otherwise rethrow. if e.args[0] != 'Trim Failed': raise # Check that the trim did not ignite the SRBs self.assertEqual(fdm['propulsion/engine[0]/thrust-lbs'], 0.0) self.assertEqual(fdm['propulsion/engine[1]/thrust-lbs'], 0.0) def test_trim_on_ground(self): fdm = CreateFDM(self.sandbox) fdm.load_model('c172x') fdm['ic/theta-deg'] = 10.0 fdm.run_ic() fdm['ic/theta-deg'] = 0.0 fdm['simulation/do_simple_trim'] = 2
class CheckAircrafts(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def testAircrafts(self): aircraft_path = self.sandbox.elude(self.sandbox.path_to_jsbsim_file('aircraft')) for d in os.listdir(aircraft_path): fullpath = os.path.join(aircraft_path, d) # Is d a directory ? if not os.path.isdir(fullpath): continue f = os.path.join(aircraft_path, d, append_xml(d)) # Is f an aircraft definition file ? if not CheckXMLFile(f, 'fdm_config'): continue if d in ('blank'): continue fdm = CreateFDM(self.sandbox) self.assertTrue(fdm.load_model(d), msg='Failed to load aircraft %s' % (d,)) for f in os.listdir(fullpath): f = os.path.join(aircraft_path, d, f) if CheckXMLFile(f, 'initialize'): self.assertTrue(fdm.load_ic(f, False), msg='Failed to load IC %s for aircraft %s' %(f,d)) try: fdm.run_ic() except RuntimeError: self.fail('Failed to run IC %s for aircraft %s' %(f,d)) break del fdm
class TestHoldDown(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_static_hold_down(self): fdm = CreateFDM(self.sandbox) fdm.load_model('J246') aircraft_path = self.sandbox.elude(self.sandbox.path_to_jsbsim_file('aircraft')) fdm.load_ic(os.path.join(aircraft_path, 'J246', 'LC39'), False) fdm.set_property_value('forces/hold-down', 1.0) fdm.run_ic() h0 = fdm.get_property_value('position/h-sl-ft') t = 0.0 while t < 420.0: fdm.run() t = fdm.get_property_value('simulation/sim-time-sec') self.assertAlmostEqual(fdm.get_property_value('position/h-sl-ft'), h0, delta=1E-5)
class TestOrbitCheckCase(unittest.TestCase): def setUp(self): self.sandbox = SandBox('check_cases', 'orbit') def tearDown(self): self.sandbox.erase() def testOrbitCheckCase(self): os.environ['JSBSIM_DEBUG'] = str(0) fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() while fdm.run(): pass ref, current = Table(), Table() ref.ReadCSV(self.sandbox.elude(self.sandbox.path_to_jsbsim_file('logged_data', 'BallOut.csv'))) current.ReadCSV(self.sandbox('BallOut.csv')) diff = ref.compare(current) self.longMessage = True self.assertTrue(diff.empty(), msg='\n'+repr(diff))
class TestHoldDown(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_static_hold_down(self): fdm = CreateFDM(self.sandbox) fdm.load_model('J246') aircraft_path = self.sandbox.elude( self.sandbox.path_to_jsbsim_file('aircraft')) fdm.load_ic(os.path.join(aircraft_path, 'J246', 'LC39'), False) fdm.set_property_value('forces/hold-down', 1.0) fdm.run_ic() h0 = fdm.get_property_value('position/h-sl-ft') t = 0.0 while t < 420.0: fdm.run() t = fdm.get_property_value('simulation/sim-time-sec') self.assertAlmostEqual(fdm.get_property_value('position/h-sl-ft'), h0, delta=1E-5)
class CheckMomentsUpdate(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_moments_update(self): script_path = self.sandbox.path_to_jsbsim_file('scripts', 'weather-balloon.xml') fdm = CreateFDM(self.sandbox) fdm.load_script(script_path) fdm.run_ic() # Moves the radio sonde to modify the CG location fdm.set_property_value('inertia/pointmass-location-X-inches', 5.0) # Check that the moment is immediately updated accordingly fdm.run() Fbz = fdm.get_property_value('forces/fbz-buoyancy-lbs') CGx = fdm.get_property_value('inertia/cg-x-in') / 12.0 # Converts from in to ft Mby = fdm.get_property_value('moments/m-buoyancy-lbsft') self.assertTrue(abs(Fbz * CGx + Mby) < 1E-7, msg="Fbz*CGx = %f and Mby = %f do not match" % (-Fbz*CGx, Mby))
class TestInputSocket(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.script_path = self.sandbox.path_to_jsbsim_file( 'scripts', 'c1722.xml') def tearDown(self): self.sandbox.erase() def sanityCheck(self, _tn): # Check that the connection has been established out = _tn.getOutput() self.assertTrue( string.split(out, '\n')[0] == 'Connected to JSBSim server', msg="Not connected to the JSBSim server.\nGot message '%s' instead" % (out, )) # Check that "help" returns the minimum set of commands that will be # tested self.assertEqual( sorted( map(lambda x: string.strip(string.split(x, '{')[0]), string.split(_tn.sendCommand("help"), '\n')[2:-2])), [ 'get', 'help', 'hold', 'info', 'iterate', 'quit', 'resume', 'set' ]) def test_no_input(self): fdm = CreateFDM(self.sandbox) fdm.load_script(self.script_path) fdm.run_ic() fdm.hold() with self.assertRaises(socket.error): TelnetInterface(fdm, 5., 1137) def test_input_socket(self): # The aircraft c172x does not contain an <input> tag so we need # to add one. tree, aircraft_name, b = CopyAircraftDef(self.script_path, self.sandbox) self.root = tree.getroot() input_tag = et.SubElement(self.root, 'input') input_tag.attrib['port'] = '1137' tree.write( self.sandbox('aircraft', aircraft_name, aircraft_name + '.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(self.script_path) fdm.run_ic() fdm.hold() tn = TelnetInterface(fdm, 5., 1137) self.sanityCheck(tn) # Check the aircraft name and its version msg = string.split(tn.sendCommand("info"), '\n') self.assertEqual(string.strip(string.split(msg[2], ':')[1]), string.strip(self.root.attrib['name'])) self.assertEqual(string.strip(string.split(msg[1], ':')[1]), string.strip(self.root.attrib['version'])) # Check that the simulation time is 0.0 self.assertEqual(float(string.strip(string.split(msg[3], ':')[1])), 0.0) self.assertEqual(tn.getSimTime(), 0.0) self.assertEqual(tn.getPropertyValue("simulation/sim-time-sec"), 0.0) # Check that 'iterate' iterates the correct number of times tn.sendCommand("iterate 19") self.assertEqual(tn.getSimTime(), 19. * tn.getDeltaT()) self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), tn.getSimTime(), delta=1E-5) # Wait a little bit and make sure that the simulation time has not # changed meanwhile thus confirming that the simulation is on hold. tn.wait(0.1) self.assertEqual(tn.getSimTime(), 19. * tn.getDeltaT()) self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), tn.getSimTime(), delta=1E-5) # Modify the tank[0] contents via the "send" command half_contents = 0.5 * tn.getPropertyValue( "propulsion/tank/contents-lbs") tn.sendCommand("set propulsion/tank/contents-lbs " + str(half_contents)) self.assertEqual(tn.getPropertyValue("propulsion/tank/contents-lbs"), half_contents) # Check the resume/hold commands tn.setRealTime(True) t = tn.getSimTime() tn.sendCommand("resume") tn.wait(0.5) self.assertNotEqual(tn.getSimTime(), t) tn.wait(0.5) tn.sendCommand("hold") tn.setRealTime(False) t = tn.getSimTime() self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), t, delta=1E-5) # Wait a little bit and make sure that the simulation time has not # changed meanwhile thus confirming that the simulation is on hold. tn.wait(0.1) self.assertEqual(tn.getSimTime(), t) self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), t, delta=1E-5) def test_script_input(self): tree = et.parse(self.sandbox.elude(self.script_path)) input_tag = et.SubElement(tree.getroot(), 'input') input_tag.attrib['port'] = '1138' tree.write(self.sandbox('c1722_1.xml')) fdm = CreateFDM(self.sandbox) fdm.load_script('c1722_1.xml') fdm.run_ic() fdm.hold() tn = TelnetInterface(fdm, 5., 1138) self.sanityCheck(tn)
class TestAccelerometer(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def AddAccelerometersToAircraft(self, script_path): tree, aircraft_name, b = CopyAircraftDef(script_path, self.sandbox) system_tag = et.SubElement(tree.getroot(), 'system') system_tag.attrib['file'] = 'accelerometers' tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) def testOrbit(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) # The time step is too small in ball_orbit so let's increase it to 0.1s # for a quicker run tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find('./run') run_tag.attrib['dt'] = '0.1' tree.write(self.sandbox(script_name)) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_name) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) fdm.run_ic() while fdm.run(): self.assertAlmostEqual(fdm.get_property_value('fcs/accelerometer/X'), 0.0, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('fcs/accelerometer/Y'), 0.0, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('fcs/accelerometer/Z'), 0.0, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('accelerations/a-pilot-x-ft_sec2'), 0.0, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('accelerations/a-pilot-y-ft_sec2'), 0.0, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('accelerations/a-pilot-z-ft_sec2'), 0.0, delta=1E-8) del fdm def testOnGround(self): script_name = 'c1721.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) # Use the standard gravity (i.e. GM/r^2) fdm.set_property_value('simulation/gravity-model', 0) # Simplifies the transformation to compare the accelerometer with the # gravity fdm.set_property_value('ic/psi-true-rad', 0.0) fdm.run_ic() for i in xrange(500): fdm.run() ax = fdm.get_property_value('accelerations/udot-ft_sec2') ay = fdm.get_property_value('accelerations/vdot-ft_sec2') az = fdm.get_property_value('accelerations/wdot-ft_sec2') g = fdm.get_property_value('accelerations/gravity-ft_sec2') theta = fdm.get_property_value('attitude/theta-rad') # There is a lag of one time step between the computations of the # accelerations and the update of the accelerometer fdm.run() fax = fdm.get_property_value('fcs/accelerometer/X') fay = fdm.get_property_value('fcs/accelerometer/Y') faz = fdm.get_property_value('fcs/accelerometer/Z') fax -= ax faz -= az self.assertAlmostEqual(fay, 0.0, delta=1E-6) self.assertAlmostEqual(fax / (g * math.sin(theta)), 1.0, delta=1E-5) self.assertAlmostEqual(faz / (g * math.cos(theta)), -1.0, delta=1E-7) del fdm def testSteadyFlight(self): script_name = 'c1722.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) # Use the standard gravity (i.e. GM/r^2) fdm.set_property_value('simulation/gravity-model', 0) # Simplifies the transformation to compare the accelerometer with the # gravity fdm.set_property_value('ic/psi-true-rad', 0.0) fdm.run_ic() while fdm.get_property_value('simulation/sim-time-sec') <= 0.5: fdm.run() fdm.set_property_value('simulation/do_simple_trim', 1) ax = fdm.get_property_value('accelerations/udot-ft_sec2') ay = fdm.get_property_value('accelerations/vdot-ft_sec2') az = fdm.get_property_value('accelerations/wdot-ft_sec2') g = fdm.get_property_value('accelerations/gravity-ft_sec2') theta = fdm.get_property_value('attitude/theta-rad') # There is a lag of one time step between the computations of the # accelerations and the update of the accelerometer fdm.run() fax = fdm.get_property_value('fcs/accelerometer/X') fay = fdm.get_property_value('fcs/accelerometer/Y') faz = fdm.get_property_value('fcs/accelerometer/Z') fax -= ax fay -= ay faz -= az # Deltas are relaxed because the tolerances of the trimming algorithm # are quite relaxed themselves. self.assertAlmostEqual(faz / (g * math.cos(theta)), -1.0, delta=1E-5) self.assertAlmostEqual(fax / (g * math.sin(theta)), 1.0, delta=1E-5) self.assertAlmostEqual(math.sqrt(fax*fax+fay*fay+faz*faz)/g, 1.0, delta=1E-6) del fdm def testSpinningBodyOnOrbit(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_model('ball') # Offset the CG along Y (by 30") fdm.set_property_value('inertia/pointmass-weight-lbs[1]', 50.0) aircraft_path = self.sandbox.elude(self.sandbox.path_to_jsbsim_file('aircraft', 'ball')) fdm.load_ic(os.path.join(aircraft_path, 'reset00.xml'), False) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) # Set the orientation such that the spinning axis is Z. fdm.set_property_value('ic/phi-rad', 0.5*math.pi) # Set the angular velocities to 0.0 in the ECEF frame. The angular # velocity R_{inertial} will therefore be equal to the Earth rotation # rate 7.292115E-5 rad/sec fdm.set_property_value('ic/p-rad_sec', 0.0) fdm.set_property_value('ic/q-rad_sec', 0.0) fdm.set_property_value('ic/r-rad_sec', 0.0) fdm.run_ic() fax = fdm.get_property_value('fcs/accelerometer/X') fay = fdm.get_property_value('fcs/accelerometer/Y') faz = fdm.get_property_value('fcs/accelerometer/Z') cgy_ft = fdm.get_property_value('inertia/cg-y-in') / 12. omega = 0.00007292115 # Earth rotation rate in rad/sec self.assertAlmostEqual(fdm.get_property_value('accelerations/a-pilot-x-ft_sec2'), fax, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('accelerations/a-pilot-y-ft_sec2'), fay, delta=1E-8) self.assertAlmostEqual(fdm.get_property_value('accelerations/a-pilot-z-ft_sec2'), faz, delta=1E-8) # Acceleration along X should be zero self.assertAlmostEqual(fax, 0.0, delta=1E-8) # Acceleration along Y should be equal to r*omega^2 self.assertAlmostEqual(fay / (cgy_ft * omega * omega), 1.0, delta=1E-7) # Acceleration along Z should be zero self.assertAlmostEqual(faz, 0.0, delta=1E-8)
class TestInputSocket(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml') def tearDown(self): self.sandbox.erase() def sanityCheck(self, _tn): # Check that the connection has been established out = _tn.getOutput() self.assertTrue(string.split(out, '\n')[0] == 'Connected to JSBSim server', msg="Not connected to the JSBSim server.\nGot message '%s' instead" % (out,)) # Check that "help" returns the minimum set of commands that will be # tested self.assertEqual(sorted(map(lambda x: string.strip(string.split(x, '{')[0]), string.split(_tn.sendCommand("help"), '\n')[2:-2])), ['get', 'help', 'hold', 'info', 'iterate', 'quit', 'resume', 'set']) def test_no_input(self): fdm = CreateFDM(self.sandbox) fdm.load_script(self.script_path) fdm.run_ic() fdm.hold() with self.assertRaises(socket.error): TelnetInterface(fdm, 5., 1137) def test_input_socket(self): # The aircraft c172x does not contain an <input> tag so we need # to add one. tree, aircraft_name, b = CopyAircraftDef(self.script_path, self.sandbox) self.root = tree.getroot() input_tag = et.SubElement(self.root, 'input') input_tag.attrib['port'] = '1137' tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(self.script_path) fdm.run_ic() fdm.hold() tn = TelnetInterface(fdm, 5., 1137) self.sanityCheck(tn) # Check the aircraft name and its version msg = string.split(tn.sendCommand("info"), '\n') self.assertEqual(string.strip(string.split(msg[2], ':')[1]), string.strip(self.root.attrib['name'])) self.assertEqual(string.strip(string.split(msg[1], ':')[1]), string.strip(self.root.attrib['version'])) # Check that the simulation time is 0.0 self.assertEqual(float(string.strip(string.split(msg[3], ':')[1])), 0.0) self.assertEqual(tn.getSimTime(), 0.0) self.assertEqual(tn.getPropertyValue("simulation/sim-time-sec"), 0.0) # Check that 'iterate' iterates the correct number of times tn.sendCommand("iterate 19") self.assertEqual(tn.getSimTime(), 19. * tn.getDeltaT()) self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), tn.getSimTime(), delta=1E-5) # Wait a little bit and make sure that the simulation time has not # changed meanwhile thus confirming that the simulation is on hold. tn.wait(0.1) self.assertEqual(tn.getSimTime(), 19. * tn.getDeltaT()) self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), tn.getSimTime(), delta=1E-5) # Modify the tank[0] contents via the "send" command half_contents = 0.5 * tn.getPropertyValue("propulsion/tank/contents-lbs") tn.sendCommand("set propulsion/tank/contents-lbs " + str(half_contents)) self.assertEqual(tn.getPropertyValue("propulsion/tank/contents-lbs"), half_contents) # Check the resume/hold commands tn.setRealTime(True) t = tn.getSimTime() tn.sendCommand("resume") tn.wait(0.5) self.assertNotEqual(tn.getSimTime(), t) tn.wait(0.5) tn.sendCommand("hold") tn.setRealTime(False) t = tn.getSimTime() self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), t, delta=1E-5) # Wait a little bit and make sure that the simulation time has not # changed meanwhile thus confirming that the simulation is on hold. tn.wait(0.1) self.assertEqual(tn.getSimTime(), t) self.assertAlmostEqual(tn.getPropertyValue("simulation/sim-time-sec"), t, delta=1E-5) def test_script_input(self): tree = et.parse(self.sandbox.elude(self.script_path)) input_tag = et.SubElement(tree.getroot(), 'input') input_tag.attrib['port'] = '1138' tree.write(self.sandbox('c1722_1.xml')) fdm = CreateFDM(self.sandbox) fdm.load_script('c1722_1.xml') fdm.run_ic() fdm.hold() tn = TelnetInterface(fdm, 5., 1138) self.sanityCheck(tn)
class TestInputSocket(unittest.TestCase): def setUp(self): self.sandbox = SandBox() script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml') # The aircraft c172x does not contain an <input> tag so we need # to add one. tree, aircraft_name, b = CopyAircraftDef(script_path, self.sandbox) self.root = tree.getroot() input_tag = et.SubElement(self.root, 'input') input_tag.attrib['port']='1137' tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) self.fdm = CreateFDM(self.sandbox) self.fdm.set_aircraft_path('aircraft') self.fdm.load_script(script_path) self.fdm.run_ic() self.fdm.hold() # Execute JSBSim in a separate thread self.cond = threading.Condition() self.thread = JSBSimThread(self.fdm, self.cond, 5., time.time()) self.thread.start() # Wait for the thread to be started before connecting a telnet session self.cond.acquire() self.cond.wait() self.tn = telnetlib.Telnet("localhost", 1137) self.cond.release() def tearDown(self): self.tn.close() self.thread.quit = True self.thread.join() self.sandbox.erase() def sendCommand(self, command): self.cond.acquire() self.tn.write(command+"\n") # Wait for a time step to be executed before reading the output from telnet self.cond.wait() msg = self.tn.read_very_eager() self.cond.release() self.thread.join(0.1) return msg def getSimTime(self): self.cond.acquire() self.cond.wait() t = self.fdm.get_sim_time() self.cond.release() return t def getDeltaT(self): self.cond.acquire() self.cond.wait() dt = self.fdm.get_delta_t() self.cond.release() return dt def getPropertyValue(self, property): msg = string.split(self.sendCommand("get "+property),'\n') return float(string.split(msg[0], '=')[1]) def test_input_socket(self): # Check that the connection has been established self.cond.acquire() self.cond.wait() out = self.tn.read_very_eager() self.cond.release() self.assertTrue(string.split(out, '\n')[0] == 'Connected to JSBSim server', msg="Not connected to the JSBSim server.\nGot message '%s' instead" % (out,)) # Check that "help" returns the minimum set of commands that will be # tested self.assertEqual(sorted(map(lambda x : string.strip(string.split(x, '{')[0]), string.split(self.sendCommand("help"), '\n')[2:-2])), ['get', 'help', 'hold', 'info', 'iterate', 'quit', 'resume', 'set']) # Check the aircraft name and its version msg = string.split(self.sendCommand("info"), '\n') self.assertEqual(string.strip(string.split(msg[2], ':')[1]), string.strip(self.root.attrib['name'])) self.assertEqual(string.strip(string.split(msg[1], ':')[1]), string.strip(self.root.attrib['version'])) # Check that the simulation time is 0.0 self.assertEqual(float(string.strip(string.split(msg[3], ':')[1])), 0.0) self.assertEqual(self.getSimTime(), 0.0) self.assertEqual(self.getPropertyValue("simulation/sim-time-sec"), 0.0) # Check that 'iterate' iterates the correct number of times self.sendCommand("iterate 19") self.assertEqual(self.getSimTime(), 19. * self.getDeltaT()) self.assertAlmostEqual(self.getPropertyValue("simulation/sim-time-sec"), self.getSimTime(), delta=1E-5) # Wait a little bit and make sure that the simulation time has not # changed meanwhile thus confirming that the simulation is on hold. self.thread.join(0.1) self.assertEqual(self.getSimTime(), 19. * self.getDeltaT()) self.assertAlmostEqual(self.getPropertyValue("simulation/sim-time-sec"), self.getSimTime(), delta=1E-5) # Modify the tank[0] contents via the "send" command half_contents = 0.5 * self.getPropertyValue("propulsion/tank/contents-lbs") self.sendCommand("set propulsion/tank/contents-lbs "+ str(half_contents)) self.cond.acquire() self.cond.wait() self.assertEqual(self.fdm.get_property_value("propulsion/tank/contents-lbs"), half_contents) self.cond.release() # Check the resume/hold commands self.thread.realTime = True t = self.getSimTime() self.sendCommand("resume") self.thread.join(0.5) self.assertNotEqual(self.getSimTime(), t) self.thread.join(0.5) self.sendCommand("hold") self.thread.realTime = False t = self.getSimTime() self.assertAlmostEqual(self.getPropertyValue("simulation/sim-time-sec"), t, delta=1E-5) # Wait a little bit and make sure that the simulation time has not # changed meanwhile thus confirming that the simulation is on hold. self.thread.join(0.1) self.assertEqual(self.getSimTime(), t) self.assertAlmostEqual(self.getPropertyValue("simulation/sim-time-sec"), t, delta=1E-5)
class TestPointMassInertia(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_point_mass_inertia(self): script_path = self.sandbox.path_to_jsbsim_file('scripts', 'J2460.xml') fdm = CreateFDM(self.sandbox) fdm.set_output_directive(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.load_script(script_path) fdm.run_ic() ExecuteUntil(fdm, 50.0) ref = Table() ref.ReadCSV(self.sandbox("output.csv")) tree, aircraft_name, path_to_jsbsim_aircrafts = CopyAircraftDef(script_path, self.sandbox) pointmass_element = tree.getroot().find('mass_balance/pointmass//form/..') weight_element = pointmass_element.find('weight') weight = float(weight_element.text) form_element = pointmass_element.find('form') radius_element = form_element.find('radius') radius, length = (0.0, 0.0) if radius_element is not None: radius = float(radius_element.text) length_element = form_element.find('length') if length_element is not None: length = float(length_element.text) shape = form_element.attrib['shape'] pointmass_element.remove(form_element) inertia = numpy.zeros((3,3)) if string.strip(shape) == 'tube': inertia[0,0] = radius * radius inertia[1,1] = (6.0 * inertia[0,0] + length * length) / 12.0 inertia[2,2] = inertia[1,1] inertia = inertia * weight / 32.174049 # conversion between slug and lb ixx_element = et.SubElement(pointmass_element, 'ixx') ixx_element.text = str(inertia[0,0]) iyy_element = et.SubElement(pointmass_element, 'iyy') iyy_element.text = str(inertia[1,1]) izz_element = et.SubElement(pointmass_element, 'izz') izz_element.text = str(inertia[2,2]) tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.set_output_directive(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.load_script(script_path) fdm.run_ic() ExecuteUntil(fdm, 50.0) mod = Table() mod.ReadCSV(self.sandbox("output.csv")) diff = ref.compare(mod) self.assertTrue(diff.empty(), msg='\n'+repr(diff))
class TestInitialConditions(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_initial_conditions(self): prop_output_to_CSV = ['velocities/vc-kts'] # A dictionary that contains the XML tags to extract from the IC file # along with the name of the properties that contain the values # extracted from the IC file. vars = [{'tag': 'vt', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/vt-fps', 'prop': 'velocities/vt-fps', 'CSV_header': 'V_{Total} (ft/s)'}, {'tag': 'vc', 'unit': convtokts, 'default_unit': 'KTS', 'ic_prop': 'ic/vc-kts', 'prop': 'velocities/vc-kts', 'CSV_header': '/fdm/jsbsim/velocities/vc-kts'}, {'tag': 'ubody', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/u-fps', 'prop': 'velocities/u-fps', 'CSV_header': 'UBody'}, {'tag': 'vbody', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/v-fps', 'prop': 'velocities/v-fps', 'CSV_header': 'VBody'}, {'tag': 'wbody', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/w-fps', 'prop': 'velocities/w-fps', 'CSV_header': 'WBody'}, {'tag': 'vnorth', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/vn-fps', 'prop': 'velocities/v-north-fps', 'CSV_header': 'V_{North} (ft/s)'}, {'tag': 'veast', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/ve-fps', 'prop': 'velocities/v-east-fps', 'CSV_header': 'V_{East} (ft/s)'}, {'tag': 'vdown', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/vd-fps', 'prop': 'velocities/v-down-fps', 'CSV_header': 'V_{Down} (ft/s)'}, {'tag': 'latitude', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/lat-gc-deg', 'prop': 'position/lat-gc-deg', 'CSV_header': 'Latitude (deg)'}, {'tag': 'longitude', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/long-gc-deg', 'prop': 'position/long-gc-deg', 'CSV_header': 'Longitude (deg)'}, {'tag': 'altitude', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/h-agl-ft', 'prop': 'position/h-agl-ft', 'CSV_header': 'Altitude AGL (ft)'}, {'tag': 'altitudeAGL', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/h-agl-ft', 'prop': 'position/h-agl-ft', 'CSV_header': 'Altitude AGL (ft)'}, {'tag': 'altitudeMSL', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/h-sl-ft', 'prop': 'position/h-sl-ft', 'CSV_header': 'Altitude ASL (ft)'}, {'tag': 'phi', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/phi-deg', 'prop': 'attitude/phi-deg', 'CSV_header': 'Phi (deg)'}, {'tag': 'theta', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/theta-deg', 'prop': 'attitude/theta-deg', 'CSV_header': 'Theta (deg)'}, {'tag': 'psi', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/psi-true-deg', 'prop': 'attitude/psi-deg', 'CSV_header': 'Psi (deg)'}, {'tag': 'elevation', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/terrain-elevation-ft', 'prop': 'position/terrain-elevation-asl-ft', 'CSV_header': 'Terrain Elevation (ft)'}] script_path = self.sandbox.path_to_jsbsim_file('scripts') for f in os.listdir(self.sandbox.elude(script_path)): # TODO These scripts need some further investigation if f in ('ZLT-NT-moored-1.xml', '737_cruise_steady_turn_simplex.xml'): continue fullpath = os.path.join(self.sandbox.elude(script_path), f) # Does f contains a JSBSim script ? if not CheckXMLFile(fullpath, 'runscript'): continue # Read the IC file name from the script tree = et.parse(fullpath) root = tree.getroot() use_tag = root.find('use') aircraft_name = use_tag.attrib['aircraft'] aircraft_path = os.path.join('aircraft', aircraft_name) path_to_jsbsim_aircrafts = self.sandbox.elude(self.sandbox.path_to_jsbsim_file(aircraft_path)) IC_file = append_xml(use_tag.attrib['initialize']) IC_tree = et.parse(os.path.join(path_to_jsbsim_aircrafts, IC_file)) IC_root = IC_tree.getroot() # Only testing version 1.0 of init files if 'version' in IC_root.attrib: if float(IC_root.attrib['version']) == 2.0: continue # Extract the IC values from XML for var in vars: var_tag = IC_root.find('./'+var['tag']) var['specified'] = var_tag is not None if not var['specified']: var['value'] = 0.0 continue var['value'] = float(var_tag.text) if 'unit' in var_tag.attrib: conv = var['unit'][var_tag.attrib['unit']] else: conv = var['unit'][var['default_unit']] var['value'] *= conv # Generate a CSV file to check that it is correctly initialized # with the initial values output_tag = et.SubElement(root, 'output') output_tag.attrib['name'] = 'check_csv_values.csv' output_tag.attrib['type'] = 'CSV' output_tag.attrib['rate'] = '10' position_tag = et.SubElement(output_tag, 'position') position_tag.text = 'ON' velocities_tag = et.SubElement(output_tag, 'velocities') velocities_tag.text = 'ON' for props in prop_output_to_CSV: property_tag = et.SubElement(output_tag, 'property') property_tag.text = props tree.write(self.sandbox(f)) # Initialize the script fdm = CreateFDM(self.sandbox) fdm.load_script(f) fdm.run_ic() # Sanity check, we just initialized JSBSim with the ICs, the time # must be set to 0.0 self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) # Check that the properties (including in 'ic/') have been # correctly initialized (i.e. that they contain the value read from # the XML file). for var in vars: if not var['specified']: continue value = var['value'] prop = fdm.get_property_value(var['ic_prop']) if var['tag'] == 'psi': if abs(prop - 360.0) <= 1E-8: prop = 0.0 self.assertAlmostEqual(value, prop, delta=1E-7, msg="In script %s: %s should be %f but found %f" % (f, var['tag'], value, prop)) prop = fdm.get_property_value(var['prop']) if var['tag'] == 'psi': if abs(prop - 360.0) <= 1E-8: prop = 0.0 self.assertAlmostEqual(value, prop, delta=1E-7, msg="In script %s: %s should be %f but found %f" % (f, var['tag'], value, prop)) # Execute the first second of the script. This is to make sure that # the CSV file is open and the ICs have been written in it. try: ExecuteUntil(fdm, 1.0) except RuntimeError as e: if e.args[0] == 'Trim Failed': self.fail("Trim failed in script %s" % (f,)) else: raise # Copies the CSV file content in a table ref = pd.read_csv(self.sandbox('check_csv_values.csv')) # Sanity check: make sure that the time step 0.0 has been copied in # the CSV file. self.assertEqual(ref['Time'][0], 0.0) # Check that the value in the CSV file equals the value read from # the IC file. for var in vars: if not var['specified']: continue value = var['value'] csv_value = ref[var['CSV_header']][0] if var['tag'] == 'psi': if abs(csv_value - 360.0) <= 1E-8: csv_value = 0.0 self.assertAlmostEqual(value, csv_value, delta=1E-7, msg="In script %s: %s should be %f but found %f" % (f, var['tag'], value, csv_value)) del fdm
class TestSimTimeReset(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_no_script(self): fdm = CreateFDM(self.sandbox) aircraft_path = self.sandbox.path_to_jsbsim_file('aircraft') fdm.set_aircraft_path(aircraft_path) fdm.load_model('c172x') aircraft_path = os.path.join(self.sandbox.elude(aircraft_path), 'c172x') fdm.load_ic(os.path.join(aircraft_path, 'reset01.xml'), False) fdm.run_ic() self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) ExecuteUntil(fdm, 5.0) t = fdm.get_property_value('simulation/sim-time-sec') fdm.set_property_value('simulation/do_simple_trim', 1) self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), t) fdm.reset_to_initial_conditions(1) self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) del fdm def test_script_start_time_0(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) fdm = CreateFDM(self.sandbox) fdm.load_script(script_path) fdm.run_ic() self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) ExecuteUntil(fdm, 5.0) fdm.reset_to_initial_conditions(1) self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) del fdm def test_script_start_time(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find('./run') run_tag.attrib['start'] = '1.2' tree.write(self.sandbox(script_name)) fdm = CreateFDM(self.sandbox) fdm.load_script(script_name) fdm.run_ic() self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 1.2) ExecuteUntil(fdm, 5.0) fdm.reset_to_initial_conditions(1) self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 1.2) del fdm def test_script_no_start_time(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find('./run') # Remove the parameter 'start' from the tag <run> del run_tag.attrib['start'] tree.write(self.sandbox(script_name)) fdm = CreateFDM(self.sandbox) fdm.load_script(script_name) fdm.run_ic() self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) ExecuteUntil(fdm, 5.0) fdm.reset_to_initial_conditions(1) self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) del fdm
class CheckFGBug1503(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.script_path = self.sandbox.path_to_jsbsim_file("scripts", "c1724.xml") # Since we will alter the aircraft definition file, we need make a copy # of it and of all the files it is refering to. self.tree, self.aircraft_name, self.path_to_jsbsim_aircrafts = CopyAircraftDef(self.script_path, self.sandbox) def tearDown(self): self.sandbox.erase() def ScriptExecution(self, fdm, time_limit=1e9): fdm.load_script(self.script_path) fdm.run_ic() while fdm.run() and fdm.get_sim_time() < time_limit: aileron_pos = fdm.get_property_value("fcs/left-aileron-pos-rad") self.assertTrue( aileron_pos == 0.0, msg="Failed running the script %s at time step %f\nProperty fcs/left-aileron-pos-rad is non-zero (%f)" % (self.script_path, fdm.get_sim_time(), aileron_pos), ) def CheckRateValue(self, fdm, output_prop, rate_value): aileron_course = [] t0 = fdm.get_sim_time() while fdm.run() and fdm.get_sim_time() <= t0 + 1.0: aileron_course += [(fdm.get_sim_time(), fdm.get_property_value(output_prop))] # Thanks to a linear regression on the values, we can check that the # value is following a slope equal to the rate limit. The correlation # coefficient r_value is also checked to verify that the output is # evolving linearly. slope, intercept, r_value, p_value, std_err = stats.linregress(aileron_course) self.assertTrue( abs(slope - rate_value) < 1e-9 and abs(1.0 - abs(r_value)) < 1e-9, msg="The actuator rate is not linear" ) def CheckRateLimit(self, input_prop, output_prop, incr_limit, decr_limit): fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path("aircraft") self.ScriptExecution(fdm, 1.0) fdm.set_property_value(input_prop, 1.0) self.CheckRateValue(fdm, output_prop, incr_limit) fdm.set_property_value(input_prop, 0.0) self.CheckRateValue(fdm, output_prop, decr_limit) # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm def test_regression_bug_1503(self): # First, the execution time of the script c1724.xml is measured. It will # be used as a reference to check if JSBSim hangs or not. fdm = CreateFDM(self.sandbox) start_time = time.time() self.ScriptExecution(fdm) exec_time = time.time() - start_time del fdm # Now the copy of the aircraft definition file will be altered: the # <rate_limit> element is split in two: one with the 'decr' sense, the # other with 'incr' sense. actuator_element = self.tree.getroot().find("flight_control/channel/actuator//rate_limit/..") rate_element = actuator_element.find("rate_limit") rate_element.attrib["sense"] = "decr" new_rate_element = et.SubElement(actuator_element, "rate_limit") new_rate_element.attrib["sense"] = "incr" new_rate_element.text = str(float(rate_element.text) * 0.5) self.tree.write(self.sandbox("aircraft", self.aircraft_name, self.aircraft_name + ".xml")) # Run the script with the modified aircraft fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path("aircraft") # A new process is created that launches the script. We wait for 10 # times the reference execution time for the script completion. Beyond # that time, if the process is not completed, it is terminated and the # test is failed. p = Process(target=self.ScriptExecution, args=(fdm,)) p.start() p.join(exec_time * 10.0) # Wait 10 times the reference time alive = p.is_alive() if alive: p.terminate() self.assertFalse(alive, msg="The script has hanged") def test_actuator_rate_from_property(self): # Second part of the test. # ####################### # # The test is run again but this time, <rate_limit> will be read from a # property instead of being read from a value hard coded in the aircraft # definition file. It has been reported in the bug 1503 of FlightGear # that for such a configuration the <actuator> output is constantly # increasing even if the input is null. For this script the <actuator> # output is stored in the property fcs/left-aileron-pos-rad. The # function ScriptExecution will monitor that property and if it changes # then the test is failed. tree = et.parse(os.path.join(self.path_to_jsbsim_aircrafts, self.aircraft_name + ".xml")) actuator_element = tree.getroot().find("flight_control/channel/actuator//rate_limit/..") rate_element = actuator_element.find("rate_limit") flight_control_element = tree.getroot().find("flight_control") property = et.SubElement(flight_control_element, "property") property.text = "fcs/rate-limit-value" property.attrib["value"] = rate_element.text actuator_element = flight_control_element.find("channel/actuator//rate_limit/..") rate_element = actuator_element.find("rate_limit") rate_element.attrib["sense"] = "decr" rate_element.text = property.text new_rate_element = et.SubElement(actuator_element, "rate_limit") new_rate_element.attrib["sense"] = "incr" new_rate_element.text = rate_element.text tree.write(self.sandbox("aircraft", self.aircraft_name, self.aircraft_name + ".xml")) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path("aircraft") self.ScriptExecution(fdm) del fdm def test_actuator_rate_is_linear(self): # Third part of the test. ######################## # # The test is run again but this time we are checking that rate_limit # drives the actuator output value as expected. The idea is to store the # output value of the actuator output vs the time and check with a # linear regression that # 1. The actuator output value is evolving linearly # 2. The slope of the actuator output is equal to the rate_limit value # The test is run with the rate_limit given by a value, a property, # different values of the ascending and descending rates and a number of # combinations thereof. # The aircraft file definition is modified such that the actuator # element input is driven by a unique property. The name of this unique # property is built in the variable 'input_prop' below. When setting # that property to 1.0 (resp. -1.0) the ascending (resp. descending) # rate is triggered. tree = et.parse(os.path.join(self.path_to_jsbsim_aircrafts, self.aircraft_name + ".xml")) flight_control_element = tree.getroot().find("flight_control") actuator_element = flight_control_element.find("channel/actuator//rate_limit/..") # Remove the hysteresis. We want to make sure we are measuring the # rate_limit and just that. hysteresis_element = actuator_element.find("hysteresis") actuator_element.remove(hysteresis_element) input_element = actuator_element.find("input") input_prop = string.split(actuator_element.attrib["name"], "-") input_prop[-1] = "input" input_prop = string.join(input_prop, "-") input_element.text = input_prop output_element = actuator_element.find("output") output_prop = string.strip(output_element.text) # Add the new properties to <flight_control> so that we can make # reference to them without JSBSim complaining property = et.SubElement(flight_control_element, "property") property.text = input_prop property.attrib["value"] = "0.0" property = et.SubElement(flight_control_element, "property") property.text = "fcs/rate-limit-value" property.attrib["value"] = "0.15" property = et.SubElement(flight_control_element, "property") property.text = "fcs/rate-limit-value2" property.attrib["value"] = "0.05" # First check with rate_limit set to 0.1 rate_element = actuator_element.find("rate_limit") rate_element.text = "0.1" output_file = self.sandbox("aircraft", self.aircraft_name, self.aircraft_name + ".xml") tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.1, -0.1) # Check when rate_limit is set by the property 'fcs/rate-limit-value' tree = et.parse(output_file) flight_control_element = tree.getroot().find("flight_control") actuator_element = flight_control_element.find("channel/actuator//rate_limit/..") rate_element = actuator_element.find("rate_limit") rate_element.text = "fcs/rate-limit-value" tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.15, -0.15) # Checking when the ascending and descending rates are different. # First with the 2 rates set by hard coded values (0.1 and 0.2 respectively) rate_element.attrib["sense"] = "decr" rate_element.text = "0.1" new_rate_element = et.SubElement(actuator_element, "rate_limit") new_rate_element.attrib["sense"] = "incr" new_rate_element.text = "0.2" tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.2, -0.1) # Check when the descending rate is set by a property and the ascending rate is # set by a value. rate_element.text = "fcs/rate-limit-value" tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.2, -0.15) # Check when the ascending rate is set by a property and the descending # rate is set by a value. rate_element.text = "0.1" new_rate_element.text = "fcs/rate-limit-value" tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.15, -0.1) # Check when the ascending and descending rates are set by properties rate_element.text = "fcs/rate-limit-value2" tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.15, -0.05)
class TestInitialConditions(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_initial_conditions(self): # A dictionary that contains the XML tags to extract from the IC file # along with the name of the properties that contain the values # extracted from the IC file. vars = [{'tag': 'vt', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/vt-fps', 'prop': 'velocities/vt-fps', 'CSV_header': 'V_{Total} (ft/s)'}, {'tag': 'ubody', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/u-fps', 'prop': 'velocities/u-fps', 'CSV_header': 'UBody'}, {'tag': 'vbody', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/v-fps', 'prop': 'velocities/v-fps', 'CSV_header': 'VBody'}, {'tag': 'wbody', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/w-fps', 'prop': 'velocities/w-fps', 'CSV_header': 'WBody'}, {'tag': 'vnorth', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/vn-fps', 'prop': 'velocities/v-north-fps', 'CSV_header': 'V_{North} (ft/s)'}, {'tag': 'veast', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/ve-fps', 'prop': 'velocities/v-east-fps', 'CSV_header': 'V_{East} (ft/s)'}, {'tag': 'vdown', 'unit': convtofps, 'default_unit': 'FT/SEC', 'ic_prop': 'ic/vd-fps', 'prop': 'velocities/v-down-fps', 'CSV_header': 'V_{Down} (ft/s)'}, {'tag': 'latitude', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/lat-gc-deg', 'prop': 'position/lat-gc-deg', 'CSV_header': 'Latitude (deg)'}, {'tag': 'longitude', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/long-gc-deg', 'prop': 'position/long-gc-deg', 'CSV_header': 'Longitude (deg)'}, {'tag': 'altitude', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/h-agl-ft', 'prop': 'position/h-agl-ft', 'CSV_header': 'Altitude AGL (ft)'}, {'tag': 'altitudeAGL', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/h-agl-ft', 'prop': 'position/h-agl-ft', 'CSV_header': 'Altitude AGL (ft)'}, {'tag': 'altitudeMSL', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/h-sl-ft', 'prop': 'position/h-sl-ft', 'CSV_header': 'Altitude ASL (ft)'}, {'tag': 'phi', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/phi-deg', 'prop': 'attitude/phi-deg', 'CSV_header': 'Phi (deg)'}, {'tag': 'theta', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/theta-deg', 'prop': 'attitude/theta-deg', 'CSV_header': 'Theta (deg)'}, {'tag': 'psi', 'unit': convtodeg, 'default_unit': 'RAD', 'ic_prop': 'ic/psi-true-deg', 'prop': 'attitude/psi-deg', 'CSV_header': 'Psi (deg)'}, {'tag': 'elevation', 'unit': convtoft, 'default_unit': 'FT', 'ic_prop': 'ic/terrain-elevation-ft', 'prop': 'position/terrain-elevation-asl-ft', 'CSV_header': 'Terrain Elevation (ft)'}] script_path = self.sandbox.path_to_jsbsim_file('scripts') for f in os.listdir(self.sandbox.elude(script_path)): # TODO These scripts need some further investigation if f in ('ZLT-NT-moored-1.xml',): continue fullpath = os.path.join(self.sandbox.elude(script_path), f) # Does f contains a JSBSim script ? if not CheckXMLFile(fullpath, 'runscript'): continue # Read the IC file name from the script tree = et.parse(fullpath) root = tree.getroot() use_tag = root.find('use') aircraft_name = use_tag.attrib['aircraft'] aircraft_path = os.path.join('aircraft', aircraft_name) path_to_jsbsim_aircrafts = self.sandbox.elude(self.sandbox.path_to_jsbsim_file(aircraft_path)) IC_file = append_xml(use_tag.attrib['initialize']) IC_tree = et.parse(os.path.join(path_to_jsbsim_aircrafts, IC_file)) IC_root = IC_tree.getroot() # Only testing version 1.0 of init files if 'version' in IC_root.attrib: if float(IC_root.attrib['version']) == 2.0: continue # Extract the IC values from XML for var in vars: var_tag = IC_root.find('./'+var['tag']) var['specified'] = var_tag is not None if not var['specified']: var['value'] = 0.0 continue var['value'] = float(var_tag.text) if 'unit' in var_tag.attrib: conv = var['unit'][var_tag.attrib['unit']] else: conv = var['unit'][var['default_unit']] var['value'] *= conv # Generate a CSV file to check that it is correctly initialized with # the initial values output_tag = et.SubElement(root, 'output') output_tag.attrib['name'] = 'check_csv_values.csv' output_tag.attrib['type'] = 'CSV' output_tag.attrib['rate'] = '10' position_tag = et.SubElement(output_tag, 'position') position_tag.text = 'ON' velocities_tag = et.SubElement(output_tag, 'velocities') velocities_tag.text = 'ON' tree.write(self.sandbox(f)) # Initialize the script fdm = CreateFDM(self.sandbox) fdm.load_script(f) fdm.run_ic() # Sanity check, we just initialized JSBSim with the ICs, the time # must be set to 0.0 self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) # Check that the properties (including in 'ic/') have been correctly # initialized (i.e. that they contain the value read from the XML # file). for var in vars: if not var['specified']: continue value = var['value'] prop = fdm.get_property_value(var['ic_prop']) if var['tag'] == 'psi': if abs(prop - 360.0) <= 1E-8: prop = 0.0 self.assertAlmostEqual(value, prop, delta=1E-7, msg="In script %s: %s should be %f but found %f" % (f, var['tag'], value, prop)) prop = fdm.get_property_value(var['prop']) if var['tag'] == 'psi': if abs(prop - 360.0) <= 1E-8: prop = 0.0 self.assertAlmostEqual(value, prop, delta=1E-7, msg="In script %s: %s should be %f but found %f" % (f, var['tag'], value, prop)) # Execute the first second of the script. This is to make sure that # the CSV file is open and the ICs have been written in it. ExecuteUntil(fdm, 1.0) # Copies the CSV file content in a table ref = pd.read_csv(self.sandbox('check_csv_values.csv')) # Sanity check: make sure that the time step 0.0 has been copied in # the CSV file. self.assertEqual(ref['Time'][0], 0.0) # Check that the value in the CSV file equals the value read from # the IC file. for var in vars: if not var['specified']: continue value = var['value'] csv_value = ref[var['CSV_header']][0] if var['tag'] == 'psi': if abs(csv_value - 360.0) <= 1E-8: csv_value = 0.0 self.assertAlmostEqual(value, csv_value, delta=1E-7, msg="In script %s: %s should be %f but found %f" % (f, var['tag'], value, csv_value)) del fdm
class TestFuelTanksInertia(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_fuel_tanks_inertia(self): script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml') # The aircraft c172x does not contain an <inertia_factor> tag so we need # to add one. tree, aircraft_name, b = CopyAircraftDef(script_path, self.sandbox) tank_tag = tree.getroot().find('./propulsion/tank') inertia_factor = et.SubElement(tank_tag, 'inertia_factor') inertia_factor.text = '1.0' tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) fdm.run_ic() contents0 = fdm.get_property_value('propulsion/tank/contents-lbs') ixx0 = fdm.get_property_value('propulsion/tank/local-ixx-slug_ft2') iyy0 = fdm.get_property_value('propulsion/tank/local-iyy-slug_ft2') izz0 = fdm.get_property_value('propulsion/tank/local-izz-slug_ft2') # Remove half of the tank contents and check that the inertias are # updated accordingly fdm.set_property_value('propulsion/tank/contents-lbs', 0.5*contents0) contents = fdm.get_property_value('propulsion/tank/contents-lbs') ixx = fdm.get_property_value('propulsion/tank/local-ixx-slug_ft2') iyy = fdm.get_property_value('propulsion/tank/local-iyy-slug_ft2') izz = fdm.get_property_value('propulsion/tank/local-izz-slug_ft2') self.assertTrue(abs(contents-0.5*contents0) < 1E-7, msg="The tank content (%f lbs) should be %f lbs" % (contents, 0.5*contents0)) self.assertTrue(abs(ixx-0.5*ixx0) < 1E-7, msg="The tank inertia Ixx (%f slug*ft^2) should be %f slug*ft^2" % (ixx, 0.5*ixx0)) self.assertTrue(abs(iyy-0.5*iyy0) < 1E-7, msg="The tank inertia Iyy (%f slug*ft^2) should be %f slug*ft^2" % (iyy, 0.5*iyy0)) self.assertTrue(abs(izz-0.5*izz0) < 1E-7, msg="The tank inertia Izz (%f slug*ft^2) should be %f slug*ft^2" % (izz, 0.5*izz0)) # Execute the script and check that the fuel inertias have been updated # along with the consumption. ExecuteUntil(fdm, 200.0) contents = fdm.get_property_value('propulsion/tank/contents-lbs') ixx = fdm.get_property_value('propulsion/tank/local-ixx-slug_ft2') iyy = fdm.get_property_value('propulsion/tank/local-iyy-slug_ft2') izz = fdm.get_property_value('propulsion/tank/local-izz-slug_ft2') contents_ratio = contents / contents0 ixx_ratio = ixx / ixx0 iyy_ratio = iyy / iyy0 izz_ratio = izz / izz0 self.assertTrue(abs(contents_ratio - ixx_ratio) < 1E-7, msg="Ixx does not vary as the tank content does\nIxx ratio=%f\nContents ratio=%f" % (ixx_ratio, contents_ratio)) self.assertTrue(abs(contents_ratio - iyy_ratio) < 1E-7, msg="Iyy does not vary as the tank content does\nIyy ratio=%f\nContents ratio=%f" % (iyy_ratio, contents_ratio)) self.assertTrue(abs(contents_ratio - izz_ratio) < 1E-7, msg="Izz does not vary as the tank content does\nIzz ratio=%f\nContents ratio=%f" % (izz_ratio, contents_ratio))
class CheckFGBug1503(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.script_path = self.sandbox.path_to_jsbsim_file( 'scripts', 'c1724.xml') # Since we will alter the aircraft definition file, we need make a copy # of it and of all the files it is refering to. self.tree, self.aircraft_name, self.path_to_jsbsim_aircrafts = CopyAircraftDef( self.script_path, self.sandbox) def tearDown(self): self.sandbox.erase() def ScriptExecution(self, fdm, time_limit=1E+9): fdm.load_script(self.script_path) fdm.run_ic() while fdm.run() and fdm.get_sim_time() < time_limit: aileron_pos = fdm.get_property_value('fcs/left-aileron-pos-rad') self.assertTrue( aileron_pos == 0.0, msg= "Failed running the script %s at time step %f\nProperty fcs/left-aileron-pos-rad is non-zero (%f)" % (self.script_path, fdm.get_sim_time(), aileron_pos)) def CheckRateValue(self, fdm, output_prop, rate_value): aileron_course = [] t0 = fdm.get_sim_time() while fdm.run() and fdm.get_sim_time() <= t0 + 1.0: aileron_course += [(fdm.get_sim_time(), fdm.get_property_value(output_prop))] # Thanks to a linear regression on the values, we can check that the # value is following a slope equal to the rate limit. The correlation # coefficient r_value is also checked to verify that the output is # evolving linearly. slope, intercept, r_value, p_value, std_err = stats.linregress( aileron_course) self.assertTrue(abs(slope - rate_value) < 1E-9 and abs(1.0 - abs(r_value)) < 1E-9, msg="The actuator rate is not linear") def CheckRateLimit(self, input_prop, output_prop, incr_limit, decr_limit): fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') self.ScriptExecution(fdm, 1.0) fdm.set_property_value(input_prop, 1.0) self.CheckRateValue(fdm, output_prop, incr_limit) fdm.set_property_value(input_prop, 0.0) self.CheckRateValue(fdm, output_prop, decr_limit) # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm def test_regression_bug_1503(self): # First, the execution time of the script c1724.xml is measured. It will # be used as a reference to check if JSBSim hangs or not. fdm = CreateFDM(self.sandbox) start_time = time.time() self.ScriptExecution(fdm) exec_time = time.time() - start_time del fdm # Now the copy of the aircraft definition file will be altered: the # <rate_limit> element is split in two: one with the 'decr' sense, the # other with 'incr' sense. actuator_element = self.tree.getroot().find( 'flight_control/channel/actuator//rate_limit/..') rate_element = actuator_element.find('rate_limit') rate_element.attrib['sense'] = 'decr' new_rate_element = et.SubElement(actuator_element, 'rate_limit') new_rate_element.attrib['sense'] = 'incr' new_rate_element.text = str(float(rate_element.text) * 0.5) self.tree.write( self.sandbox('aircraft', self.aircraft_name, self.aircraft_name + '.xml')) # Run the script with the modified aircraft fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') # A new process is created that launches the script. We wait for 10 # times the reference execution time for the script completion. Beyond # that time, if the process is not completed, it is terminated and the # test is failed. p = Process(target=self.ScriptExecution, args=(fdm, )) p.start() p.join(exec_time * 10.0) # Wait 10 times the reference time alive = p.is_alive() if alive: p.terminate() self.assertFalse(alive, msg="The script has hanged") def test_actuator_rate_from_property(self): # Second part of the test. # ####################### # # The test is run again but this time, <rate_limit> will be read from a # property instead of being read from a value hard coded in the aircraft # definition file. It has been reported in the bug 1503 of FlightGear # that for such a configuration the <actuator> output is constantly # increasing even if the input is null. For this script the <actuator> # output is stored in the property fcs/left-aileron-pos-rad. The # function ScriptExecution will monitor that property and if it changes # then the test is failed. tree = et.parse( os.path.join(self.path_to_jsbsim_aircrafts, self.aircraft_name + '.xml')) actuator_element = tree.getroot().find( 'flight_control/channel/actuator//rate_limit/..') rate_element = actuator_element.find('rate_limit') flight_control_element = tree.getroot().find('flight_control') property = et.SubElement(flight_control_element, 'property') property.text = 'fcs/rate-limit-value' property.attrib['value'] = rate_element.text actuator_element = flight_control_element.find( 'channel/actuator//rate_limit/..') rate_element = actuator_element.find('rate_limit') rate_element.attrib['sense'] = 'decr' rate_element.text = property.text new_rate_element = et.SubElement(actuator_element, 'rate_limit') new_rate_element.attrib['sense'] = 'incr' new_rate_element.text = rate_element.text tree.write( self.sandbox('aircraft', self.aircraft_name, self.aircraft_name + '.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') self.ScriptExecution(fdm) del fdm def test_actuator_rate_is_linear(self): # Third part of the test. ######################## # # The test is run again but this time we are checking that rate_limit # drives the actuator output value as expected. The idea is to store the # output value of the actuator output vs the time and check with a # linear regression that # 1. The actuator output value is evolving linearly # 2. The slope of the actuator output is equal to the rate_limit value # The test is run with the rate_limit given by a value, a property, # different values of the ascending and descending rates and a number of # combinations thereof. # The aircraft file definition is modified such that the actuator # element input is driven by a unique property. The name of this unique # property is built in the variable 'input_prop' below. When setting # that property to 1.0 (resp. -1.0) the ascending (resp. descending) # rate is triggered. tree = et.parse( os.path.join(self.path_to_jsbsim_aircrafts, self.aircraft_name + '.xml')) flight_control_element = tree.getroot().find('flight_control') actuator_element = flight_control_element.find( 'channel/actuator//rate_limit/..') # Remove the hysteresis. We want to make sure we are measuring the # rate_limit and just that. hysteresis_element = actuator_element.find('hysteresis') actuator_element.remove(hysteresis_element) input_element = actuator_element.find('input') input_prop = string.split(actuator_element.attrib['name'], '-') input_prop[-1] = 'input' input_prop = string.join(input_prop, '-') input_element.text = input_prop output_element = actuator_element.find('output') output_prop = string.strip(output_element.text) # Add the new properties to <flight_control> so that we can make # reference to them without JSBSim complaining property = et.SubElement(flight_control_element, 'property') property.text = input_prop property.attrib['value'] = '0.0' property = et.SubElement(flight_control_element, 'property') property.text = 'fcs/rate-limit-value' property.attrib['value'] = '0.15' property = et.SubElement(flight_control_element, 'property') property.text = 'fcs/rate-limit-value2' property.attrib['value'] = '0.05' # First check with rate_limit set to 0.1 rate_element = actuator_element.find('rate_limit') rate_element.text = '0.1' output_file = self.sandbox('aircraft', self.aircraft_name, self.aircraft_name + '.xml') tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.1, -0.1) # Check when rate_limit is set by the property 'fcs/rate-limit-value' tree = et.parse(output_file) flight_control_element = tree.getroot().find('flight_control') actuator_element = flight_control_element.find( 'channel/actuator//rate_limit/..') rate_element = actuator_element.find('rate_limit') rate_element.text = 'fcs/rate-limit-value' tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.15, -0.15) # Checking when the ascending and descending rates are different. # First with the 2 rates set by hard coded values (0.1 and 0.2 respectively) rate_element.attrib['sense'] = 'decr' rate_element.text = '0.1' new_rate_element = et.SubElement(actuator_element, 'rate_limit') new_rate_element.attrib['sense'] = 'incr' new_rate_element.text = '0.2' tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.2, -0.1) # Check when the descending rate is set by a property and the ascending rate is # set by a value. rate_element.text = 'fcs/rate-limit-value' tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.2, -0.15) # Check when the ascending rate is set by a property and the descending # rate is set by a value. rate_element.text = '0.1' new_rate_element.text = 'fcs/rate-limit-value' tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.15, -0.1) # Check when the ascending and descending rates are set by properties rate_element.text = 'fcs/rate-limit-value2' tree.write(output_file) self.CheckRateLimit(input_prop, output_prop, 0.15, -0.05)
# # Check again on a brand new FDM # sandbox.delete_csv_files() fdm = CreateFDM(sandbox) fdm.load_script(sandbox.path_to_jsbsim_file('scripts', 'c1722.xml')) fdm.run_ic() fdm.set_output_filename(0,'oops.csv') # Oops!! Changed my mind ExecuteUntil(fdm, 1.0) if sandbox.exists('oops.csv') or not sandbox.exists('JSBout172B.csv'): if sandbox.exists('oops.csv'): print "New FDM: 'oops.csv' should not exist." if not sandbox.exists('JSBout172B.csv'): print "New FDM: 'JSBout172B.csv' should exist." sys.exit(-1) # 'make test' will report the test failed. # # The new file name 'oops.csv' has been ignored. # Check if it is now taken into account. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) if not sandbox.exists('oops.csv'): print "Reset new FDM: 'oops.csv' should exist." sys.exit(-1) # 'make test' will report the test failed. sandbox.erase()
class TestICOverride(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_IC_override(self): # Run the script c1724.xml script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1724.xml') fdm = CreateFDM(self.sandbox) fdm.load_script(script_path) vt0 = fdm.get_property_value('ic/vt-kts') fdm.run_ic() ExecuteUntil(fdm, 1.0) # Check that the total velocity exported in the output file matches the IC # defined in the initialization file ref = Table() ref.ReadCSV(self.sandbox('JSBout172B.csv')) for col, title in enumerate(ref._lines[0]): if title == 'V_{Total} (ft/s)': self.assertTrue( abs(ref._lines[1][col] - (vt0 / fpstokts)) < 1E-5, msg= "Original script %s\nThe total velocity is %f. The value %f was expected" % (script_path, ref._lines[1][col], vt0 / fpstokts)) break else: self.fail("The total velocity is not exported in %s" % (script_path, )) # Now, we will re-run the same test but the IC will be overridden in the scripts # The initial total velocity is increased by 1 ft/s vt0 += 1.0 # The script c1724.xml is loaded and the following line is added in it: # <property value="..."> ic/vt-kts </property> # The modified script is then saved with the named 'c1724_0.xml' tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find("./run") property = et.SubElement(run_tag, 'property') property.text = 'ic/vt-kts' property.attrib['value'] = str(vt0) tree.write(self.sandbox('c1724_0.xml')) # Re-run the same check than above. This time we are making sure than the total # initial velocity is increased by 1 ft/s self.sandbox.delete_csv_files() # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm fdm = CreateFDM(self.sandbox) fdm.load_script('c1724_0.xml') self.assertTrue( abs(fdm.get_property_value('ic/vt-kts') - vt0) < 1E-5, msg= "Modified script %s\nThe total velocity in the IC (%f) is different from %f" % (self.sandbox('JSBout172B.csv'), fdm.get_property_value('ic/vt-kts'), vt0)) fdm.run_ic() ExecuteUntil(fdm, 1.0) mod = Table() mod.ReadCSV(self.sandbox('JSBout172B.csv')) for col, title in enumerate(mod._lines[0]): if title == 'V_{Total} (ft/s)': self.assertTrue( abs(mod._lines[1][col] - (vt0 / fpstokts)) < 1E-5, msg= "Modified script %s\nThe total velocity is %f. The value %f was expected" % (self.sandbox('JSBout172B.csv'), mod._lines[1][col], vt0 / fpstokts)) break else: self.fail("The total velocity is not exported in %s" % (sandbox('JSBout172B.csv'), ))
class TestICOverride(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_IC_override(self): # Run the script c1724.xml script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1724.xml') fdm = CreateFDM(self.sandbox) fdm.load_script(script_path) vt0 = fdm.get_property_value('ic/vt-kts') fdm.run_ic() self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) self.assertAlmostEqual(fdm.get_property_value('velocities/vt-fps'), vt0 / fpstokts, delta=1E-7) ExecuteUntil(fdm, 1.0) # Check that the total velocity exported in the output file matches the # IC defined in the initialization file ref = Table() ref.ReadCSV(self.sandbox('JSBout172B.csv')) self.assertEqual(ref.get_column('Time')[1], 0.0) self.assertAlmostEqual(ref.get_column('V_{Total} (ft/s)')[1], vt0 / fpstokts, delta=1E-7) # Now, we will re-run the same test but the IC will be overridden in the # script. The initial total velocity is increased by 1 ft/s vt0 += 1.0 # The script c1724.xml is loaded and the following line is added in it: # <property value="..."> ic/vt-kts </property> # The modified script is then saved with the named 'c1724_0.xml' tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find("./run") property = et.SubElement(run_tag, 'property') property.text = 'ic/vt-kts' property.attrib['value'] = str(vt0) tree.write(self.sandbox('c1724_0.xml')) # Re-run the same check than above. This time we are making sure than # the total initial velocity is increased by 1 ft/s self.sandbox.delete_csv_files() # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm fdm = CreateFDM(self.sandbox) fdm.load_script('c1724_0.xml') self.assertAlmostEqual(fdm.get_property_value('ic/vt-kts'), vt0, delta=1E-6) fdm.run_ic() self.assertEqual(fdm.get_property_value('simulation/sim-time-sec'), 0.0) self.assertAlmostEqual(fdm.get_property_value('velocities/vt-fps'), vt0 / fpstokts, delta=1E-6) ExecuteUntil(fdm, 1.0) mod = Table() mod.ReadCSV(self.sandbox('JSBout172B.csv')) self.assertAlmostEqual(mod.get_column('V_{Total} (ft/s)')[1], vt0 / fpstokts, delta=1E-6)
class ResetOutputFiles(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_reset_output_files(self): # # Regular run that checks the correct CSV file is created # We are just checking its existence, not its content. To accelerate the # test execution, the simulation is interrupted after 1.0sec of simulated # time. # fdm = CreateFDM(self.sandbox) fdm.load_script( self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml')) fdm.run_ic() ExecuteUntil(fdm, 1.0) self.assertTrue( self.sandbox.exists('JSBout172B.csv'), msg="Standard run: the file 'JSBout172B.csv' should exist.") # # Reset the simulation and check that iteration number is correctly # appended to the filename. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('JSBout172B_0.csv'), msg="Reset: the file 'JSBout172B_0.csv' should exist.") # # Change the output filename and check that the naming logic is reset # (e.g. that no iteration number is appended to the filename) # fdm.set_output_filename(0, 'dummy.csv') fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue( self.sandbox.exists('dummy.csv'), msg="Output name renaming: the file 'dummy.csv' should exist.") # # Call FGFDMExec::SetOutputFileName() after the simulation is reset. And # verify that the new output file name is ignored until the next call to # FGOutput::SetStartNewOutput(). This should be so according to the # documentation of FGOutput::SetOutputName(). # fdm.reset_to_initial_conditions(1) fdm.set_output_filename(0, 'dummyx.csv') ExecuteUntil(fdm, 1.0) self.assertTrue(not self.sandbox.exists('dummyx.csv'), msg="Late renaming: 'dummyx.csv' should not exist.") self.assertTrue(self.sandbox.exists('dummy_0.csv'), msg="Late renaming: 'dummy_0.csv' should exist.") # # Check that the new filename is taken into account when the simulation is # reset. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue( self.sandbox.exists('dummyx.csv'), msg="Reset after late renaming: 'dummyx.csv' should exist.") # # Check against multiple calls to FGFDMExec::SetOutputFileName() # fdm.set_output_filename(0, 'this_one.csv') fdm.set_output_filename(0, 'that_one.csv') fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue( not self.sandbox.exists('this_one.csv'), msg="Output name overwritten: 'this_one.csv' should not exist.") self.assertTrue( self.sandbox.exists('that_one.csv'), msg="Output name overwritten: 'that_one.csv' should exist.") # # Check again on a brand new FDM # self.sandbox.delete_csv_files() # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm fdm = CreateFDM(self.sandbox) fdm.load_script( self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml')) fdm.run_ic() fdm.set_output_filename(0, 'oops.csv') # Oops!! Changed my mind ExecuteUntil(fdm, 1.0) self.assertTrue(not self.sandbox.exists('oops.csv'), msg="New FDM: 'oops.csv' should not exist.") self.assertTrue(self.sandbox.exists('JSBout172B.csv'), msg="New FDM: 'JSBout172B.csv' should exist.") # # The new file name 'oops.csv' has been ignored. # Check if it is now taken into account. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('oops.csv'), msg="Reset new FDM: 'oops.csv' should exist.")
class CheckOutputRate(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.fdm = CreateFDM(self.sandbox) self.script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml') # Read the time step 'dt' from the script file self.tree = et.parse(self.sandbox.elude(self.script_path)) root = self.tree.getroot() use_tag = root.find("./use") aircraft_name = use_tag.attrib['aircraft'] self.run_tag = root.find("./run") self.dt = float(self.run_tag.attrib['dt']) # Read the date at which the trim will be run event_tags = root.findall('./run/event') for event in event_tags: if event.attrib['name'] == 'Trim': cond_tag = event.find('./condition') self.trim_date = float(string.split(cond_tag.text)[-1]) break # Read the output rate and the output file from the aircraft file aircraft_path = self.sandbox.path_to_jsbsim_file('aircraft', aircraft_name, append_xml(aircraft_name)) tree = et.parse(self.sandbox.elude(aircraft_path)) output_tag = tree.getroot().find("./output") self.output_file = self.sandbox(output_tag.attrib['name']) self.rateHz = float(output_tag.attrib['rate']) self.rate = int(1.0 / (self.rateHz * self.dt)) def tearDown(self): del self.fdm self.sandbox.erase() def testOutputRate(self): self.fdm.load_script(self.script_path) # Check that the output is enabled by default self.assertEqual(self.fdm.get_property_value("simulation/output/enabled"), 1.0) # Check that the rate is consistent with the values extracted from the # script and the aircraft definition self.assertAlmostEqual(self.fdm.get_property_value("simulation/output/log_rate_hz"), self.rateHz, delta=1E-5) self.fdm.run_ic() for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # According to the settings, the output file must contain 2 lines in # addition to the headers : # 1. The initial conditions # 2. The output after 'rate' iterations self.assertEqual(output.get_column(0)[1], 0.0) self.assertEqual(output.get_column(0)[2], self.rate * self.dt) self.assertEqual(output.get_column(0)[2], self.fdm.get_property_value("simulation/sim-time-sec")) def testDisablingOutput(self): self.fdm.load_script(self.script_path) # Disables the output during the initialization self.fdm.set_property_value("simulation/output/enabled", 0.0) self.fdm.run_ic() self.fdm.set_property_value("simulation/output/enabled", 1.0) for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # According to the settings, the output file must contain 1 line in # addition to the headers : # 1. The output after 'rate' iterations self.assertEqual(output.get_column(0)[1], self.fdm.get_property_value("simulation/sim-time-sec")) def testTrimRestoresOutputSettings(self): self.fdm.load_script(self.script_path) # Disables the output during the initialization self.fdm.set_property_value("simulation/output/enabled", 0.0) self.fdm.run_ic() # Check that the output remains disabled even after the trim is # executed while self.fdm.get_property_value("simulation/sim-time-sec") < self.trim_date + 2.0*self.dt: self.fdm.run() self.assertEqual(self.fdm.get_property_value("simulation/output/enabled"), 0.0) # Re-enable the output and check that the output rate is unaffected by # the previous operations self.fdm.set_property_value("simulation/output/enabled", 1.0) frame = int(self.fdm.get_property_value("simulation/frame")) for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # The frame at which the data is logged must be the next multiple of # the output rate self.assertEqual(int(output.get_column(0)[1]/self.dt), (1 + frame/self.rate)*self.rate) def testDisablingOutputInScript(self): property = et.SubElement(self.run_tag, 'property') property.text = 'simulation/output/enabled' property.attrib['value'] = "0.0" self.tree.write(self.sandbox('c1722_0.xml')) self.fdm.load_script('c1722_0.xml') # Check that the output is disabled self.assertEqual(self.fdm.get_property_value("simulation/output/enabled"), 0.0) self.fdm.run_ic() self.fdm.set_property_value("simulation/output/enabled", 1.0) for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # According to the settings, the output file must contain 1 line in # addition to the headers : # 1. The output after 'rate' iterations self.assertEqual(output.get_column(0)[1], self.fdm.get_property_value("simulation/sim-time-sec"))
class TestAccelerometer(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def AddAccelerometersToAircraft(self, script_path): tree, aircraft_name, b = CopyAircraftDef(script_path, self.sandbox) system_tag = et.SubElement(tree.getroot(), 'system') system_tag.attrib['file'] = 'accelerometers' tree.write( self.sandbox('aircraft', aircraft_name, aircraft_name + '.xml')) def testOrbit(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) # The time step is too small in ball_orbit so let's increase it to 0.1s # for a quicker run tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find('./run') run_tag.attrib['dt'] = '0.1' tree.write(self.sandbox(script_name)) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_name) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) fdm.run_ic() while fdm.run(): self.assertAlmostEqual( fdm.get_property_value('fcs/accelerometer/X'), 0.0, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('fcs/accelerometer/Y'), 0.0, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('fcs/accelerometer/Z'), 0.0, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('accelerations/a-pilot-x-ft_sec2'), 0.0, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('accelerations/a-pilot-y-ft_sec2'), 0.0, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('accelerations/a-pilot-z-ft_sec2'), 0.0, delta=1E-8) del fdm def testOnGround(self): script_name = 'c1721.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) # Use the standard gravity (i.e. GM/r^2) fdm.set_property_value('simulation/gravity-model', 0) # Simplifies the transformation to compare the accelerometer with the # gravity fdm.set_property_value('ic/psi-true-rad', 0.0) fdm.run_ic() for i in xrange(500): fdm.run() ax = fdm.get_property_value('accelerations/udot-ft_sec2') ay = fdm.get_property_value('accelerations/vdot-ft_sec2') az = fdm.get_property_value('accelerations/wdot-ft_sec2') g = fdm.get_property_value('accelerations/gravity-ft_sec2') theta = fdm.get_property_value('attitude/theta-rad') # There is a lag of one time step between the computations of the # accelerations and the update of the accelerometer fdm.run() fax = fdm.get_property_value('fcs/accelerometer/X') fay = fdm.get_property_value('fcs/accelerometer/Y') faz = fdm.get_property_value('fcs/accelerometer/Z') fax -= ax faz -= az self.assertAlmostEqual(fay, 0.0, delta=1E-6) self.assertAlmostEqual(fax / (g * math.sin(theta)), 1.0, delta=1E-5) self.assertAlmostEqual(faz / (g * math.cos(theta)), -1.0, delta=1E-7) del fdm def testSteadyFlight(self): script_name = 'c1722.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) # Use the standard gravity (i.e. GM/r^2) fdm.set_property_value('simulation/gravity-model', 0) # Simplifies the transformation to compare the accelerometer with the # gravity fdm.set_property_value('ic/psi-true-rad', 0.0) fdm.run_ic() while fdm.get_property_value('simulation/sim-time-sec') <= 0.5: fdm.run() fdm.set_property_value('simulation/do_simple_trim', 1) ax = fdm.get_property_value('accelerations/udot-ft_sec2') ay = fdm.get_property_value('accelerations/vdot-ft_sec2') az = fdm.get_property_value('accelerations/wdot-ft_sec2') g = fdm.get_property_value('accelerations/gravity-ft_sec2') theta = fdm.get_property_value('attitude/theta-rad') # There is a lag of one time step between the computations of the # accelerations and the update of the accelerometer fdm.run() fax = fdm.get_property_value('fcs/accelerometer/X') fay = fdm.get_property_value('fcs/accelerometer/Y') faz = fdm.get_property_value('fcs/accelerometer/Z') fax -= ax fay -= ay faz -= az # Deltas are relaxed because the tolerances of the trimming algorithm # are quite relaxed themselves. self.assertAlmostEqual(faz / (g * math.cos(theta)), -1.0, delta=1E-5) self.assertAlmostEqual(fax / (g * math.sin(theta)), 1.0, delta=1E-5) self.assertAlmostEqual(math.sqrt(fax * fax + fay * fay + faz * faz) / g, 1.0, delta=1E-6) del fdm def testSpinningBodyOnOrbit(self): script_name = 'ball_orbit.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) self.AddAccelerometersToAircraft(script_path) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_model('ball') # Offset the CG along Y (by 30") fdm.set_property_value('inertia/pointmass-weight-lbs[1]', 50.0) aircraft_path = self.sandbox.elude( self.sandbox.path_to_jsbsim_file('aircraft', 'ball')) fdm.load_ic(os.path.join(aircraft_path, 'reset00.xml'), False) # Switch the accel on fdm.set_property_value('fcs/accelerometer/on', 1.0) # Set the orientation such that the spinning axis is Z. fdm.set_property_value('ic/phi-rad', 0.5 * math.pi) # Set the angular velocities to 0.0 in the ECEF frame. The angular # velocity R_{inertial} will therefore be equal to the Earth rotation # rate 7.292115E-5 rad/sec fdm.set_property_value('ic/p-rad_sec', 0.0) fdm.set_property_value('ic/q-rad_sec', 0.0) fdm.set_property_value('ic/r-rad_sec', 0.0) fdm.run_ic() fax = fdm.get_property_value('fcs/accelerometer/X') fay = fdm.get_property_value('fcs/accelerometer/Y') faz = fdm.get_property_value('fcs/accelerometer/Z') cgy_ft = fdm.get_property_value('inertia/cg-y-in') / 12. omega = 0.00007292115 # Earth rotation rate in rad/sec self.assertAlmostEqual( fdm.get_property_value('accelerations/a-pilot-x-ft_sec2'), fax, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('accelerations/a-pilot-y-ft_sec2'), fay, delta=1E-8) self.assertAlmostEqual( fdm.get_property_value('accelerations/a-pilot-z-ft_sec2'), faz, delta=1E-8) # Acceleration along X should be zero self.assertAlmostEqual(fax, 0.0, delta=1E-8) # Acceleration along Y should be equal to r*omega^2 self.assertAlmostEqual(fay / (cgy_ft * omega * omega), 1.0, delta=1E-7) # Acceleration along Z should be zero self.assertAlmostEqual(faz, 0.0, delta=1E-8)
class TestModelLoading(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def BuildReference(self, script_name): # Run the script self.script = self.sandbox.path_to_jsbsim_file( os.path.join('scripts', script_name)) self.sandbox.delete_csv_files() fdm = CreateFDM(self.sandbox) fdm.set_output_directive( self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.load_script(self.script) fdm.set_property_value('simulation/randomseed', 0.0) fdm.run_ic() ExecuteUntil(fdm, 50.0) self.ref = Table() self.ref.ReadCSV(self.sandbox("output.csv")) # Since the script will work with modified versions of the aircraft XML # definition file, we need to make a copy of the directory that contains # all the input data of that aircraft tree, self.aircraft_name, self.path_to_jsbsim_aircrafts = CopyAircraftDef( self.script, self.sandbox) self.aircraft_path = self.sandbox('aircraft', self.aircraft_name) def ProcessAndCompare(self, section): # Here we determine if the original aircraft definition <section> is # inline or read from an external file. tree = et.parse( os.path.join(self.path_to_jsbsim_aircrafts, self.aircraft_name + '.xml')) root = tree.getroot() # Iterate over all the tags named <section> for section_element in root.findall(section): if 'file' in section_element.keys(): self.InsertAndCompare(section_element, tree) else: self.DetachAndCompare(section_element, tree) def DetachAndCompare(self, section_element, tree): # Extract <section> from the original aircraft definition file and copy # it in a separate XML file 'section.xml' section_tree = et.ElementTree(element=section_element) if 'name' in section_element.keys(): section = section_element.attrib['name'] else: section = section_element.tag section_tree.write(os.path.join(self.aircraft_path, section + '.xml'), xml_declaration=True) # Now, we need to clean up the aircraft definition file from all # references to <section>. We just need a single <section> tag that # points to the file 'section.xml' for element in list(section_element): section_element.remove(element) section_element.attrib = {'file': section + '.xml'} tree.write(os.path.join(self.aircraft_path, self.aircraft_name + '.xml'), xml_declaration=True) self.Compare(section) def InsertAndCompare(self, section_element, tree): file_name = append_xml(section_element.attrib['file']) section_file = os.path.join(self.path_to_jsbsim_aircrafts, file_name) # If <section> is actually <system>, we need to iterate over all the # directories in which the file is allowed to be stored until the file # is located. if not os.path.exists( section_file) and section_element.tag == 'system': section_file = os.path.join(self.path_to_jsbsim_aircrafts, "systems", file_name) if not os.path.exists(section_file): section_file = self.sandbox.elude( self.sandbox.path_to_jsbsim_file("systems", file_name)) # The original <section> tag is dropped and replaced by the content of # the file. section_root = et.parse(section_file).getroot() del section_element.attrib['file'] section_element.attrib.update(section_root.attrib) section_element.extend(section_root) tree.write( os.path.join(self.aircraft_path, self.aircraft_name + '.xml')) self.Compare(section_element.tag + " file:" + section_file) def Compare(self, section): # Rerun the script with the modified aircraft definition self.sandbox.delete_csv_files() fdm = CreateFDM(self.sandbox) # We need to tell JSBSim that the aircraft definition is located in the # directory build/.../aircraft fdm.set_aircraft_path('aircraft') fdm.set_output_directive( self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.load_script(self.script) fdm.set_property_value('simulation/randomseed', 0.0) fdm.run_ic() ExecuteUntil(fdm, 50.0) mod = Table() mod.ReadCSV(self.sandbox('output.csv')) # Whether the data is read from the aircraft definition file or from an # external file, the results shall be exactly identical. Hence the # precision set to 0.0. diff = self.ref.compare(mod, 0.0) self.assertTrue(diff.empty(), msg='\nTesting section "' + section + '"\n' + repr(diff)) def test_model_loading(self): self.longMessage = True self.BuildReference('c1724.xml') output_ref = Table() output_ref.ReadCSV(self.sandbox('JSBout172B.csv')) self.ProcessAndCompare('aerodynamics') self.ProcessAndCompare('autopilot') self.ProcessAndCompare('flight_control') self.ProcessAndCompare('ground_reactions') self.ProcessAndCompare('mass_balance') self.ProcessAndCompare('metrics') self.ProcessAndCompare('propulsion') self.ProcessAndCompare('system') # The <output> section needs special handling. In addition to the check # conducted by ProcessAndCompare with a directive file, we need to # verify that the <output> tag has been correctly executed by JSBSim. # In the case of the script c1724.xml, this means that the data output # in JSBout172B.csv is the same between the reference 'output_ref' and # the result 'mod' below where the <output> tag was moved in a separate # file. self.ProcessAndCompare('output') mod = Table() mod.ReadCSV(self.sandbox('JSBout172B.csv')) diff = output_ref.compare(mod, 0.0) self.assertTrue(diff.empty(), msg='\nTesting section "output"\n' + repr(diff)) self.BuildReference('weather-balloon.xml') self.ProcessAndCompare('buoyant_forces') self.BuildReference('Concorde_runway_test.xml') self.ProcessAndCompare('external_reactions')
class CheckMomentsUpdate(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def CheckCGPosition(self): weight = self.fdm.get_property_value('inertia/weight-lbs') empty_weight = self.fdm.get_property_value('inertia/empty-weight-lbs') contents = self.fdm.get_property_value( 'buoyant_forces/gas-cell/contents-mol') radiosonde_weight = weight - empty_weight - contents * mol2lbs CGx = self.fdm.get_property_value('inertia/cg-x-in') CGy = self.fdm.get_property_value('inertia/cg-y-in') CGz = self.fdm.get_property_value('inertia/cg-z-in') X = self.fdm.get_property_value('inertia/pointmass-location-X-inches') Y = self.fdm.get_property_value('inertia/pointmass-location-Y-inches') Z = self.fdm.get_property_value('inertia/pointmass-location-Z-inches') self.assertAlmostEqual(CGx, X * radiosonde_weight / weight, delta=1E-7) self.assertAlmostEqual(CGy, Y * radiosonde_weight / weight, delta=1E-7) self.assertAlmostEqual(CGz, Z * radiosonde_weight / weight, delta=1E-7) def test_moments_update(self): script_path = self.sandbox.path_to_jsbsim_file('scripts', 'weather-balloon.xml') self.fdm = CreateFDM(self.sandbox) self.fdm.load_script(script_path) self.fdm.set_output_directive( self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) self.fdm.run_ic() self.CheckCGPosition() dt = self.fdm.get_property_value('simulation/dt') ExecuteUntil(self.fdm, 1.0 - 2.0 * dt) self.CheckCGPosition() # Moves the radio sonde to modify the CG location self.fdm.set_property_value('inertia/pointmass-location-X-inches', 5.0) # Check that the moment is immediately updated accordingly self.fdm.run() self.CheckCGPosition() Fbx = self.fdm.get_property_value('forces/fbx-buoyancy-lbs') Fbz = self.fdm.get_property_value('forces/fbz-buoyancy-lbs') CGx = self.fdm.get_property_value( 'inertia/cg-x-in') / 12.0 # Converts from in to ft CGz = self.fdm.get_property_value('inertia/cg-z-in') / 12.0 Mby = self.fdm.get_property_value('moments/m-buoyancy-lbsft') self.assertAlmostEqual( Fbx * CGz - Fbz * CGx, Mby, delta=1E-7, msg="Fbx*CGz-Fbz*CGx = %f and Mby = %f do not match" % (Fbx * CGz - Fbz * CGx, Mby)) # One further step to log the same results in the output file self.fdm.run() self.CheckCGPosition() csv = Table() csv.ReadCSV(self.sandbox('output.csv')) Mby = csv.get_column('M_{Buoyant} (ft-lbs)')[-1] Fbx = csv.get_column('F_{Buoyant x} (lbs)')[-1] Fbz = csv.get_column('F_{Buoyant z} (lbs)')[-1] self.assertAlmostEqual( Fbx * CGz - Fbz * CGx, Mby, delta=1E-7, msg="Fbx*CGz-Fbz*CGx = %f and Mby = %f do not match" % (Fbx * CGz - Fbz * CGx, Mby))
class ResetOutputFiles(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_reset_output_files(self): # # Regular run that checks the correct CSV file is created # We are just checking its existence, not its content. To accelerate the # test execution, the simulation is interrupted after 1.0sec of simulated # time. # fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml')) fdm.run_ic() ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('JSBout172B.csv'), msg="Standard run: the file 'JSBout172B.csv' should exist.") # # Reset the simulation and check that iteration number is correctly # appended to the filename. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('JSBout172B_0.csv'), msg="Reset: the file 'JSBout172B_0.csv' should exist.") # # Change the output filename and check that the naming logic is reset # (e.g. that no iteration number is appended to the filename) # fdm.set_output_filename(0, 'dummy.csv') fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('dummy.csv'), msg="Output name renaming: the file 'dummy.csv' should exist.") # # Call FGFDMExec::SetOutputFileName() after the simulation is reset. And # verify that the new output file name is ignored until the next call to # FGOutput::SetStartNewOutput(). This should be so according to the # documentation of FGOutput::SetOutputName(). # fdm.reset_to_initial_conditions(1) fdm.set_output_filename(0, 'dummyx.csv') ExecuteUntil(fdm, 1.0) self.assertTrue(not self.sandbox.exists('dummyx.csv'), msg="Late renaming: 'dummyx.csv' should not exist.") self.assertTrue(self.sandbox.exists('dummy_0.csv'), msg="Late renaming: 'dummy_0.csv' should exist.") # # Check that the new filename is taken into account when the simulation is # reset. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('dummyx.csv'), msg="Reset after late renaming: 'dummyx.csv' should exist.") # # Check against multiple calls to FGFDMExec::SetOutputFileName() # fdm.set_output_filename(0, 'this_one.csv') fdm.set_output_filename(0, 'that_one.csv') fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(not self.sandbox.exists('this_one.csv'), msg="Output name overwritten: 'this_one.csv' should not exist.") self.assertTrue(self.sandbox.exists('that_one.csv'), msg="Output name overwritten: 'that_one.csv' should exist.") # # Check again on a brand new FDM # self.sandbox.delete_csv_files() # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm fdm = CreateFDM(self.sandbox) fdm.load_script(self.sandbox.path_to_jsbsim_file('scripts', 'c1722.xml')) fdm.run_ic() fdm.set_output_filename(0,'oops.csv') # Oops!! Changed my mind ExecuteUntil(fdm, 1.0) self.assertTrue(not self.sandbox.exists('oops.csv'), msg="New FDM: 'oops.csv' should not exist.") self.assertTrue(self.sandbox.exists('JSBout172B.csv'), msg="New FDM: 'JSBout172B.csv' should exist.") # # The new file name 'oops.csv' has been ignored. # Check if it is now taken into account. # fdm.reset_to_initial_conditions(1) ExecuteUntil(fdm, 1.0) self.assertTrue(self.sandbox.exists('oops.csv'), msg="Reset new FDM: 'oops.csv' should exist.")
class TestPitotAngle(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_CAS_ic(self): script_name = 'Short_S23_3.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) # Add a Pitot angle to the Short S23 tree, aircraft_name, path_to_jsbsim_aircrafts = CopyAircraftDef( script_path, self.sandbox) metrics_tag = tree.getroot().find('./metrics') pitot_tag = et.SubElement(metrics_tag, 'pitot_angle') pitot_tag.attrib['unit'] = 'DEG' pitot_tag.text = '5.0' tree.write( self.sandbox('aircraft', aircraft_name, aircraft_name + '.xml')) # Read the CAS specified in the IC file tree = et.parse(self.sandbox.elude(script_path)) use_element = tree.getroot().find('use') IC_file = use_element.attrib['initialize'] tree = et.parse( os.path.join(path_to_jsbsim_aircrafts, append_xml(IC_file))) vc_tag = tree.getroot().find('./vc') VCAS = float(vc_tag.text) if 'unit' in vc_tag.attrib and vc_tag.attrib['unit'] == 'FT/SEC': VCAS /= 1.68781 # Run the IC and check that the model is initialized correctly fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) fdm.run_ic() self.assertAlmostEqual(fdm.get_property_value('ic/vc-kts'), VCAS, delta=1E-7) self.assertAlmostEqual(fdm.get_property_value('velocities/vc-kts'), VCAS, delta=1E-7) def test_pitot_angle(self): script_name = 'ball_chute.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) # Add a Pitot angle to the Cessna 172 tree, aircraft_name, path_to_jsbsim_aircrafts = CopyAircraftDef( script_path, self.sandbox) root = tree.getroot() metrics_tag = root.find('./metrics') pitot_tag = et.SubElement(metrics_tag, 'pitot_angle') pitot_tag.attrib['unit'] = 'DEG' pitot_tag.text = '5.0' contact_tag = root.find('./ground_reactions/contact') contact_tag.attrib['type'] = 'STRUCTURE' tree.write( self.sandbox('aircraft', aircraft_name, aircraft_name + '.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_model('ball') pitot_angle = float(pitot_tag.text) * math.pi / 180. weight = fdm.get_property_value('inertia/weight-lbs') spring_tag = contact_tag.find('./spring_coeff') spring_coeff = float(spring_tag.text) print "Weight=%d Spring=%d" % (weight, spring_coeff) fdm.set_property_value('ic/h-sl-ft', weight / spring_coeff) fdm.set_property_value('forces/hold-down', 1.0) fdm.run_ic() ExecuteUntil(fdm, 10.) for i in xrange(36): for j in xrange(-9, 10): angle = math.pi * i / 18.0 angle2 = math.pi * j / 18.0 ca2 = math.cos(angle2) fdm.set_property_value('atmosphere/wind-north-fps', 10. * math.cos(angle) * ca2) fdm.set_property_value('atmosphere/wind-east-fps', 10. * math.sin(angle) * ca2) fdm.set_property_value('atmosphere/wind-down-fps', 10. * math.sin(angle2)) fdm.run() vg = fdm.get_property_value('velocities/vg-fps') self.assertAlmostEqual(vg, 0.0, delta=1E-7) vt = fdm.get_property_value('velocities/vt-fps') self.assertAlmostEqual(vt, 10., delta=1E-7) mach = vt / fdm.get_property_value('atmosphere/a-fps') P = fdm.get_property_value('atmosphere/P-psf') pt = P * math.pow(1 + 0.2 * mach * mach, 3.5) psl = fdm.get_property_value('atmosphere/P-sl-psf') rhosl = fdm.get_property_value('atmosphere/rho-sl-slugs_ft3') A = math.pow((pt - P) / psl + 1.0, 1.0 / 3.5) alpha = fdm.get_property_value('aero/alpha-rad') beta = fdm.get_property_value('aero/beta-rad') vc = math.sqrt( 7.0 * psl / rhosl * (A - 1.0)) * math.cos(alpha + pitot_angle) * math.cos(beta) self.assertAlmostEqual( fdm.get_property_value('velocities/vc-kts'), max(0.0, vc) / 1.68781, delta=1E-7)
class CheckMomentsUpdate(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def CheckCGPosition(self): weight = self.fdm.get_property_value('inertia/weight-lbs') empty_weight = self.fdm.get_property_value('inertia/empty-weight-lbs') contents = self.fdm.get_property_value('buoyant_forces/gas-cell/contents-mol') radiosonde_weight = weight - empty_weight - contents * mol2lbs CGx = self.fdm.get_property_value('inertia/cg-x-in') CGy = self.fdm.get_property_value('inertia/cg-y-in') CGz = self.fdm.get_property_value('inertia/cg-z-in') X = self.fdm.get_property_value('inertia/pointmass-location-X-inches') Y = self.fdm.get_property_value('inertia/pointmass-location-Y-inches') Z = self.fdm.get_property_value('inertia/pointmass-location-Z-inches') self.assertAlmostEqual(CGx, X * radiosonde_weight / weight, delta = 1E-7) self.assertAlmostEqual(CGy, Y * radiosonde_weight / weight, delta = 1E-7) self.assertAlmostEqual(CGz, Z * radiosonde_weight / weight, delta = 1E-7) def test_moments_update(self): script_path = self.sandbox.path_to_jsbsim_file('scripts', 'weather-balloon.xml') self.fdm = CreateFDM(self.sandbox) self.fdm.load_script(script_path) self.fdm.set_output_directive(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) self.fdm.run_ic() self.CheckCGPosition() dt = self.fdm.get_property_value('simulation/dt') ExecuteUntil(self.fdm, 1.0-2.0*dt) self.CheckCGPosition() # Moves the radio sonde to modify the CG location self.fdm.set_property_value('inertia/pointmass-location-X-inches', 5.0) # Check that the moment is immediately updated accordingly self.fdm.run() self.CheckCGPosition() Fbx = self.fdm.get_property_value('forces/fbx-buoyancy-lbs') Fbz = self.fdm.get_property_value('forces/fbz-buoyancy-lbs') CGx = self.fdm.get_property_value('inertia/cg-x-in') / 12.0 # Converts from in to ft CGz = self.fdm.get_property_value('inertia/cg-z-in') / 12.0 Mby = self.fdm.get_property_value('moments/m-buoyancy-lbsft') self.assertAlmostEqual(Fbx * CGz - Fbz * CGx, Mby, delta=1E-7, msg="Fbx*CGz-Fbz*CGx = %f and Mby = %f do not match" % (Fbx*CGz-Fbz*CGx, Mby)) # One further step to log the same results in the output file self.fdm.run() self.CheckCGPosition() csv = Table() csv.ReadCSV(self.sandbox('output.csv')) Mby = csv.get_column('M_{Buoyant} (ft-lbs)')[-1] Fbx = csv.get_column('F_{Buoyant x} (lbs)')[-1] Fbz = csv.get_column('F_{Buoyant z} (lbs)')[-1] self.assertAlmostEqual(Fbx * CGz - Fbz * CGx, Mby, delta=1E-7, msg="Fbx*CGz-Fbz*CGx = %f and Mby = %f do not match" % (Fbx*CGz-Fbz*CGx, Mby))
class TestPitotAngle(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_CAS_ic(self): script_name = 'Short_S23_3.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) # Add a Pitot angle to the Short S23 tree, aircraft_name, path_to_jsbsim_aircrafts = CopyAircraftDef(script_path, self.sandbox) metrics_tag = tree.getroot().find('./metrics') pitot_tag = et.SubElement(metrics_tag, 'pitot_angle') pitot_tag.attrib['unit'] = 'DEG' pitot_tag.text = '5.0' tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) # Read the CAS specified in the IC file tree = et.parse(self.sandbox.elude(script_path)) use_element = tree.getroot().find('use') IC_file = use_element.attrib['initialize'] tree = et.parse(os.path.join(path_to_jsbsim_aircrafts, append_xml(IC_file))) vc_tag = tree.getroot().find('./vc') VCAS = float(vc_tag.text) if 'unit' in vc_tag.attrib and vc_tag.attrib['unit'] == 'FT/SEC': VCAS /= 1.68781 # Run the IC and check that the model is initialized correctly fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_script(script_path) fdm.run_ic() self.assertAlmostEqual(fdm.get_property_value('ic/vc-kts'), VCAS, delta=1E-7) self.assertAlmostEqual(fdm.get_property_value('velocities/vc-kts'), VCAS, delta=1E-7) def test_pitot_angle(self): script_name = 'ball_chute.xml' script_path = self.sandbox.path_to_jsbsim_file('scripts', script_name) # Add a Pitot angle to the Cessna 172 tree, aircraft_name, path_to_jsbsim_aircrafts = CopyAircraftDef(script_path, self.sandbox) root = tree.getroot() metrics_tag = root.find('./metrics') pitot_tag = et.SubElement(metrics_tag, 'pitot_angle') pitot_tag.attrib['unit'] = 'DEG' pitot_tag.text = '5.0' contact_tag = root.find('./ground_reactions/contact') contact_tag.attrib['type'] = 'STRUCTURE' tree.write(self.sandbox('aircraft', aircraft_name, aircraft_name+'.xml')) fdm = CreateFDM(self.sandbox) fdm.set_aircraft_path('aircraft') fdm.load_model('ball') pitot_angle = float(pitot_tag.text) * math.pi / 180. weight = fdm.get_property_value('inertia/weight-lbs') spring_tag = contact_tag.find('./spring_coeff') spring_coeff = float(spring_tag.text) print "Weight=%d Spring=%d" % (weight, spring_coeff) fdm.set_property_value('ic/h-sl-ft', weight / spring_coeff) fdm.set_property_value('forces/hold-down', 1.0) fdm.run_ic() ExecuteUntil(fdm, 10.) for i in xrange(36): for j in xrange(-9, 10): angle = math.pi * i / 18.0 angle2 = math.pi * j / 18.0 ca2 = math.cos(angle2) fdm.set_property_value('atmosphere/wind-north-fps', 10. * math.cos(angle) * ca2) fdm.set_property_value('atmosphere/wind-east-fps', 10. * math.sin(angle) * ca2) fdm.set_property_value('atmosphere/wind-down-fps', 10. * math.sin(angle2)) fdm.run() vg = fdm.get_property_value('velocities/vg-fps') self.assertAlmostEqual(vg, 0.0, delta=1E-7) vt = fdm.get_property_value('velocities/vt-fps') self.assertAlmostEqual(vt, 10., delta=1E-7) mach = vt / fdm.get_property_value('atmosphere/a-fps') P = fdm.get_property_value('atmosphere/P-psf') pt = P * math.pow(1+0.2*mach*mach, 3.5) psl = fdm.get_property_value('atmosphere/P-sl-psf') rhosl = fdm.get_property_value('atmosphere/rho-sl-slugs_ft3') A = math.pow((pt-P)/psl+1.0, 1.0/3.5) alpha = fdm.get_property_value('aero/alpha-rad') beta = fdm.get_property_value('aero/beta-rad') vc = math.sqrt(7.0*psl/rhosl*(A-1.0))*math.cos(alpha+pitot_angle)*math.cos(beta) self.assertAlmostEqual(fdm.get_property_value('velocities/vc-kts'), max(0.0, vc) / 1.68781, delta=1E-7)
class TestICOverride(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def test_IC_override(self): # Run the script c1724.xml script_path = self.sandbox.path_to_jsbsim_file('scripts', 'c1724.xml') fdm = CreateFDM(self.sandbox) fdm.load_script(script_path) vt0 = fdm.get_property_value('ic/vt-kts') fdm.run_ic() ExecuteUntil(fdm, 1.0) # Check that the total velocity exported in the output file matches the IC # defined in the initialization file ref = Table() ref.ReadCSV(self.sandbox('JSBout172B.csv')) for col, title in enumerate(ref._lines[0]): if title == 'V_{Total} (ft/s)': self.assertTrue(abs(ref._lines[1][col] - (vt0 / fpstokts)) < 1E-5, msg="Original script %s\nThe total velocity is %f. The value %f was expected" % (script_path, ref._lines[1][col], vt0 / fpstokts)) break else: self.fail("The total velocity is not exported in %s" % (script_path,)) # Now, we will re-run the same test but the IC will be overridden in the scripts # The initial total velocity is increased by 1 ft/s vt0 += 1.0 # The script c1724.xml is loaded and the following line is added in it: # <property value="..."> ic/vt-kts </property> # The modified script is then saved with the named 'c1724_0.xml' tree = et.parse(self.sandbox.elude(script_path)) run_tag = tree.getroot().find("./run") property = et.SubElement(run_tag, 'property') property.text = 'ic/vt-kts' property.attrib['value'] = str(vt0) tree.write(self.sandbox('c1724_0.xml')) # Re-run the same check than above. This time we are making sure than the total # initial velocity is increased by 1 ft/s self.sandbox.delete_csv_files() # Because JSBSim internals use static pointers, we cannot rely on Python # garbage collector to decide when the FDM is destroyed otherwise we can # get dangling pointers. del fdm fdm = CreateFDM(self.sandbox) fdm.load_script('c1724_0.xml') self.assertTrue(abs(fdm.get_property_value('ic/vt-kts') - vt0) < 1E-5, msg="Modified script %s\nThe total velocity in the IC (%f) is different from %f" % (self.sandbox('JSBout172B.csv'), fdm.get_property_value('ic/vt-kts'), vt0)) fdm.run_ic() ExecuteUntil(fdm, 1.0) mod = Table() mod.ReadCSV(self.sandbox('JSBout172B.csv')) for col, title in enumerate(mod._lines[0]): if title == 'V_{Total} (ft/s)': self.assertTrue(abs(mod._lines[1][col] - (vt0 / fpstokts)) < 1E-5, msg="Modified script %s\nThe total velocity is %f. The value %f was expected" % (self.sandbox('JSBout172B.csv'), mod._lines[1][col], vt0 / fpstokts)) break else: self.fail("The total velocity is not exported in %s" % (sandbox('JSBout172B.csv'),))
class TestModelLoading(unittest.TestCase): def setUp(self): self.sandbox = SandBox() def tearDown(self): self.sandbox.erase() def BuildReference(self, script_name): # Run the script self.script = self.sandbox.path_to_jsbsim_file(os.path.join('scripts', script_name)) self.sandbox.delete_csv_files() fdm = CreateFDM(self.sandbox) fdm.set_output_directive(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.load_script(self.script) fdm.set_property_value('simulation/randomseed', 0.0) fdm.run_ic() ExecuteUntil(fdm, 50.0) self.ref = Table() self.ref.ReadCSV(self.sandbox("output.csv")) # Since the script will work with modified versions of the aircraft XML # definition file, we need to make a copy of the directory that contains # all the input data of that aircraft tree, self.aircraft_name, self.path_to_jsbsim_aircrafts = CopyAircraftDef(self.script, self.sandbox) self.aircraft_path = self.sandbox('aircraft', self.aircraft_name) def ProcessAndCompare(self, section): # Here we determine if the original aircraft definition <section> is # inline or read from an external file. tree = et.parse(os.path.join(self.path_to_jsbsim_aircrafts, self.aircraft_name + '.xml')) root = tree.getroot() # Iterate over all the tags named <section> for section_element in root.findall(section): if 'file' in section_element.keys(): self.InsertAndCompare(section_element, tree) else: self.DetachAndCompare(section_element, tree) def DetachAndCompare(self, section_element, tree): # Extract <section> from the original aircraft definition file and copy # it in a separate XML file 'section.xml' section_tree = et.ElementTree(element=section_element) if 'name' in section_element.keys(): section = section_element.attrib['name'] else: section = section_element.tag section_tree.write(os.path.join(self.aircraft_path, section+'.xml'), xml_declaration=True) # Now, we need to clean up the aircraft definition file from all # references to <section>. We just need a single <section> tag that # points to the file 'section.xml' for element in list(section_element): section_element.remove(element) section_element.attrib = {'file': section+'.xml'} tree.write(os.path.join(self.aircraft_path, self.aircraft_name+'.xml'), xml_declaration=True) self.Compare(section) def InsertAndCompare(self, section_element, tree): file_name = append_xml(section_element.attrib['file']) section_file = os.path.join(self.path_to_jsbsim_aircrafts, file_name) # If <section> is actually <system>, we need to iterate over all the # directories in which the file is allowed to be stored until the file # is located. if not os.path.exists(section_file) and section_element.tag == 'system': section_file = os.path.join(self.path_to_jsbsim_aircrafts, "systems", file_name) if not os.path.exists(section_file): section_file = self.sandbox.elude(self.sandbox.path_to_jsbsim_file("systems", file_name)) # The original <section> tag is dropped and replaced by the content of # the file. section_root = et.parse(section_file).getroot() del section_element.attrib['file'] section_element.attrib.update(section_root.attrib) section_element.extend(section_root) tree.write(os.path.join(self.aircraft_path, self.aircraft_name+'.xml')) self.Compare(section_element.tag+" file:"+section_file) def Compare(self, section): # Rerun the script with the modified aircraft definition self.sandbox.delete_csv_files() fdm = CreateFDM(self.sandbox) # We need to tell JSBSim that the aircraft definition is located in the # directory build/.../aircraft fdm.set_aircraft_path('aircraft') fdm.set_output_directive(self.sandbox.path_to_jsbsim_file('tests', 'output.xml')) fdm.load_script(self.script) fdm.set_property_value('simulation/randomseed', 0.0) fdm.run_ic() ExecuteUntil(fdm, 50.0) mod = Table() mod.ReadCSV(self.sandbox('output.csv')) # Whether the data is read from the aircraft definition file or from an # external file, the results shall be exactly identical. Hence the # precision set to 0.0. diff = self.ref.compare(mod, 0.0) self.assertTrue(diff.empty(), msg='\nTesting section "'+section+'"\n'+repr(diff)) def test_model_loading(self): self.longMessage = True self.BuildReference('c1724.xml') output_ref = Table() output_ref.ReadCSV(self.sandbox('JSBout172B.csv')) self.ProcessAndCompare('aerodynamics') self.ProcessAndCompare('autopilot') self.ProcessAndCompare('flight_control') self.ProcessAndCompare('ground_reactions') self.ProcessAndCompare('mass_balance') self.ProcessAndCompare('metrics') self.ProcessAndCompare('propulsion') self.ProcessAndCompare('system') # The <output> section needs special handling. In addition to the check # conducted by ProcessAndCompare with a directive file, we need to # verify that the <output> tag has been correctly executed by JSBSim. # In the case of the script c1724.xml, this means that the data output # in JSBout172B.csv is the same between the reference 'output_ref' and # the result 'mod' below where the <output> tag was moved in a separate # file. self.ProcessAndCompare('output') mod = Table() mod.ReadCSV(self.sandbox('JSBout172B.csv')) diff = output_ref.compare(mod, 0.0) self.assertTrue(diff.empty(), msg='\nTesting section "output"\n'+repr(diff)) self.BuildReference('weather-balloon.xml') self.ProcessAndCompare('buoyant_forces') self.BuildReference('Concorde_runway_test.xml') self.ProcessAndCompare('external_reactions')
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, see <http://www.gnu.org/licenses/> # import sys from JSBSim_utils import CreateFDM, Table, SandBox sandbox = SandBox('check_cases', 'orbit') fdm = CreateFDM(sandbox) fdm.load_script(sandbox.path_to_jsbsim_file('scripts', 'ball_orbit.xml')) fdm.run_ic() while fdm.run(): pass ref, current = Table(), Table() ref.ReadCSV(sandbox.elude(sandbox.path_to_jsbsim_file('logged_data', 'BallOut.csv'))) current.ReadCSV(sandbox('BallOut.csv')) diff = ref.compare(current) if not diff.empty(): print diff sys.exit(-1) # Needed for 'make test' to report the test passed. sandbox.erase()
class CheckOutputRate(unittest.TestCase): def setUp(self): self.sandbox = SandBox() self.fdm = CreateFDM(self.sandbox) self.script_path = self.sandbox.path_to_jsbsim_file("scripts", "c1722.xml") # Read the time step 'dt' from the script file self.tree = et.parse(self.sandbox.elude(self.script_path)) root = self.tree.getroot() use_tag = root.find("./use") aircraft_name = use_tag.attrib["aircraft"] self.run_tag = root.find("./run") self.dt = float(self.run_tag.attrib["dt"]) # Read the date at which the trim will be run event_tags = root.findall("./run/event") for event in event_tags: if event.attrib["name"] == "Trim": cond_tag = event.find("./condition") self.trim_date = float(string.split(cond_tag.text)[-1]) break # Read the output rate and the output file from the aircraft file aircraft_path = self.sandbox.path_to_jsbsim_file("aircraft", aircraft_name, append_xml(aircraft_name)) tree = et.parse(self.sandbox.elude(aircraft_path)) output_tag = tree.getroot().find("./output") self.output_file = self.sandbox(output_tag.attrib["name"]) self.rateHz = float(output_tag.attrib["rate"]) self.rate = int(1.0 / (self.rateHz * self.dt)) def tearDown(self): del self.fdm self.sandbox.erase() def testOutputRate(self): self.fdm.load_script(self.script_path) # Check that the output is enabled by default self.assertEqual(self.fdm.get_property_value("simulation/output/enabled"), 1.0) # Check that the rate is consistent with the values extracted from the # script and the aircraft definition self.assertAlmostEqual(self.fdm.get_property_value("simulation/output/log_rate_hz"), self.rateHz, delta=1e-5) self.fdm.run_ic() for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # According to the settings, the output file must contain 2 lines in # addition to the headers : # 1. The initial conditions # 2. The output after 'rate' iterations self.assertEqual(output.get_column(0)[1], 0.0) self.assertEqual(output.get_column(0)[2], self.rate * self.dt) self.assertEqual(output.get_column(0)[2], self.fdm.get_property_value("simulation/sim-time-sec")) def testDisablingOutput(self): self.fdm.load_script(self.script_path) # Disables the output during the initialization self.fdm.set_property_value("simulation/output/enabled", 0.0) self.fdm.run_ic() self.fdm.set_property_value("simulation/output/enabled", 1.0) for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # According to the settings, the output file must contain 1 line in # addition to the headers : # 1. The output after 'rate' iterations self.assertEqual(output.get_column(0)[1], self.fdm.get_property_value("simulation/sim-time-sec")) def testTrimRestoresOutputSettings(self): self.fdm.load_script(self.script_path) # Disables the output during the initialization self.fdm.set_property_value("simulation/output/enabled", 0.0) self.fdm.run_ic() # Check that the output remains disabled even after the trim is # executed while self.fdm.get_property_value("simulation/sim-time-sec") < self.trim_date + 2.0 * self.dt: self.fdm.run() self.assertEqual(self.fdm.get_property_value("simulation/output/enabled"), 0.0) # Re-enable the output and check that the output rate is unaffected by # the previous operations self.fdm.set_property_value("simulation/output/enabled", 1.0) frame = int(self.fdm.get_property_value("simulation/frame")) for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # The frame at which the data is logged must be the next multiple of # the output rate self.assertEqual(int(output.get_column(0)[1] / self.dt), (1 + frame / self.rate) * self.rate) def testDisablingOutputInScript(self): property = et.SubElement(self.run_tag, "property") property.text = "simulation/output/enabled" property.attrib["value"] = "0.0" self.tree.write(self.sandbox("c1722_0.xml")) self.fdm.load_script("c1722_0.xml") # Check that the output is disabled self.assertEqual(self.fdm.get_property_value("simulation/output/enabled"), 0.0) self.fdm.run_ic() self.fdm.set_property_value("simulation/output/enabled", 1.0) for i in xrange(self.rate): self.fdm.run() output = Table() output.ReadCSV(self.output_file) # According to the settings, the output file must contain 1 line in # addition to the headers : # 1. The output after 'rate' iterations self.assertEqual(output.get_column(0)[1], self.fdm.get_property_value("simulation/sim-time-sec"))