runner.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Copyright (c) 2013, Felix Kolbe
00004 # All rights reserved. BSD License
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 # * Redistributions of source code must retain the above copyright
00011 #   notice, this list of conditions and the following disclaimer.
00012 #
00013 # * Redistributions in binary form must reproduce the above copyright
00014 #   notice, this list of conditions and the following disclaimer in the
00015 #   documentation and/or other materials provided with the distribution.
00016 #
00017 # * Neither the name of the {organization} nor the names of its
00018 #   contributors may be used to endorse or promote products derived
00019 #   from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00024 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
00025 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00026 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
00027 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00028 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00029 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00030 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
00031 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00032 
00033 
00034 import roslib; roslib.load_manifest('goap')
00035 
00036 from time import sleep
00037 
00038 import rgoap
00039 
00040 from common import Condition, WorldState, stringify, stringify_dict
00041 from memory import Memory
00042 from planning import Planner, PlanExecutor
00043 
00044 
00045 import logging
00046 _logger = logging.getLogger('rgoap')
00047 
00048 
00049 
00050 class Runner(object):
00051     """
00052     self.memory: memory to be used for conditions and actions
00053     self.worldstate: the default/start worldstate
00054     self.actions: the actions this runner uses
00055     self.planner: the planner this runner uses
00056     """
00057 
00058     def __init__(self, config_module=None):
00059         """
00060         param:config_module: a scenario/robot specific module to prepare setup,
00061                 that has the following members:
00062                     get_all_conditions() -> return a list of conditions
00063                     get_all_actions() -> return a list of actions
00064         """
00065         self.memory = Memory()
00066         self.worldstate = WorldState()
00067         self.actions = set()
00068 
00069         if config_module is not None:
00070             for condition in config_module.get_all_conditions(self.memory):
00071                 Condition.add(condition)
00072             for action in config_module.get_all_actions(self.memory):
00073                 self.actions.add(action)
00074 
00075         self.planner = Planner(self.actions, self.worldstate, None)
00076 
00077         self._last_goal = None
00078         self._preempt_requested = False # preemption mechanism
00079 
00080 
00081     def __repr__(self):
00082         return '<%s memory=%s worldstate=%s actions=%s planner=%s>' % (self.__class__.__name__,
00083                                 self.memory, self.worldstate, self.actions, self.planner)
00084 
00085 
00086     def request_preempt(self):
00087         self._preempt_requested = True
00088 
00089     def preempt_requested(self):
00090         return self._preempt_requested
00091 
00092     def service_preempt(self):
00093         self._preempt_requested = False
00094 
00095 
00096     def _update_worldstate(self):
00097         """update worldstate to reality"""
00098         Condition.initialize_worldstate(self.worldstate)
00099         _logger.info("worldstate initialized/updated to: %s", self.worldstate)
00100 
00101     def _check_conditions(self):
00102         # check for any still uninitialised condition
00103         for (condition, value) in self.worldstate._condition_values.iteritems():
00104             if value is None:
00105                 _logger.warn("Condition still 'None': %s", condition)
00106 
00107 
00108     def update_and_plan(self, goal, tries=1, introspection=False):
00109         """update worldstate and call self.plan(...), repeating for
00110         number of tries or until a plan is found"""
00111         assert tries >= 1
00112         while tries > 0:
00113             tries -= 1
00114             self._update_worldstate()
00115             start_node = self.plan(goal, introspection)
00116             if start_node is not None:
00117                 break
00118             if tries > 0: # if there are tries left
00119                 _logger.warn("Runner retrying in update_and_plan")
00120         return start_node
00121 
00122 
00123     def plan(self, goal, introspection=False):
00124         """plan for given goal and return start_node of plan or None"""
00125         self._check_conditions()
00126         return self.planner.plan(goal=goal)
00127 
00128 
00129 
00130     def plan_and_execute_goals(self, goals):
00131         """Sort goals by usability and try to plan and execute one by one until
00132         one goal is achieved"""
00133         self._update_worldstate()
00134 
00135         # sort goals
00136         goals.sort(key=lambda goal: goal.usability, reverse=True)
00137 
00138         if _logger.isEnabledFor(logging.INFO):
00139             _logger.info("Available goals:\n%s", stringify(goals, '\n'))
00140 
00141         # plan until plan for one goal found
00142         for goal in goals:
00143             # skip goal we used last time
00144             if self._last_goal is goal:
00145                 continue
00146 
00147             if self.preempt_requested():
00148                 self.service_preempt()
00149                 return 'preempted'
00150 
00151             plan = self.plan(goal)
00152             if plan is None:
00153                 continue # try next goal
00154 
00155             # execution
00156             self._last_goal = goal
00157             _logger.info("Executing most usable goal: %s", goal)
00158             _logger.info("With plan: %s", plan)
00159             outcome = self.execute(plan, introspection=True)
00160             _logger.info("Most usable goal returned: %s", outcome)
00161             if outcome == 'aborted':
00162                 _logger.warn("Executed goal return 'aborted', trying next goal")
00163                 continue # try next goal
00164 
00165             return outcome
00166 
00167         _logger.error("For no goal a plan could be found!")
00168         outcome = 'aborted'
00169 
00170         return outcome
00171 
00172 
00173     def update_and_plan_and_execute(self, goal, tries=1, introspection=False):
00174         """loop that updates, plans and executes until the goal is reached"""
00175         outcome = None
00176         # replan and retry on failure as long as a plan is found
00177         while not rgoap.is_shutdown():
00178             if self.preempt_requested():
00179                 self.service_preempt()
00180                 return 'preempted'
00181 
00182             start_node = self.update_and_plan(goal, tries, introspection)
00183 
00184             if start_node is None:
00185                 # TODO: maybe at this point update and replan, regardless of 'tries'? reality might have changed
00186                 _logger.error("RGOAP Runner aborts, no plan found!")
00187                 return 'aborted'
00188 
00189             outcome = self.execute(start_node, introspection)
00190 
00191             if outcome != 'aborted':
00192                 break # retry
00193 
00194             # check failure
00195             _logger.warn("RGOAP Runner execution fails, replanning..")
00196 
00197             self._update_worldstate()
00198             if not goal.is_valid(self.worldstate):
00199                 _logger.warn("Goal isn't valid in current worldstate")
00200             else:
00201                 _logger.error("Though goal is valid in current worldstate, the plan execution failed!?")
00202 
00203         # until we are succeeding or are preempted
00204         return outcome
00205 
00206 
00207     def execute(self, start_node, introspection=False):
00208         success = PlanExecutor().execute(start_node)
00209         return success
00210 
00211     def print_worldstate_loop(self):
00212         while not rgoap.is_shutdown():
00213             self._update_worldstate()
00214             _logger.info("%s", self.worldstate)
00215             sleep(2)
00216 


rgoap
Author(s): Felix Kolbe
autogenerated on Sun Oct 5 2014 23:53:02