# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER
# This file is part of the Neurorobotics Platform software
# Copyright (C) 2014,2015,2016,2017 Human Brain Project
# https://www.humanbrainproject.eu
#
# The Human Brain Project is a European Commission funded project
# in the frame of the Horizon2020 FET Flagship plan.
# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# ---LICENSE-END
"""
Implementation of the closed loop engine.
"""
__author__ = 'Georg Hinkel'
import hbp_nrp_cle as cle
from hbp_nrp_cle.cle.DeterministicClosedLoopEngine import DeterministicClosedLoopEngine
import time
import logging
import threading
from hbp_nrp_cle.cle.CLEInterface import ForcedStopException
logger = logging.getLogger('hbp_nrp_cle')
# pylint: disable=R0902
# the attributes are reasonable in this case
[docs]class ClosedLoopEngine(DeterministicClosedLoopEngine):
"""
Implementation of the closed loop engine that runs Transfer Functions (TF), brain
simulation (B) and world simulation (W) concurrently for best effort performance
World simulation is run in parallel in a separate process;
Transfer Functions and brain simulation are run as python Threads.
Notes about synchronization:
TF, B and W start at cle.clock == 0
The difference between clocks is within one CLE timestep:
i.e. abs(TF_t - B_t) < timestep , abs(TF_t - W_t) < timestep
e.g. rospy.get_time() in a Transfer Function will not return, very likely, the same t which
the TF has been called with (the t parameter).
What is guaranteed is that the time difference stays within one CLE timestep.
In fact, the components are waited on for step completion at the end of a simulated step
(run_step method) and their relative intra-step speed depends on their respective workload and
scheduling.
"""
def __init__(self,
robot_control_adapter,
robot_comm_adapter,
brain_control_adapter,
brain_comm_adapter,
transfer_function_manager,
external_module_manager,
dt):
"""
Create an instance of the cle.
:param robot_control_adapter: an instance of IRobotContolAdapter
:param robot_comm_adapter: an instance of IRobotCommunicationAdapter
:param brain_control_adapter: an instance of IBrainContolAdapter
:param brain_comm_adapter: an instance of IBrainCommunicationAdapter
:param transfer_function_manager: an instance of ITransferFunctionManager
:param dt: The CLE time step in seconds
"""
super(ClosedLoopEngine, self).__init__(robot_control_adapter, robot_comm_adapter,
brain_control_adapter, brain_comm_adapter,
transfer_function_manager, external_module_manager,
dt)
self.__tf_thread = None
self.__tf_start_event = threading.Event()
self.__tf_done_event = threading.Event()
# indicates that the simulation is shutting down
self.shutdown_event = threading.Event()
self.shutdown_event.clear()
# indicates that the TFs loop is over
self.tfs_stopped_event = threading.Event()
self.tfs_stopped_event.clear()
[docs] def run_step(self, timestep_s):
"""
Runs simulations and TFs for the given time step in seconds.
:param timestep_s: simulation time, in seconds
:return: Updated simulation time, otherwise -1
"""
self.__tf_start_event.set()
# robot simulation
self.rca_future = self.rca.run_step_async(timestep_s)
self.rcm.refresh_buffers(cle.clock)
# brain simulation
logger.debug("Run step: Brain simulation")
start = time.time()
self.bca.run_step(timestep_s * 1000.0)
self._bca_step_time = time.time() - start
self.bcm.refresh_buffers(cle.clock)
self._bca_elapsed_time += time.time() - start
# Wait for all threads to finish
# - World simulator
logger.debug("Run step: wait on robot simulation.")
try:
f = self.rca_future
f.result()
self._rca_elapsed_time += f.end - f.start
except ForcedStopException:
logger.warn("Run step: Simulation was brutally stopped.")
return -1
# - Transfer Functions
logger.debug("Run step: wait on Transfer functions.")
self.__tf_done_event.wait()
self.__tf_done_event.clear()
# update clock
cle.clock += timestep_s
logger.debug("Run_step: done !")
return cle.clock
def __run_tfs(self):
"""
Runs the Transfer Functions. To be executed in a separate thread
"""
# simulation loop, return when done
while not self.shutdown_event.isSet():
# step loop
while not self.stop_event.isSet():
# wait for step start
self.__tf_start_event.wait()
self.__tf_start_event.clear()
try:
self.tfm.run_tfs(cle.clock)
finally:
self.__tf_done_event.set()
# step is over
self.tfs_stopped_event.set()
# wait for the sim to be started again
self.tfs_stopped_event.wait()
[docs] def start(self):
"""
Starts the orchestrated simulations
"""
if super(ClosedLoopEngine, self).start() and not self.__tf_thread:
if not self.__tf_thread:
self.__tf_thread = threading.Thread(target=self.__run_tfs, name="TFs_THREAD")
self.__tf_thread.setDaemon(True)
self.__tf_thread.start()
# start/resume the TFs thread
self.tfs_stopped_event.clear()
[docs] def shutdown(self):
"""
Shuts down the simulation.
"""
self.shutdown_event.set()
super(ClosedLoopEngine, self).shutdown()