Esempio n. 1
0
    def test_load_bad_aircraft_id(self):
        bad_name = 'qwertyuiop'
        bad_aircraft = aircraft.Aircraft(bad_name, '', '', 100.)

        with self.assertRaises(RuntimeError):
            self.sim = None
            self.sim = Simulation(aircraft=bad_aircraft)
Esempio n. 2
0
    def test_initialise_conditions_basic_config(self):
        plane = aircraft.f15

        # manually reset JSBSim instance with new initial conditions
        if self.sim:
            self.sim.close()
        sim_frequency = 2
        self.sim = Simulation(sim_frequency_hz=sim_frequency, aircraft=plane, init_conditions=None)

        self.assertEqual(self.sim.get_loaded_model_name(), plane.jsbsim_id,
                         msg='JSBSim did not load expected aircraft model: ' +
                         self.sim.get_loaded_model_name())

        # check that properties are as we expected them to be
        expected_values = {
            prp.initial_u_fps: 328.0,
            prp.initial_v_fps: 0.0,
            prp.initial_w_fps: 0.0,
            prp.u_fps: 328.0,
            prp.v_fps: 0.0,
            prp.w_fps: 0.0,
            prp.BoundedProperty('simulation/dt', '', None, None): 1 / sim_frequency
        }

        for prop, expected in expected_values.items():
            actual = self.sim[prop]
            self.assertAlmostEqual(expected, actual)
Esempio n. 3
0
    def reset(self):
        """

        Resets the state of the environment and returns an initial observation.

        :return: array, the initial observation of the space.

        """
        if self.sim:
            self.sim.close()

        self.sim = Simulation(
            aircraft_name=self.task.aircraft_name,
            init_conditions=self.task.init_conditions,
            jsbsim_freq=self.task.jsbsim_freq,
            agent_interaction_steps=self.task.agent_interaction_steps,
        )

        self.state = self.get_observation()

        self.observation_space = self.task.get_observation_space()

        self.action_space = self.task.get_action_space()

        return self.state
Esempio n. 4
0
    def test_load_model(self):
        plane = aircraft.a320
        self.sim = None
        self.sim = Simulation(aircraft=plane)
        actual_name = self.sim.get_loaded_model_name()

        self.assertEqual(plane.jsbsim_id, actual_name,
                         msg=f'Unexpected aircraft model name after loading.')
Esempio n. 5
0
def basic_task():
    """ A simple task involving initing a JSBSimInstance to test multiprocessing. """
    model = aircraft.cessna172P
    time.sleep(0.05)
    fdm = Simulation(aircraft=model)
    fdm.run()
    time.sleep(0.05)

    return 0
Esempio n. 6
0
    def _new_episode_init(self, sim: Simulation) -> None:
        """
        This method is called at the start of every episode. It is used to set
        the value of any controls or environment properties not already defined
        in the task's initial conditions.

        By default it simply starts the aircraft engines.
        """
        sim.start_engines()
        sim.raise_landing_gear()
        self._store_reward(RewardStub(1.0, 1.0), sim)
Esempio n. 7
0
    def task_step(self, sim: Simulation, action: Sequence[float], sim_steps: int) \
            -> Tuple[NamedTuple, float, bool, Dict]:
        # input actions
        for prop, command in zip(self.action_variables, action):
            sim[prop] = command

        # run simulation
        for _ in range(sim_steps):
            sim.run()

        self._update_custom_properties(sim)
        state = self.State(*(sim[prop] for prop in self.state_variables))
        done = self._is_terminal(sim)
        reward = self._get_reward(state, sim)
        if self.debug:
            self._validate_state(state, done, action, reward)
        self._store_reward(reward, sim)
        self.last_state = state
        info = {'reward': reward, 'action': action}

        return state, reward, done, info
