def _alter_constraints(self, constraints, count): """ Alters *count* constraints from the given constraints by choosing a new value (could be also 'dontcare'). """ constraints_candidates = constraints[:] # copy list if not constraints_candidates: for _constraint in self.goal.constraints: if _constraint.value != 'dontcare': constraints_candidates.append( Constraint(_constraint.slot, _constraint.value)) else: # any constraint from the current system actions has to be taken into consideration # make sure that constraints are part of the goal since noise could have influenced the # dialog -> given constraints must conform to the current goal constraints_candidates = list(filter(lambda x:\ not self.goal.is_inconsistent_constraint_strict(x), constraints_candidates)) if not constraints_candidates: return [] constraints_to_alter = common.numpy.random.choice( constraints_candidates, count, replace=False) new_constraints = [] for _constraint in constraints_to_alter: self.goal.excluded_inf_slot_values[_constraint.slot].add( _constraint.value) possible_values = self.goal.inf_slot_values[_constraint.slot][:] for _value in self.goal.excluded_inf_slot_values[_constraint.slot]: # remove values which have been tried already # NOTE values in self.excluded_inf_slot_values should always be in possible_values # because the same source is used for both and to initialize the goal possible_values.remove(_value) if not possible_values: # add 'dontcare' as last option possible_values.append('dontcare') # 'dontcare' value with some probability if common.random.random( ) < self.parameters['usermodel']['DontcareIfNoVenue']: value = 'dontcare' else: value = common.numpy.random.choice(possible_values) if not self.goal.update_constraint(_constraint.slot, value): # NOTE: this case should never happen! print( "The given constraints (probably by the system) are not part of the goal!" ) new_constraints.append(Constraint(_constraint.slot, value)) self.logger.dialog_turn("Goal altered! {} -> {}.".format( constraints_to_alter, new_constraints)) return new_constraints
def add_constraint(self, entries): entries = [int(e) for e in entries] if self.type == "T": c = Constraint(self.type, day=entries[0], time_slot=entries[1], teacher=self.id) else: c = Constraint(self.type, tc_avail=entries) self.CI.append(c) for ba in self.bas: ba.add_constraint(copy.deepcopy(c))
def clean(self, goal: Goal): """Cleans the agenda, i.e. makes sure that actions are consistent with goal and in the correct order. Args: goal: The goal which is needed to determine the consistent actions. """ cleaned_stack = [] # reverse order since most recent actions are on top of agenda for action in self.stack[::-1]: if action not in cleaned_stack: # NOTE sufficient if there is only one slot per (request) action # remove accomplished requests if (action.type is not UserActionType.Request or (action.slot in goal.requests and goal.requests[action.slot] is None) or not action.slot in goal.requests): # make sure to remove "old" inform actions if action.type is UserActionType.Inform: if not goal.is_inconsistent_constraint(Constraint(action.slot,\ action.value)): cleaned_stack.insert(0, action) else: cleaned_stack.insert(0, action) self.stack = cleaned_stack
def respond(self): """Gets n actions from the agenda, where n is drawn depending on the agenda or a pdf.""" # get some actions from the agenda assert len( self.agenda ) > 0, "Agenda is empty, this must not happen at this point!" if self.num_actions_next_turn > 0: # use and reset self.num_actions_next_turn if set num_actions = self.num_actions_next_turn self.num_actions_next_turn = -1 elif self.agenda.stack[-1].type == UserActionType.Bye: # pop all actions from agenda since agenda can only contain thanks (optional) and # bye action num_actions = -1 else: # draw amount of actions num_actions = min(len(self.agenda), common.numpy.random.choice( [1, 2, 3], p=[.6, .3, .1])) # hardcoded pdf # get actions from agenda user_actions = self.agenda.get_actions(num_actions) # copy needed for repeat action since they might be changed in other modules self.last_user_actions = copy.deepcopy(user_actions) for action in user_actions: if action.type == UserActionType.Inform: _constraint = Constraint(action.slot, action.value) # if _constraint in self.goal.constraints: if action in self.goal.missing_informs: self.goal.missing_informs.remove(action) return user_actions
def _receive_select(self, sys_act: SysAct): """Processes a select action from the system.""" # handle as request value_in_goal = False for slot, values in sys_act.slot_values.items(): for value in values: # do not consider 'dontcare' as any value if not self.goal.is_inconsistent_constraint_strict( Constraint(slot, value)): value_in_goal = True if value_in_goal: self._receive_request(sys_act) else: assert len(sys_act.slot_values.keys()) == 1,\ "There shall be only one slot in a select action." slot = list(sys_act.slot_values.keys())[0] # inform about correct value with some probability if common.random.random( ) < self.parameters['usermodel']['InformOnSelect']: self.agenda.push(UserAct(act_type=UserActionType.Inform, slot=slot,\ value=self.goal.get_constraint(slot), score=1.0)) for slot, values in sys_act.slot_values.items(): for value in values: self.agenda.push(UserAct(act_type=UserActionType.NegativeInform, slot=slot,\ value=value, score=1.0))
def _receive_confirm(self, sys_act): """Processes a confirm action from the system.""" for slot, _value in sys_act.slot_values.items(): value = _value[0] # there is always only one value if self.goal.is_inconsistent_constraint_strict( Constraint(slot, value)): # inform about correct value with some probability, otherwise deny value if common.random.random( ) < self.parameters['usermodel']['InformOnConfirm']: self.agenda.push( UserAct(act_type=UserActionType.Inform, slot=slot,\ value=self.goal.get_constraint(slot), score=1.0)) else: self.agenda.push(UserAct(act_type=UserActionType.NegativeInform, slot=slot,\ value=value, score=1.0)) else: # NOTE using inform currently since NLU currently does not support Affirm here and # NLU would tinker it into an Inform action anyway # self.agenda.push( # UserAct(act_type=UserActionType.Affirm, score=1.0)) self.agenda.push( UserAct(act_type=UserActionType.Inform, slot=slot, value=value, score=1.0))
def _receive_informbyname(self, sys_act): """Processes an informbyname action from the system.""" # check all system informs for offer inform_list = [] offers = [] for slot, value_list in sys_act.slot_values.items(): for value in value_list: if slot == 'name': offers.append(value) else: inform_list.append(Constraint(slot, value)) # check offer if offers: if self._check_offer(offers, inform_list): # valid offer for slot, value in inform_list: self.goal.fulfill_request(slot, value) # needed to make sure that not informed constraints (which have been turned into requests) # will be asked first (before ending the dialog too early) req_actions_not_in_goal = [] for action in self.agenda.get_actions_of_type(UserActionType.Request): if not action.slot in self.goal.requests: req_actions_not_in_goal.append(copy.deepcopy(action)) # goal might be fulfilled now if (self.goal.is_fulfilled() and not self.agenda.contains_action_of_type(UserActionType.Inform) and not req_actions_not_in_goal): self._finish_dialog()
def set_tools_constraints(self): for constr in self.rules["cell_constraints"]["tools_availability"]: if self.room == constr["room"]: c = Constraint("A", day=self.day, time_slot=self.time_slot, room=constr["room"], tools=constr["tools"], weight=1) self.C.append(c)
def build_intrinsic_constraints(self): self.CI = [] if self.type == "T": for constr in self.rules["ra_constraints"][self.type][str( self.id)]["constraints"]: c = Constraint(self.type, day=constr["day"], time_slot=constr["time_slot"], teacher=constr["teacher"]) self.CI.append(c) else: for constr in self.rules["ra_constraints"][self.type][str( self.id)]["constraints"]: c = Constraint(self.type, tc_avail=constr["T_courses_availability"]) self.CI.append(c)
def set_unavailability_constraints(self): for constr in self.rules["cell_constraints"]["unavailability"]: if constr["day"] == self.day and \ constr["time_slot"] == self.time_slot and \ constr["room"] == self.room: c = Constraint("U", day=constr["day"], time_slot=constr["time_slot"], teacher=constr["room"], weight=1) self.C.append(c)
def __init__(self, day, room, time_slot, rules): self.day = day self.room = room self.time_slot = time_slot self.rules = rules self.C = [Constraint("R", day=day, time_slot=time_slot, room=room)] self.bas = [] self.reservation = None self.set_unavailability_constraints() self.set_tools_constraints() self.f = open("messages.txt", "a")
def process_messages(self): while self.mq: message = self.mq.popleft() if message.type == 'partnership': if self.type == 'SG' and message.sender.type == 'SG': sg = message.sender t = message.partner_of_cell print( "[Message] RA {} received partnership message from sg {} about adding constraint from teacher {}" .format(self.name, sg.name, t.ra_id)) self.add_induced_constraint( Constraint("B", teacher=t.ra_id, owner_name=sg.name, owner=sg)) if message.type == 'partnership_cancelation': if self.type == 'SG' and message.sender.type == 'SG': sg = message.sender t = message.partner_of_cell print( "[Message] RA {} received partnership cancelation message from sg {} about removing constraint from teacher {}" .format(self.name, sg.name, t.ra_id)) self.remove_induced_constraint("B", sg.name) if message.type == 'reservation': print( "[Message] RA {} received reservation message from {} about reserving cell DAY {} TIME {} ROOM {}" .format(self.name, message.sender.name, message.partner_of_cell.day, message.partner_of_cell.time_slot, message.partner_of_cell.room)) self.add_induced_constraint( Constraint("I", cell=message.partner_of_cell, owner_name=message.sender.name, owner=message.sender)) if message.type == 'reservation_cancelation': print( "[Message] RA {} received reservation cancelation message from {}" .format(self.name, message.sender.name)) self.remove_induced_constraint("I", message.sender.name)
def set_reservation(self, cell): self.cancel_reservation() if cell.reservation is not None and cell.reservation != self.partnership: cell.reservation.cancel_reservation() self.reservation = True self.rCell = cell if cell.reservation is None: cell.reservation = self if cell.reservation == self: self.CR = [ Constraint("R", day=cell.day, time_slot=cell.time_slot, room=cell.room) ]
def _check_offer(self, offers, informed_constraints_by_system): """ Checks for an offer and returns True if the offer is valid. """ if not self._check_informs(informed_constraints_by_system): # reset offer in goal since inconsistencies have been detected and covered self.goal.requests[self.domain.get_primary_key()] = None return False # TODO maybe check for current offer first since alternative with name='none' by system # would trigger goal change -> what is the correct action in this case? if offers: if 'none' not in offers: # offer was given # convert informs of values != 'dontcare' to requests actions_to_convert = list(self.agenda.get_actions_of_type(UserActionType.Inform,\ consider_dontcare=False)) if len(self.goal.constraints) > 1 and len( actions_to_convert) == len(self.goal.constraints): # penalise too early offers self._repeat_last_actions() self.num_actions_next_turn = len(self.last_user_actions) return False # ask for values of remaining inform slots on agenda - this has two purposes: # 1. making sure that offer is consistent with goal # 2. making sure that inconsistent offers prolongate a dialog for action in actions_to_convert: self.agenda.push(UserAct(act_type=UserActionType.Request, slot=action.slot,\ value=None, score=1.0)) self.agenda.remove_actions_of_type(UserActionType.Inform) if self.goal.requests[ self.domain.get_primary_key()] is not None: if self.goal.requests[ self.domain.get_primary_key()] in offers: # offer is the same, don't change anything but treat offer as valid return True else: # offer is not the same, but did not request a new one # NOTE with current bst do not (negative) inform about the offer, because # it will only set the proability to zero -> will not be excluded # self.agenda.push(UserAct(act_type=UserActionType.NegativeInform,\ # slot=self.domain.get_primary_key(), value=offers[0])) return False else: for _offer in offers: if _offer not in self.excluded_venues: # offer is not on the exclusion list (e.g. from reqalt action) and # there is no current offer # sometimes ask for alternative if common.random.random( ) < self.parameters['usermodel']['ReqAlt']: self._request_alt(_offer) return False else: self.goal.requests[ self.domain.get_primary_key()] = _offer for _action in self.goal.missing_informs: # informed constraints by system are definitely consistent with # goal at this point if Constraint( _action.slot, _action.value ) not in informed_constraints_by_system: self.agenda.push( UserAct(act_type=UserActionType. Request, slot=_action.slot, value=None)) return True # no valid offer was given self._request_alt() return False else: # no offer was given # TODO add probability to choose number of alternations altered_constraints = self._alter_constraints( informed_constraints_by_system, 1) # reset goal push new actions on top of agenda self.goal.reset() self.goal.missing_informs = [ UserAct(act_type=UserActionType.Inform, slot=_constraint.slot, value=_constraint.value) for _constraint in self.goal.constraints ] for _constraint in altered_constraints: self.agenda.push(UserAct(act_type=UserActionType.Inform,\ slot=_constraint.slot, value=_constraint.value, score=1.0)) self.agenda.clean(self.goal) return False return False
def add_cell_unavailability_constraint(self, constr): c = Constraint("U", day=constr["day"], time_slot=constr["time_slot"], teacher=constr["room"], weight=1) self.C.append(c)