def unify_obs(observation): ''' TODO: accumulate and apply them all at once ''' action = copy.deepcopy(observation.action) action.update_start_time(observation.start_time) action.update_end_time(observation.end_time) # Constraint check action_cons, return_state = constraints_satisfied(action, Plan(), Proposed(), is_observation=True) if not action_cons: return_state.update_subs({ var('T1'): observation.start_time, var('T2'): observation.end_time, }) process_return_state(observation, return_state) return KB.log_action_new(action, from_obs=True) KB.add_cycle_obs(observation) initiates, terminates = process_causalities(action) return initiates, terminates
def iff(self, *args): converted = [(arg, True) for arg in args if not isinstance(arg, tuple)] converted += [arg for arg in args if isinstance(arg, tuple)] KB.set_causality_reqs(self, converted) return self
def process_return_state(observation, return_state): # TODO: Optional display message for return state KB.log_rejected_observation(observation) subs = return_state.subs for constraint in return_state.goals: obj = constraint.goal res = reify_obj_args(obj, subs)
def _obs_before(self): KB.clear_cycle_obs(self.current_time) self._check_observations() self._check_rules() self._solve_plans() commit_outcomes(self.initiated, self.terminated)
def false_if(*args): converted = [] for arg in args: if isinstance(arg, tuple): converted.append(arg) else: converted.append((arg, True)) KB.add_constraint(converted)
def execute(n_solutions=CONFIG_DEFAULT_N_SOLUTIONS, single_clause=False, solution_preference=SOLN_PREF_FIRST, debug=False, experimental=True, strategy=STRATEGY_GREEDY, stepwise=False, obs=OBS_BEFORE): '''Execute pyLPS program Keyword arguments: n_solutions -- the number of solutions, use -1 for all solutions (default 1) single_clause -- only consider the first clause for an event (default true) solutions_preference -- the type of solution to favour (defaults to first) ''' if solution_preference is SOLN_PREF_MAX: # If we want maximum solutions, override n_solutions if it is default if n_solutions == CONFIG_DEFAULT_N_SOLUTIONS: n_solutions = -1 # All solutions if -1 if n_solutions == -1: n_solutions = 10000000 options_dict = { 'n_solutions': n_solutions, 'obs': obs, 'single_clause': single_clause, 'solution_preference': solution_preference, # Development 'debug': debug, 'experimental': experimental, 'strategy': strategy, } # Resets CONFIG.reactive_id = 0 KB.reset_kb() # Initially # for fluent in KB.initial_fluents: # KB.add_fluent(fluent) # KB.log_fluent(fluent, 0, F_INITIATE) CONFIG.set_options(options_dict) ENGINE.run(stepwise=stepwise)
def process_cycle(cycle_events): ''' TODO - Delay all commitment into KB until all are processed ''' for event in cycle_events: # Convert args for action converted_args = convert_args_to_python(event) # Add into observations KB.add_cycle_obs(Observation(event, event.start_time, event.end_time)) # Log action KB.log_action_new(event, converted_args=converted_args) initiates, terminates = process_causalities(event) commit_outcomes(initiates, terminates)
def unify_fluent(cond, cycle_time, counter=0): fluent = cond temporal_var = cond.time # debug_display(fluent, KB.exists_fluent(fluent)) substitutions = {} grounded = is_grounded(fluent) # Handle the grounded case if grounded: # Check if fluent is in KB if not KB.exists_fluent(fluent): return None # Unify with temporal vars, return the substitution substitutions.update(unify( var(temporal_var.name.split(VAR_SEPARATOR)[0] + VAR_SEPARATOR + str(counter)), cycle_time)) yield substitutions kb_fluents = KB.get_fluents(fluent) for kb_fluent in kb_fluents: unify_res = unify_args(fluent.args, kb_fluent.args) if unify_res == {}: continue # debug_display('FLUENT_UNIFY_RES', unify_res) unify_res.update(unify( var(temporal_var.name.split(VAR_SEPARATOR)[0] + VAR_SEPARATOR + str(counter)), cycle_time )) yield unify_res return substitutions
def generate_outcome_fluents(fluent): ret = [] if is_grounded(fluent): return [fluent] for kb_fluent in KB.get_fluents(fluent): unify_subs = unify_args(fluent.args, kb_fluent.args) kb_fluent.args = reify_args(kb_fluent.args, unify_subs) fluent_args = reify_args(fluent.args, unify_subs) if kb_fluent.args == fluent_args: ret.append(kb_fluent) return ret
def process_causalities(action, deconflict=True): causalities = KB.exists_causality(action) initiates = OrderedSet() terminates = OrderedSet() if not causalities: return OrderedSet(), OrderedSet() for causality in causalities: action_subs = unify_args(causality.action.args, action.args) ''' TODO: This check should be shifted into generating fluents Because the fluent might not be grounded yet ''' constraint_subs = _check_reqs(causality.reqs, action_subs) if not constraint_subs: continue # Handle the case where there is no constraint if isinstance(constraint_subs, bool): constraint_subs = [action_subs] for c_sub in constraint_subs: for causality_outcome in causality.outcomes: outcome = causality_outcome.outcome fluent = copy.deepcopy(causality_outcome.fluent) fluent.args = reify_args(fluent.args, c_sub) # TODO: Constraint check on solution check_outcome_constraint(fluent) fluents = generate_outcome_fluents(fluent) if outcome is A_INITIATE: for f in fluents: initiates.add((f, action.end_time)) elif outcome is A_TERMINATE: for f in fluents: terminates.add((f, action.end_time)) else: raise UnknownOutcomeError(outcome) if deconflict: terminates = terminates - initiates return initiates, terminates
def commit_outcomes(initiates, terminates): for (fluent, time) in initiates: if KB.add_fluent(fluent): KB.log_fluent(fluent, time, F_INITIATE) for (fluent, time) in terminates: if KB.remove_fluent(fluent): KB.log_fluent(fluent, time, F_TERMINATE)
def add_to_cycle_proposed(cycle_proposed, state): actions = reify_actions(state, reify=True) cycle_proposed.add_actions(actions) for action in actions: causalities = KB.exists_causality(action) if not causalities: continue for causality in causalities: action_subs = unify_args(causality.action.args, action.args) for causality_outcome in causality.outcomes: reify_outcome = copy.deepcopy(causality_outcome) reify_outcome.fluent.args = reify_args_constraint_causality( reify_outcome.fluent.args, action_subs) cycle_proposed.add_fluent(reify_outcome)
def unify_fact(fact, reactive=False): substitutions = [] kb_facts = KB.get_facts(fact, reactive) grounded = is_grounded(fact) if grounded: for kb_fact in kb_facts: if fact.args == kb_fact.args: yield True yield False for kb_fact in kb_facts: unify_res = unify_args(fact.args, kb_fact.args) if unify_res == {}: continue yield unify_res return substitutions
def show_kb_log(show_events=False): return KB.show_log(show_events=show_events)
def _check_rules(self): # Check rules for rule in KB.rules: # Handle triggering from constant if rule.constant_trigger: continue conds = [] true_trigger = False only_facts = True for cond in rule.conds: cond_object = copy.deepcopy(cond) # TODO: What if its hidden inside an EXPR? # Flag setting for fact triggers if cond_object.BaseClass != FACT: only_facts = False cond_object.from_reactive = True rename_args(0, cond_object) conds.append(cond_object) # Special case for rules beginning with True if len(conds) == 1 and conds[0].BaseClass is CONSTANT and \ conds[0].const is True: true_trigger = True rule._constant_trigger = True state_list = [Plan([], {}, result=G_SOLVED)] # fact only if only_facts: rule._constant_trigger = True if not true_trigger: state_list = list(SOLVER.backtrack_solve( start=Plan(conds, {}), # REMOVED_DEEPCOPY reactive=True, only_facts=only_facts, current_time=self.current_time )) # debug_display('STATE_LIST', self.current_time, state_list) if not state_list: continue for state in state_list: subs = state.subs result = state.result # TODO: Not exactly, there may just be facts with constant if subs == {} and not (true_trigger or result is G_DEFER): continue new_goals = reify_goals(rule.goals, subs) if result is G_SOLVED: KB.add_plan(new_goals, subs) if result is G_DEFER: defer = list(state.goals)[state.goal_pos:] new_goals = defer + new_goals KB.add_plan(new_goals, subs)
def show_kb_rules(): return KB.show_reactive_rules()
def process_solutions(solutions, cycle_time): maximum_solved = max([sol.solved for sol in solutions]) new_kb_goals = [] # Ensure that actions executed per cycle are unique processed = set() solved_goals = set() cycle_events = OrderedSet() for solution in solutions: for state in solution.states: if state.result is G_SOLVED: solved_goals.add(state.reactive_id) processed.add(state.reactive_id) cycle_events |= state.actions elif state.result is G_DEFER: if state.reactive_id in solved_goals: continue processed.add(state.reactive_id) # Kept because of the reactive_id possibly being solved cycle_events |= state.actions new_state = state # REMOVED DEEPCOPY # Clear actions / fluents and set to unprocessed new_state.clear_actions() new_state.clear_fluents() new_state.set_result(G_NPROCESSED) # Allow another temporal sub new_state.set_temporal_used(False) new_state.compress() new_kb_goals.append(new_state) elif state.result is G_DISCARD: processed.add(state.reactive_id) continue elif state.result is G_NPROCESSED: continue if maximum_solved > 0 and solution.solved == maximum_solved: break process_cycle(cycle_events) unsolved_existing_goals = OrderedSet() for start_state in KB.plans: if start_state.reactive_id in processed or \ start_state.reactive_id in solved_goals: continue unsolved_existing_goals.add(start_state) for state in new_kb_goals: if state.reactive_id in solved_goals: continue unsolved_existing_goals.add(state) KB.set_plans(unsolved_existing_goals)
def observe(obs): # TODO: Make observations iterable? obs = Observation(obs, obs.start_time, obs.end_time) KB.add_observation(obs)
def kb_display_log(show_events=False, print_log=False): KB.show_log(show_events=show_events, print_log=False) return KB.display_log
def event(*args): new_clause = GoalClause(args) KB.add_clause(new_clause) return new_clause
def reactive_rule(*args): new_rule = ReactiveRule(args) KB.add_rule(new_rule) return new_rule
def goal(*args): new_clause = GoalClause(args) KB.add_clause(new_clause) return new_clause
def terminates(self, fluent): self.fluent = fluent KB.add_causality_outcome(self, A_TERMINATE) return self
def show_kb_fluents(): return KB.show_fluents()
def constraints_satisfied(o_goal, state, cycle_proposed: Proposed, is_observation=False): constraints = KB.get_constraints(o_goal) causalities = KB.exists_causality(o_goal) if not constraints: return (True, True) if is_observation else True # Handle goal goal = reify_obj_args(o_goal, state.subs) # print(goal) all_proposed = copy.deepcopy(cycle_proposed) # The new action all_proposed._actions.add(goal) all_proposed._actions = OrderedSet( [reify_action(c_action, state.subs) for c_action in all_proposed._actions] ) for obs in KB.cycle_obs: end_time = o_goal.end_time if not isinstance(end_time, int): end_time = reify(var(end_time.name), state.subs) # print(obs.action.end_time, end_time) if obs.end_time == end_time: all_proposed._actions.add(obs.action) if causalities: for causality in causalities: action_subs = unify_args(causality.action.args, goal.args) for causality_outcome in causality.outcomes: reify_outcome = copy.deepcopy(causality_outcome) reify_outcome.fluent.args = reify_args_constraint_causality( reify_outcome.fluent.args, action_subs) if reify_outcome in all_proposed.fluents: continue all_proposed.add_fluent(reify_outcome) # REMOVED_DEEPCOPY # TODO: Check this addition for duplicates co_cons = KB.get_constraints(causality_outcome.fluent) if co_cons: constraints.extend(co_cons) for constraint in constraints: try: res = next(check_constraint(constraint, all_proposed)) if is_observation: return (False, res) return False except StopIteration: continue if is_observation: return (True, True) return True
def _handle_initial(self): for fluent in KB.initial_fluents: KB.add_fluent(fluent) KB.log_fluent(fluent, 0, F_INITIATE)
def __pos__(self): KB.add_fact(self, force=True)
def initiates(self, fluent): self.fluent = fluent KB.add_causality_outcome(self, A_INITIATE) return self
def expand_fluent(constraint, cur_state, states, all_proposed): cons_fluent, outcome = constraint.goal, constraint.outcome cur_subs = cur_state.subs fluents = copy.deepcopy(KB.get_fluents(cons_fluent)) grounded = True for arg in cons_fluent.args: try: if not cur_subs.get(var(arg.name)): grounded = False except AttributeError: continue # debug_display('CONS_FLUENT', cons_fluent, outcome, cur_subs) # debug_display('CUR_SUBS', cur_subs) # debug_display('FROM KB', fluents) # debug_display('ALL_PROP', all_proposed) for causality_outcome in all_proposed.fluents: if causality_outcome.outcome == A_INITIATE: if causality_outcome.fluent in fluents: continue # TODO: Hotfix, force grounded if is_grounded(causality_outcome.fluent): # debug_display('CFLUENT', causality_outcome.fluent) fluents.append(causality_outcome.fluent) # TODO: Why does this work? # elif causality_outcome.outcome == A_TERMINATE: # if outcome: # pass # if causality_outcome.fluent not in fluents: # continue # fluents.remove(causality_outcome.fluent) # debug_display('FROM KB AFTER ADD', fluents) # debug_display() # No fluents found if not fluents: # Expect something if outcome: return new_state = cur_state # REMOVED_DEEPCOPY states.append(new_state) return matched = False for fluent in fluents: if grounded: res = reify_args(cons_fluent.args, cur_subs) if res == fluent.args: matched = True if outcome: new_state = copy.deepcopy(cur_state) states.append(new_state) continue continue new_state = copy.deepcopy(cur_state) cons_fluent_res = reify_args(cons_fluent.args, cur_subs) res = unify_args(cons_fluent_res, fluent.args) if res == {}: continue new_state.update_subs(res) states.append(new_state) if not outcome and not matched: new_state = cur_state # REMOVED_DEEPCOPY states.append(new_state)
def show_kb_facts(): return KB.show_facts()