Esempio n. 8
0
    def test_initialise_conditions_custom_config(self):
        """ Test JSBSimInstance initialisation with custom initial conditions. """

        plane = aircraft.f15
        init_conditions = {
            prp.initial_u_fps: 1000.0,
            prp.initial_v_fps: 0.0,
            prp.initial_w_fps: 1.0,
            prp.initial_altitude_ft: 5000,
            prp.initial_heading_deg: 12,
            prp.initial_r_radps: -0.1,
        }
        # map JSBSim initial condition properties to sim properties
        init_to_sim_conditions = {
            prp.initial_u_fps: prp.u_fps,
            prp.initial_v_fps: prp.v_fps,
            prp.initial_w_fps: prp.w_fps,
            prp.initial_altitude_ft: prp.altitude_sl_ft,
            prp.initial_heading_deg: prp.heading_deg,
            prp.initial_r_radps: prp.r_radps,
        }
        sim_frequency = 10

        # manually reset JSBSim instance
        if self.sim:
            self.sim.close()
        self.sim = Simulation(sim_frequency, plane, init_conditions)

        # check JSBSim initial condition and simulation properties
        for init_prop, expected in init_conditions.items():
            sim_prop = init_to_sim_conditions[init_prop]

            init_actual = self.sim[init_prop]
            sim_actual = self.sim[sim_prop]
            self.assertAlmostEqual(expected, init_actual,
                                   msg=f'wrong value for property {init_prop}')
            self.assertAlmostEqual(expected, sim_actual,
                                   msg=f'wrong value for property {sim_prop}')

        self.assertAlmostEqual(1.0 / sim_frequency, self.sim[prp.sim_dt])
Esempio n. 9
0
    def __init__(self, sim: Simulation, print_props: Tuple[prp.Property], block_until_loaded=True):
        """
        Launches FlightGear in subprocess and starts figure for plotting actions.

        :param sim: Simulation that will be visualised
        :param aircraft: Aircraft to be loaded in FlightGear for visualisation
        :param print_props: collection of Propertys to be printed to Figure
        :param block_until_loaded: visualiser will block until it detects that
            FlightGear has loaded if True.
        """
        self.configure_simulation_output(sim)
        self.print_props = print_props
        self.flightgear_process = self._launch_flightgear(sim.get_aircraft())
        self.figure = FigureVisualiser(sim, print_props)
        if block_until_loaded:
            time.sleep(20)
Esempio n. 10
0
 def _new_episode_init(self, sim: Simulation) -> None:
     super()._new_episode_init(sim)
     sim.set_throttle_mixture_controls(self.THROTTLE_CMD, self.MIXTURE_CMD)
     sim[self.steps_left] = self.steps_left.max
     sim[self.target_track_deg] = self._get_target_track()
