Select Git revision
ModuleErrorException.java
-
Jan Gruteser authoredJan Gruteser authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
UserModel.py 34.62 KiB
###############################################################################
# PyDial: Multi-domain Statistical Spoken Dialogue System Software
###############################################################################
#
# Copyright 2015 - 2019
# Cambridge University Engineering Department Dialogue Systems Group
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
###############################################################################
'''
UserModel.py - goal, agenda inventor for sim user
====================================================
Copyright CUED Dialogue Systems Group 2015 - 2017
.. seealso:: CUED Imports/Dependencies:
import :mod:`utils.DiaAct` |.|
import :mod:`utils.dact` |.|
import :mod:`usersimulator.UMHdcSim` |.|
import :mod:`ontology.Ontology` |.|
import :mod:`utils.Settings` |.|
import :mod:`utils.ContextLogger`
************************
'''
__author__ = "cued_dialogue_systems_group"
import copy
from utils import DiaAct, dact, Settings, ContextLogger
from usersimulator import UMHdcSim
from ontology import Ontology
logger = ContextLogger.getLogger('')
class UMAgenda(object):
'''Agenda of :class:`DiaAct` acts corresponding to a goal.
:param domain: domain tag (ie CamRestaurants)
:type domain: str
'''
def __init__(self, dstring):
self.dstring = dstring
self.agenda_items = [] # Stack of DiaAct
self.rules = None
# HDC probs on how simuser will conditionally behave: values are upper limits of ranges uniform sample falls in
self.NOT_MENTION = 1.0 # default - this value doesnt actually matter
self.LEAVE_INFORM = 0.4
self.CONFIRM = -1.0 # doesn't make sense actually to change inform(X=Y) to a confirm() act.
# ONE OTHER OPTION IS TO APPEND constraint into another inform act so it is inform(X=Y,A=B)
def init(self, goal):
"""
Initialises the agenda by creating DiaActs corresponding to the
constraints in the goal G. Uses the default order for the
dialogue acts on the agenda: an inform act is created for
each constraint. Finally a bye act is added at the bottom of the agenda.
:param goal:
.. Note::
No requests are added to the agenda.
"""
self.agenda_items = []
self.append_dact_to_front(DiaAct.DiaAct('inform(type="%s")' % goal.request_type))
for const in goal.constraints:
slot = const.slot
value = const.val
if slot == 'method':
continue
do_not_add = False
dia_act = DiaAct.DiaAct('inform()')
slots = Ontology.global_ontology.getSlotsToExpress(self.dstring, slot, value)
for s in slots:
val = goal.get_correct_const_value(s)
if val is not None:
dia_act.append(slot, val)
if len(slots) == 1 and self.contains(s, val):
do_not_add = True
elif len(slots) > 1:
# Slot value pair might already be in other act on agenda: remove that one
self.filter_acts_slot(s)
if not do_not_add:
self.append_dact_to_front(dia_act)
# CONDITIONALLY INIT THE AGENDA:
if len(goal.copied_constraints): # dont need self.CONDITIONAL_BEHAVIOUR - list will be empty as appropriate
self._conditionally_init_agenda(goal)
# Finally append a bye() act to complete agenda:
self.append_dact_to_front(DiaAct.DiaAct('bye()'))
return
def _conditionally_init_agenda(self, goal):
"""Use goal.copied_constraints -- to conditionally init Agenda.
Probabilistically remove/alter the agenda for this constraint then:
:param:
:returns:
"""
for dact in goal.copied_constraints:
#print dact.slot, dact.op, dact.val # TODO - delete debug prints
uniform_sample = Settings.random.uniform()
#print uniform_sample
if uniform_sample < self.CONFIRM:
# TODO - remove/change this -
# Decided this doesn't make sense - (so set self.CONFIRM=0) - info is in belief state, that is enough.
logger.info("changing inform() act to a confirm() act")
self.replace_acts_slot(dact.slot, replaceact="confirm")
elif uniform_sample < self.LEAVE_INFORM:
pass
else:
logger.info("removing the inform() act and not replacing with anything.")
self.filter_acts_slot(dact.slot)
return
def contains(self, slot, value, negate=False):
'''Check if slot, value pair is in an agenda dialogue act
:param slot:
:param value:
:param negate: None
:type negate: bool
:returns: (bool) slot, value is in an agenda dact?
'''
for dact in self.agenda_items:
if dact.contains(slot, value, negate):
return True
return False
def get_agenda_with_act(self, act):
'''agenda items with this act
:param act: dialogue act
:type act: str
:return: (list) agenda items
'''
items = []
for ait in self.agenda_items:
if ait.act == act:
items.append(ait)
return items
def get_agenda_with_act_slot(self, act, slot):
'''
:param act: dialogue act
:type act: str
:param slot: slot name
:type slot: str
:return: (list) of agenda items
'''
items = []
for ait in self.agenda_items:
if ait.act == act:
for item in ait.items:
if item.slot == slot:
items.append(ait)
break
return items
def replace_acts_slot(self, slot, replaceact="confirm"):
"""
"""
for ait in self.agenda_items:
if len(ait.items) == 1:
if ait.items[0].slot == slot:
print(ait.act)
print(ait.items)
ait.act = replaceact
print(ait)
input('going to change this to confirm')
def filter_acts_slot(self, slot):
'''
Any acts related to the given slot are removed from the agenda.
:param slot: slot name
:type slot: str
:return: None
'''
deleted = []
for ait in self.agenda_items:
if ait.act in ['inform', 'confirm', 'affirm'] and len(ait.items) > 0:
if len(ait.items) > 1:
pass
# logger.error('Assumes all agenda items have only one semantic items: {}'.format(ait))
for only_item in ait.items:
if only_item.slot == slot:
deleted.append(ait)
for ait in deleted:
self.agenda_items.remove(ait)
def filter_constraints(self, dap):
'''Filters out acts on the agenda that convey the constraints mentioned in the given dialogue act.
Calls :meth:`filter_acts_slot` to do so.
:param dap:
:returns: None
'''
if dap.act in ['inform', 'confirm'] or \
(dap.act in ['affirm', 'negate'] and len(dap.items) > 0):
for item in dap.items:
self.filter_acts_slot(item.slot)
def size(self):
'''Utility func to get size of agenda_items list
:returns: (int) length
'''
return len(self.agenda_items)
def clear(self):
'''
Erases all acts on the agenda (empties list)
:return: None
'''
self.agenda_items = []
def append_dact_to_front(self, dact):
'''Adds the given dialogue act to the front of the agenda
:param (instance): dact
:returns: None
'''
self.agenda_items = [dact] + self.agenda_items
def push(self, dact):
# if dact.act == 'null':
# logger.warning('null() in agenda')
# if dact.act == 'bye' and len(self.agenda_items) > 0:
# logger.warning('bye() in agenda')
self.agenda_items.append(dact)
def pop(self):
return self.agenda_items.pop()
def remove(self, dact):
self.agenda_items.remove(dact)
class UMGoal(object):
'''Defines a goal within a domain
:param patience: user patience
:type patience: int
'''
def __init__(self, patience, domainString):
self.constraints = []
self.copied_constraints = [] # goals copied over from other domains. Used to conditionally create agenda.
self.requests = {}
self.prev_slot_values = {}
self.patience = patience
self.request_type = Ontology.global_ontology.get_type(domainString)
self.system_has_informed_name_none = False
self.no_relaxed_constraints_after_name_none = False
def clear(self, patience, domainString):
self.constraints = []
self.requests = {}
self.prev_slot_values = {}
self.patience = patience
self.request_type = Ontology.global_ontology.get_type(domainString)
self.system_has_informed_name_none = False
self.no_relaxed_constraints_after_name_none = False
'''
Methods for constraints.
'''
def set_copied_constraints(self, all_conditional_constraints):
"""Creates a list of dacts, where the constraints have come from earlier domains in the dialog.
:param all_conditional_constraints: of all previous constraints (over all domains in dialog)
:type all_conditional_constraints: dict
:returns: None
"""
for dact in self.constraints:
slot,op,value = dact.slot,dact.op,dact.val
if slot in list(all_conditional_constraints.keys()):
if len(all_conditional_constraints[slot]):
if value in all_conditional_constraints[slot]:
self.copied_constraints.append(dact)
return
def add_const(self, slot, value, negate=False):
"""
"""
if not negate:
op = '='
else:
op = '!='
item = dact.DactItem(slot, op, value)
self.constraints.append(item)
def replace_const(self, slot, value, negate=False):
self.remove_slot_const(slot, negate)
self.add_const(slot, value, negate)
def contains_slot_const(self, slot):
for item in self.constraints:
# an error introduced here by dact.py __eq__ method:
#if item.slot == slot:
if str(item.slot) == slot:
return True
return False
def remove_slot_const(self, slot, negate=None):
copy_consts = copy.deepcopy(self.constraints)
if negate is not None:
if not negate:
op = '='
else:
op = '!='
for item in copy_consts:
if item.slot == slot:
if item.op == op:
self.constraints.remove(item)
else:
for item in copy_consts:
if item.slot == slot:
self.constraints.remove(item)
def get_correct_const_value(self, slot, negate=False):
'''
:return: (list of) value of the given slot in user goal constraint.
'''
values = []
for item in self.constraints:
if item.slot == slot:
if item.op == '!=' and negate or item.op == '=' and not negate:
values.append(item.val)
if len(values) == 1:
return values[0]
elif len(values) == 0:
return None
logger.error('Multiple values are found for %s in constraint: %s' % (slot, str(values)))
return values
def get_correct_const_value_list(self, slot, negate=False):
'''
:return: (list of) value of the given slot in user goal constraint.
'''
values = []
for item in self.constraints:
if item.slot == slot:
if (item.op == '!=' and negate) or (item.op == '=' and not negate):
values.append(item.val)
return values
def add_prev_used(self, slot, value):
'''
Adds the given slot-value pair to the record of previously used slot-value pairs.
'''
if slot not in self.prev_slot_values:
self.prev_slot_values[slot] = set()
self.prev_slot_values[slot].add(value)
def add_name_constraint(self, value, negate=False):
if value in [None, 'none']:
return
wrong_venues = self.get_correct_const_value_list('name', negate=True)
correct_venue = self.get_correct_const_value('name', negate=False)
if not negate:
# Adding name=value but there is name!=value.
if value in wrong_venues:
logger.error('Failed to add name=%s: already got constraint name!=%s.' %
(value, value))
return
# Can have only one name= constraint.
if correct_venue is not None:
#logger.debug('Failed to add name=%s: already got constraint name=%s.' %
# (value, correct_venue))
self.replace_const('name', value) # ic340: added to override previously informed venues, to avoid
# simuser getting obsessed with a wrong venue
return
# Adding name=value, then remove all name!=other.
self.replace_const('name', value)
return
# if not negate and not self.is_suitable_venue(value):
# logger.debug('Failed to add name=%s: %s is not a suitable venue for goals.' % (value, value))
# return
if negate:
# Adding name!=value but there is name=value.
if correct_venue == value:
logger.error('Failed to add name!=%s: already got constraint name=%s.' % (value, value))
return
# Adding name!=value, but there is name=other. No need to add.
if correct_venue is not None:
return
self.add_const('name', value, negate=True)
return
# def is_correct(self, item):
# '''
# Check if the given items are correct in goal constraints.
# :param item: set[(slot, op, value), ...]
# :return:
# '''
# if type(item) is not set:
# item = set([item])
# for it in item:
# for const in self.constraints:
# if const.match(it):
def is_satisfy_all_consts(self, item):
'''
Check if all the given items set[(slot, op, value),..] satisfies all goal constraints (conjunction of constraints).
'''
if type(item) is not set:
item = set([item])
for it in item:
for const in self.constraints:
if not const.match(it):
return False
return True
def is_completed(self):
# If the user has not specified any constraints, return True
if not self.constraints:
return True
if (self.system_has_informed_name_none and not self.no_relaxed_constraints_after_name_none) or\
(self.is_venue_recommended() and self.are_all_requests_filled()):
return True
return False
'''
Methods for requests.
'''
def reset_requests(self):
for info in self.requests:
self.requests[info] = None
def fill_requests(self, dact_items):
for item in dact_items:
if item.op != '!=':
self.requests[item.slot] = item.val
def are_all_requests_filled(self):
'''
:return: True if all request slots have a non-empty value.
'''
return None not in list(self.requests.values())
def is_venue_recommended(self):
'''
Returns True if the request slot 'name' is not empty.
:return:
'''
if 'name' in self.requests and self.requests['name'] is not None:
return True
return False
def get_unsatisfied_requests(self):
results = []
for info in self.requests:
if self.requests[info] is None:
results.append(info)
return results
def __str__(self):
result = 'constraints: ' + str(self.constraints) + '\n'
result += 'requests: ' + str(self.requests) + '\n'
if self.patience is not None:
result += 'patience: ' + str(self.patience) + '\n'
return result
class GoalGenerator(object):
'''
Master class for defining a goal generator to generate domain specific goals for the simulated user.
This class also defines the interface for new goal generators.
To implement domain-specific behaviour, derive from this class and override init_goals.
'''
def __init__(self, dstring):
'''
The init method of the goal generator reading the config and setting the default parameter values.
:param dstring: domain name tag
:type dstring: str
'''
self.dstring = dstring
configlist = []
self.CONDITIONAL_BEHAVIOUR = False
if Settings.config.has_option("conditional","conditionalsimuser"):
self.CONDITIONAL_BEHAVIOUR = Settings.config.getboolean("conditional","conditionalsimuser")
self.MAX_VENUES_PER_GOAL = 4
if Settings.config.has_option('goalgenerator','maxvenuespergoal'):
configlist.append('maxvenuespergoal')
self.MAX_VENUES_PER_GOAL = Settings.config.getint('goalgenerator','maxvenuespergoal')
self.MIN_VENUES_PER_GOAL = 1
self.MAX_CONSTRAINTS = 3
if Settings.config.has_option('goalgenerator','maxconstraints'):
configlist.append('maxconstraints')
self.MAX_CONSTRAINTS = Settings.config.getint('goalgenerator','maxconstraints')
self.MAX_REQUESTS = 3
if Settings.config.has_option('goalgenerator','maxrequests'):
configlist.append('maxrequests')
self.MAX_REQUESTS = Settings.config.getint('goalgenerator','maxrequests')
self.MIN_REQUESTS = 1
if Settings.config.has_option('goalgenerator','minrequests'):
configlist.append('minrequests')
self.MIN_REQUESTS = Settings.config.getint('goalgenerator','minrequests')
assert(self.MIN_REQUESTS > 0)
if Settings.config.has_option('goalgenerator','minvenuespergoal'):
self.MIN_VENUES_PER_GOAL = int(Settings.config.get('goalgenerator','minvenuespergoal'))
# self.PERCENTAGE_OF_ZERO_SOLUTION_TASKS = 50
# if Settings.config.has_option('GOALGENERATOR','PERCZEROSOLUTION'):
# self.PERCENTAGE_OF_ZERO_SOLUTION_TASKS = int(Settings.config.get('GOALGENERATOR','PERCZEROSOLUTION'))
# self.NO_REQUESTS_WITH_VALUE_NONE = False
# if Settings.config.has_option('GOALGENERATOR','NOREQUESTSWITHVALUENONE'):
# self.NO_REQUESTS_WITH_VALUE_NONE
if self.MIN_VENUES_PER_GOAL > self.MAX_VENUES_PER_GOAL:
logger.error('Invalid config: MIN_VENUES_PER_GOAL > MAX_VENUES_PER_GOAL')
self.conditional_constraints = []
self.conditional_constraints_slots = []
def init_goal(self, otherDomainsConstraints, um_patience):
'''
Initialises the goal g with random constraints and requests
:param otherDomainsConstraints: of constraints from other domains in this dialog which have already had goals generated.
:type otherDomainsConstraints: list
:param um_patience: the patiance value for this goal
:type um_patience: int
:returns: (instance) of :class:`UMGoal`
'''
# clean/parse the domainConstraints list - contains other domains already generated goals:
self._set_other_domains_constraints(otherDomainsConstraints)
# Set initial goal status vars
goal = UMGoal(um_patience, domainString=self.dstring)
logger.debug(str(goal))
num_attempts_to_resample = 2000
while True:
num_attempts_to_resample -= 1
# Randomly sample a goal (ie constraints):
self._init_consts_requests(goal, um_patience)
# Check that there are venues that satisfy the constraints:
venues = Ontology.global_ontology.entity_by_features(self.dstring, constraints=goal.constraints)
#logger.info('num_venues: %d' % len(venues))
if self.MIN_VENUES_PER_GOAL < len(venues) < self.MAX_VENUES_PER_GOAL:
break
if num_attempts_to_resample == 0:
logger.warning('Maximum number of goal resampling attempts reached.')
if self.CONDITIONAL_BEHAVIOUR:
# now check self.generator.conditional_constraints list against self.goal -assume any values that are the same
# are because they are conditionally copied over from earlier domains goal. - set self.goal.copied_constraints
goal.set_copied_constraints(all_conditional_constraints=self.conditional_constraints)
# logger.warning('SetSuitableVenues is deprecated.')
return goal
def _set_other_domains_constraints(self, otherDomainsConstraints):
"""Simplest approach for now: just look for slots with same name
"""
# Get a list of slots that are valid for this task.
valid_const_slots = Ontology.global_ontology.getValidSlotsForTask(self.dstring)
self.conditional_constraints = {slot: [] for slot in valid_const_slots}
if not self.CONDITIONAL_BEHAVIOUR:
self.conditional_constraint_slots = []
return
for const in otherDomainsConstraints:
if const.slot in valid_const_slots and const.val != "dontcare": #TODO think dontcare should be dealt with diff
# issue is that first domain may be dontcare - but 2nd should be generated conditioned on first.
if const.op == "!=":
continue
#TODO delete: if const.val in Ontology.global_ontology.ontology['informable'][const.slot]: #make sure value is valid for slot
if Ontology.global_ontology.is_value_in_slot(self.dstring, value=const.val, slot=const.slot):
self.conditional_constraints[const.slot] += [const.val]
self.conditional_constraint_slots = [s for s,v in self.conditional_constraints.items() if len(v)]
return
def _init_consts_requests(self, goal, um_patience):
'''
Randomly initialises constraints and requests of the given goal.
'''
goal.clear(um_patience, domainString=self.dstring)
# Randomly pick a task: bar, hotel, or restaurant (in case of TownInfo)
goal.request_type = Ontology.global_ontology.getRandomValueForSlot(self.dstring, slot='type')
# Get a list of slots that are valid for this task.
valid_const_slots = Ontology.global_ontology.getValidSlotsForTask(self.dstring)
# First randomly sample some slots from those that are valid:
sampling_probs = Ontology.global_ontology.get_sample_prob(self.dstring,
candidate=valid_const_slots,
conditional_values=self.conditional_constraint_slots)
random_slots = list(Settings.random.choice(valid_const_slots,
size=min(self.MAX_CONSTRAINTS, len(valid_const_slots)),
replace=False,
p=sampling_probs))
# Now randomly fill in some constraints for the sampled slots:
for slot in random_slots:
goal.add_const(slot, Ontology.global_ontology.getRandomValueForSlot(self.dstring, slot=slot,
nodontcare=False,
conditional_values=self.conditional_constraints[slot]))
# Add requests. Assume that the user always wants to know the name of the place
goal.requests['name'] = None
if self.MIN_REQUESTS == self.MAX_REQUESTS:
n = self.MIN_REQUESTS -1 # since 'name' is already included
else:
n = Settings.random.randint(low=self.MIN_REQUESTS-1,high=self.MAX_REQUESTS)
valid_req_slots = Ontology.global_ontology.getValidRequestSlotsForTask(self.dstring)
if n > 0 and len(valid_req_slots) >= n: # ie more requests than just 'name'
choosen = Settings.random.choice(valid_req_slots, n,replace=False)
for reqslot in choosen:
goal.requests[reqslot] = None
class UM(object):
'''Simulated user
:param None:
'''
def __init__(self, domainString):
self.max_patience = 5
self.sample_patience = None
if Settings.config.has_option('goalgenerator', 'patience'): # only here for backwards compatibility; should actually be in um
self.max_patience = Settings.config.get('goalgenerator', 'patience')
if Settings.config.has_option('usermodel', 'patience'):
self.max_patience = Settings.config.get('usermodel', 'patience')
if isinstance(self.max_patience,str) and len(self.max_patience.split(',')) > 1:
self.sample_patience = [int(x.strip()) for x in self.max_patience.split(',')]
if len(self.sample_patience) != 2 or self.sample_patience[0] > self.sample_patience[1]:
logger.error('Patience should be either a single int or a range between 2 ints.')
else:
self.max_patience = int(self.max_patience)
self.patience_old_style = False
if Settings.config.has_option('usermodel', 'oldstylepatience'):
self.patience_old_style = Settings.config.getboolean('usermodel', 'oldstylepatience')
self.old_style_parameter_sampling = True
if Settings.config.has_option('usermodel', 'oldstylesampling'):
self.old_style_parameter_sampling = Settings.config.getboolean('usermodel', 'oldstylesampling')
self.sampleParameters = False
if Settings.config.has_option('usermodel', 'sampledialogueprobs'):
self.sampleParameters = Settings.config.getboolean('usermodel', 'sampledialogueprobs')
self.generator = self._load_domain_goal_generator(domainString)
self.goal = None
self.prev_goal = None
self.hdcSim = self._load_domain_simulator(domainString)
self.lastUserAct = None
self.lastSysAct = None
def init(self, otherDomainsConstraints):
'''
Initialises the simulated user.
1. Initialises the goal G using the goal generator.
2. Populates the agenda A using the goal G.
Resets all UM status to their defaults.
:param otherDomainsConstraints: of domain goals/constraints (slot=val) from other domains in dialog for which goal has already been generated.
:type otherDomainsConstraints: list
:returns None:
'''
if self.sampleParameters:
self._sampleParameters()
if self.sample_patience:
self.max_patience = Settings.random.randint(self.sample_patience[0], self.sample_patience[1])
self.goal = self.generator.init_goal(otherDomainsConstraints, self.max_patience)
logger.debug(str(self.goal))
self.lastUserAct = None
self.lastSysAct = None
self.hdcSim.init(self.goal, self.max_patience) #uses infor in self.goal to do conditional generation of agenda as well.
def receive(self, sys_act):
'''
This method is called to transmit the machine dialogue act to the user.
It updates the goal and the agenda.
:param sys_act: System action.
:return:
'''
# Update previous goal.
self.prev_goal = copy.deepcopy(self.goal)
# Update the user patience level.
if self.lastUserAct is not None and self.lastUserAct.act == 'repeat' and\
self.lastSysAct is not None and self.lastSysAct.act == 'repeat' and\
sys_act.act == 'repeat':
# Endless cycle of repeating repeats: reduce patience to zero.
logger.info("Endless cycle of repeating repeats. Setting patience to zero.")
self.goal.patience = 0
elif sys_act.act == 'badact' or sys_act.act == 'null' or\
(self.lastSysAct is not None and self.lastUserAct is not None and self.lastUserAct.act != 'repeat' and self.lastSysAct == sys_act):
# Same action as last turn. Patience decreased.
self.goal.patience -= 1
elif self.patience_old_style:
# not same action as last time so patience is restored
self.goal.patience = self.max_patience
if self.goal.patience < 1:
logger.debug(str(self.goal))
logger.debug('All patience gone. Clearing agenda.')
self.hdcSim.agenda.clear()
# Pushing bye act onto agenda.
self.hdcSim.agenda.push(DiaAct.DiaAct('bye()'))
return
# Update last system action.
self.lastSysAct = sys_act
# Process the sys_act
self.hdcSim.receive(sys_act, self.goal)
# logger.warning('should update goal information')
def respond(self):
'''
This method is called after receive() to get the user dialogue act response.
The method first increments the turn counter, then pops n items off the agenda to form
the response dialogue act. The agenda and goal are updated accordingly.
:param None:
:returns: (instance) of :class:`DiaAct`
'''
user_output = self.hdcSim.respond(self.goal)
if user_output.act == 'request' and len(user_output.items) > 0:
# If there is a goal constraint on near, convert act type to confirm
# TODO- do we need to add more domain dependent type rules here for other domains beyond CamRestaurants?
# this whole section mainly seems to stem from having "area" and "near" in ontology...
if user_output.contains_slot('near') and self.goal.contains_slot_const('near'):
for const in self.goal.constraints:
if const.slot == 'near':
near_const = const.val
near_op = const.op
break
# original: bug. constraints is a list --- near_const = self.goal.constraints['near']
if near_const != 'dontcare':
if near_op == "=": # should be true for 'dontcare' value
#TODO - delete-WRONG-user_output.dact['act'] = 'confirm'
#TODO-delete-user_output.dact['slots'][0].val = near_const
user_output.act = 'confirm'
user_output.items[0].val = near_const
self.lastUserAct = user_output
#self.goal.update_au(user_output)
return user_output
def _sampleParameters(self):
if not self.old_style_parameter_sampling:
self.max_patience = Settings.random.randint(2,10)
def _load_domain_goal_generator(self, domainString):
'''
Loads and instantiates the respective goal generator object as configured in config file. The new object is returned.
Default is GoalGenerator.
.. Note:
To dynamically load a class, the __init__() must take two arguments: domainString (str), conditional_behaviour (bool)
:param domainString: the domain the goal generator will be loaded for.
:type domainString: str
:returns: goal generator object
'''
generatorClass = None
if Settings.config.has_option('usermodel_' + domainString, 'goalgenerator'):
generatorClass = Settings.config.get('usermodel_' + domainString, 'goalgenerator')
if generatorClass is None:
return GoalGenerator(domainString)
else:
try:
# try to view the config string as a complete module path to the class to be instantiated
components = generatorClass.split('.')
packageString = '.'.join(components[:-1])
classString = components[-1]
mod = __import__(packageString, fromlist=[classString])
klass = getattr(mod, classString)
return klass(domainString)
except ImportError:
logger.error('Unknown domain ontology class "{}" for domain "{}"'.format(generatorClass, domainString))
def _load_domain_simulator(self, domainString):
'''
Loads and instantiates the respective simulator object as configured in config file. The new object is returned.
Default is UMHdcSim.
.. Note:
To dynamically load a class, the __init__() must take one argument: domainString (str)
:param domainString: the domain the simulator will be loaded for.
:type domainString: str
:returns: simulator object
'''
simulatorClass = None
if Settings.config.has_option('usermodel_' + domainString, 'usersimulator'):
simulatorClass = Settings.config.get('usermodel_' + domainString, 'usersimulator')
if simulatorClass is None:
return UMHdcSim.UMHdcSim(domainString)
else:
try:
# try to view the config string as a complete module path to the class to be instantiated
components = simulatorClass.split('.')
packageString = '.'.join(components[:-1])
classString = components[-1]
mod = __import__(packageString, fromlist=[classString])
klass = getattr(mod, classString)
return klass(domainString)
except ImportError:
logger.error('Unknown domain ontology class "{}" for domain "{}"'.format(simulatorClass, domainString))
#END OF FILE