Esempio n. 11
0
class TestSimulation(unittest.TestCase):
    sim: Simulation = None

    def setUp(self):
        if self.sim:
            self.sim.close()
        self.sim = Simulation()

    def tearDown(self):
        self.sim = None

    def test_init_jsbsim(self):
        self.assertIsInstance(self.sim.jsbsim, jsbsim.FGFDMExec,
                              msg=f'Expected Simulation.jsbsim to hold an '
                              'instance of JSBSim.')

    def test_load_model(self):
        plane = aircraft.a320
        self.sim = None
        self.sim = Simulation(aircraft=plane)
        actual_name = self.sim.get_loaded_model_name()

        self.assertEqual(plane.jsbsim_id, actual_name,
                         msg=f'Unexpected aircraft model name after loading.')

    def test_load_bad_aircraft_id(self):
        bad_name = 'qwertyuiop'
        bad_aircraft = aircraft.Aircraft(bad_name, '', '', 100.)

        with self.assertRaises(RuntimeError):
            self.sim = None
            self.sim = Simulation(aircraft=bad_aircraft)

    def test_get_property(self):
        self.setUp()
        # we expect certain values specified in the IC config XML file
        expected_values = {
            prp.initial_u_fps: 328.0,
            prp.initial_v_fps: 0.0,
            prp.initial_w_fps: 0.0,
            prp.u_fps: 328.0,
            prp.v_fps: 0.0,
            prp.w_fps: 0.0,
        }

        for prop, expected in expected_values.items():
            actual = self.sim[prop]
            self.assertAlmostEqual(expected, actual)

    def test_get_bad_property(self):
        self.setUp()
        bad_prop = prp.BoundedProperty("bad_prop_name", "", 0, 0)
        with self.assertRaises(KeyError):
            _ = self.sim[bad_prop]

    def test_set_property(self):
        self.setUp()
        set_values = {
            prp.altitude_sl_ft: 1000,
            prp.aileron_cmd: 0.2,
            prp.elevator_cmd: 0.2,
            prp.rudder_cmd: 0.2,
            prp.throttle_cmd: 0.2,
        }

        for prop, value in set_values.items():
            self.sim[prop] = value

        for prop, expected in set_values.items():
            actual = self.sim[prop]
            self.assertAlmostEqual(expected, actual)

    def test_initialise_conditions_basic_config(self):
        plane = aircraft.f15

        # manually reset JSBSim instance with new initial conditions
        if self.sim:
            self.sim.close()
        sim_frequency = 2
        self.sim = Simulation(sim_frequency_hz=sim_frequency, aircraft=plane, init_conditions=None)

        self.assertEqual(self.sim.get_loaded_model_name(), plane.jsbsim_id,
                         msg='JSBSim did not load expected aircraft model: ' +
                         self.sim.get_loaded_model_name())

        # check that properties are as we expected them to be
        expected_values = {
            prp.initial_u_fps: 328.0,
            prp.initial_v_fps: 0.0,
            prp.initial_w_fps: 0.0,
            prp.u_fps: 328.0,
            prp.v_fps: 0.0,
            prp.w_fps: 0.0,
            prp.BoundedProperty('simulation/dt', '', None, None): 1 / sim_frequency
        }

        for prop, expected in expected_values.items():
            actual = self.sim[prop]
            self.assertAlmostEqual(expected, actual)

    def test_initialise_conditions_custom_config(self):
        """ Test JSBSimInstance initialisation with custom initial conditions. """

        plane = aircraft.f15
        init_conditions = {
            prp.initial_u_fps: 1000.0,
            prp.initial_v_fps: 0.0,
            prp.initial_w_fps: 1.0,
            prp.initial_altitude_ft: 5000,
            prp.initial_heading_deg: 12,
            prp.initial_r_radps: -0.1,
        }
        # map JSBSim initial condition properties to sim properties
        init_to_sim_conditions = {
            prp.initial_u_fps: prp.u_fps,
            prp.initial_v_fps: prp.v_fps,
            prp.initial_w_fps: prp.w_fps,
            prp.initial_altitude_ft: prp.altitude_sl_ft,
            prp.initial_heading_deg: prp.heading_deg,
            prp.initial_r_radps: prp.r_radps,
        }
        sim_frequency = 10

        # manually reset JSBSim instance
        if self.sim:
            self.sim.close()
        self.sim = Simulation(sim_frequency, plane, init_conditions)

        # check JSBSim initial condition and simulation properties
        for init_prop, expected in init_conditions.items():
            sim_prop = init_to_sim_conditions[init_prop]

            init_actual = self.sim[init_prop]
            sim_actual = self.sim[sim_prop]
            self.assertAlmostEqual(expected, init_actual,
                                   msg=f'wrong value for property {init_prop}')
            self.assertAlmostEqual(expected, sim_actual,
                                   msg=f'wrong value for property {sim_prop}')

        self.assertAlmostEqual(1.0 / sim_frequency, self.sim[prp.sim_dt])

    def test_multiprocess_simulations(self):
        """
        JSBSim segfaults when multiple instances are run on one process.

        Let's confirm that we can launch multiple processes each with 1 instance.
        """
        processes = 4
        with multiprocessing.Pool(processes) as pool:
            # N.B. basic_task is a top level function that inits JSBSim
            future_results = [pool.apply_async(basic_task) for _ in range(processes)]
            results = [f.get() for f in future_results]

        good_exit_code = 0
        expected = [good_exit_code] * processes
        self.assertListEqual(results, expected,
                             msg="multiprocess execution of JSBSim failed")
Esempio n. 12
0
 def setUp(self):
     if self.sim:
         self.sim.close()
     self.sim = Simulation()
Esempio n. 13
0
class JSBSimEnv(gym.Env):
    """
    A class wrapping the JSBSim flight dynamics module (FDM) for simulating
    aircraft as an RL environment conforming to the OpenAI Gym Env
    interface.

    An JsbSimEnv is instantiated with a Task that implements a specific
    aircraft control task with its own specific observation/action space and
    variables and agent_reward calculation.

    ATTRIBUTION: this class implements the OpenAI Gym Env API. Method
    docstrings have been adapted or copied from the OpenAI Gym source code.
    """

    metadata = {'render.modes': ['human', 'csv']}

    def __init__(self, task):
        """

        Constructor. Init some internal state, but JSBSimEnv.reset() must be

        called first before interacting with environment.

        :param task: the Task for the task agent is to perform

        """

        self.sim = None
        self.task = task()

        self.observation_space = self.task.get_observation_space()  # None
        self.action_space = self.task.get_action_space()  # None

        self.state = None

    def step(self, action=None):
        """

        Run one timestep of the environment's dynamics. When end of

        episode is reached, you are responsible for calling `reset()`

        to reset this environment's state.

        Accepts an action and returns a tuple (observation, reward, done, info).



        :param action: np.array, the agent's action, with same length as action variables.

        :return:

            state: agent's observation of the current environment

            reward: amount of reward returned after previous action

            done: whether the episode has ended, in which case further step() calls are undefined

            info: auxiliary information

        """

        if action is not None:
            #print(action, self.action_space)
            #nb_action = 0
            # for x in action:
            #    nb_action += 1
            # print(nb_action)
            # print(len(self.action_space.spaces))
            if not len(action) == len(self.action_space.spaces):
                raise ValueError(
                    'mismatch between action and action space size')

        self.state = self.make_step(action)

        reward, done, info = self.task.get_reward(
            self.state, self.sim), self.is_terminal(), {}
        state = self.state if not done else self._get_clipped_state(
        )  # returned state should be in observation_space

        return state, reward, done, info

    def make_step(self, action=None):
        """

        Calculates new state.


        :param action: array of floats, the agent's last action

        :return: observation: array, agent's observation of the environment state


        """
        # take actions
        if action is not None:
            self.sim.set_property_values(self.task.get_action_var(), action)

        # run simulation
        self.sim.run()

        return self.get_observation()

    def reset(self):
        """

        Resets the state of the environment and returns an initial observation.

        :return: array, the initial observation of the space.

        """
        if self.sim:
            self.sim.close()

        self.sim = Simulation(
            aircraft_name=self.task.aircraft_name,
            init_conditions=self.task.init_conditions,
            jsbsim_freq=self.task.jsbsim_freq,
            agent_interaction_steps=self.task.agent_interaction_steps)

        self.state = self.get_observation()

        self.observation_space = self.task.get_observation_space()

        self.action_space = self.task.get_action_space()

        return self.state

    def is_terminal(self):
        """

        Checks if the state is terminal.

        :return: bool

        """
        is_not_contained = not self.observation_space.contains(self.state)

        return is_not_contained or self.task.is_terminal(self.state, self.sim)

    def render(self, mode='human', **kwargs):
        """Renders the environment.

        The set of supported modes varies per environment. (And some

        environments do not support rendering at all.) By convention,

        if mode is:

        - human: print on the terminal
        - csv: output to cvs files

        Note:

            Make sure that your class's metadata 'render.modes' key includes

              the list of supported modes. It's recommended to call super()

              in implementations to use the functionality of this method.



        :param mode: str, the mode to render with
        """
        return self.task.render(self.sim, mode=mode, **kwargs)

    def seed(self, seed=None):
        """

        Sets the seed for this env's random number generator(s).

        Note:

            Some environments use multiple pseudorandom number generators.

            We want to capture all such seeds used in order to ensure that

            there aren't accidental correlations between multiple generators.

        Returns:

            list<bigint>: Returns the list of seeds used in this env's random

              number generators. The first value in the list should be the

              "main" seed, or the value which a reproducer should pass to

              'seed'. Often, the main seed equals the provided 'seed', but

              this won't be true if seed=None, for example.

        """
        return

    def close(self):
        """ Cleans up this environment's objects



        Environments automatically close() when garbage collected or when the

        program exits.

        """
        if self.sim:
            self.sim.close()

    def get_observation(self):
        """
        get state observation from sim.

        :return: NamedTuple, the first state observation of the episode

        """
        obs_list = self.sim.get_property_values(
            self.task.get_observation_var())
        return tuple([np.array([obs]) for obs in obs_list])

    def get_sim_time(self):
        """ Gets the simulation time from sim, a float. """
        return self.sim.get_sim_time()

    def get_state(self):
        return self.sim.get_sim_state()

    def _get_clipped_state(self):
        clipped = [
            np.clip(self.state[i], o.low, o.high)
            if self.task.state_var[i].clipped else self.state[i]
            for i, o in enumerate(self.observation_space)
        ]
        return tuple(clipped)

    def set_state(self, state):
        self.sim.set_sim_state(state)
        self.state = self.get_observation()
Esempio n. 14
0
 def _init_new_sim(self, dt, aircraft, initial_conditions):
     return Simulation(sim_frequency_hz=dt,
                       aircraft=aircraft,
                       init_conditions=initial_conditions)
Esempio n. 15
0
 def _init_new_sim(self, dt: float, aircraft: Aircraft, initial_conditions: Dict):
     return Simulation(sim_frequency_hz=dt,
                       aircraft=aircraft,
                       init_conditions=initial_conditions,
                       allow_flightgear_output=False)
Esempio n. 16
0
 def configure_simulation_output(self, sim: Simulation):
     sim.enable_flightgear_output()
     sim.set_simulation_time_factor(self.FLIGHTGEAR_TIME_FACTOR)