#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module contains the functions that help implement the functions in the other modules in this folder.
"""
import copy
[docs]
def create_oppos_sent(sent):
"""
Creates an opposite sentence by adding an AM-NEG argument with the negation "not."
in the describer. The "not" element is not added in the sent.parts because
it might be hard to pinpoint the location where it should be added.
"""
from ..language import components as lc
desc = sent.describers[0]
opposite_sent = lc.Sentence()
if desc.get_arg("AM-NEG") is None:
opposite_sent.describers = [lc.Describer(
args=copy.copy(desc.args))]
opposite_sent.describers[0].args["AM-NEG"] = lc.Arg("not", lc.Word("not"))
else:
opposite_sent.describers = [lc.Describer(
args=copy.copy(sent.describers[0].args))]
if "AM-NEG" in opposite_sent.describers[0].args:
del opposite_sent.describers[0].args["AM-NEG"]
return opposite_sent
[docs]
def check_prop(sent):
"""
Checks whether the sentence is in one of the following formats:
- <entity> 's <property_key> is (not) <property_val>
- <entity> is (not) <attribute>
If it fits the format, it extracts the variables in the brackets.
Otherwise, None is returned.
The <entity> has to be an instance of env_main.BaseEntity.
"""
from ..environment import entities as em
if len(sent.describers) == 0:
return None
desc = sent.describers[0]
rel = desc.get_arg('Rel', _type=0)
neg = desc.get_arg('AM-NEG')
if rel.infinitive == 'be' and rel.value[0].islower():
topic_arg = desc.get_arg('Arg-PPT', _type=0)
if topic_arg is not None:
topic = topic_arg.value
comment_arg = desc.get_arg('Arg-PRD', _type=0)
if comment_arg is not None:
if isinstance(topic, list) and len(topic) >= 2 and topic[1] == "'s":
ent = topic[0]
if ent is not None and isinstance(ent, em.BaseEntity):
pkey = topic[2:]
if len(pkey) == 1:
pkey = topic[2]
else:
pkey = tuple(pkey)
pval = comment_arg.value
return ent, pkey, pval, neg
elif isinstance(topic, em.BaseEntity):
attr = comment_arg.value
ent = topic
if isinstance(attr, list):
attr = tuple(attr)
if ent is not None:
return ent, None, attr, neg
return None
[docs]
def shared_elements(objects, entity, elements=None, only_seen=False, break_first=False):
if elements is None:
elements = []
if len(elements) == 0:
return None
elements = copy.copy(elements)
if only_seen:
elements = [elem for elem in elements if elem in entity.prop_seen or elem in entity.attr_seen]
else:
elements = [elem for elem in elements if elem in entity.properties or elem in entity.attributes]
shared_el = []
for other in objects:
all_elements = []
if other != entity:
for elem in elements:
if only_seen:
if elem in other.prop_seen and entity.prop_seen[elem] == other.prop_seen[elem]:
all_elements.append(elem)
elif elem in other.attr_seen:
all_elements.append(elem)
else:
if elem in other.properties and entity.properties[elem] == other.properties[elem]:
all_elements.append(elem)
elif elem in other.attributes:
all_elements.append(elem)
if len(elements) > 0 and set(elements).issubset(all_elements):
shared_el.extend(elements)
if break_first:
break
return shared_el
[docs]
def check_unique_entity(objects, entity, elements=None, only_seen=False):
""" Check whether the <entity> differs from the list of objects based on a list of elements.
An element can be a property like size or material or an attribute like 'locked' ('west', 'obstacle'), etc.
only_seen indicates whether to use the element values that are observed by the agent.
"""
return len(shared_elements(objects, entity, elements, only_seen, True)) == 0
[docs]
def find_similar_objs(objects, entity, elem, only_seen=False):
""" Finds objects that are similar to the <entity> based on a single element. An element can be
a property or an attribute.
only_seen indicates whether to look for objects whose element values are observed by the agent.
"""
similar_objs = []
for other in objects:
if only_seen:
if (elem in other.prop_seen and entity.prop_seen[elem] == other.prop_seen[elem]) or elem in other.attr_seen:
similar_objs.append(other)
else:
if (elem in other.properties and entity.properties[elem] == other.properties[elem]) or elem in other.attributes:
similar_objs.append(other)
return similar_objs
[docs]
def add_prop_seen(kb_state, ent, pkey, pval):
""" If one of the agents observes that the entity's property key equals the property value,
the (pkey, pval) pair is added to the entity.prop_seen field.
"""
old_val = ent.prop_seen.get(pkey)
ent.prop_seen[pkey] = pval
def undo(old_value=old_val, entity=ent, prop_key=pkey):
if old_value is None:
del entity.prop_seen[prop_key]
else:
entity.prop_seen[prop_key] = old_value
kb_state.undo_changes.append(undo)
[docs]
def remove_prop_seen(kb_state, ent, pkey):
""" If one of the agents observes that the entity's property key no longer equals its current property value,
the property key gets removed from the prop_seen field.
For example, if the color of the chair is no longer white and the agent observes this,
it gets removed.
"""
if pkey in ent.prop_seen:
old_val = ent.prop_seen[pkey]
del ent.prop_seen[pkey]
def undo(entity=ent, prop_key=pkey, old_value=old_val):
entity.prop_seen[prop_key] = old_value
kb_state.undo_changes.append(undo)
[docs]
def add_prop_seen_neg(kb_state, ent, pkey, pval):
""" If one of the agents observes that the entity's property does not equal the property value,
it gets added to the prop_seen_neg field.
For example, if Andy's nickname is no longer 'cuddle bunny', it will be written in this field.
"""
if pkey not in ent.prop_seen_neg:
ent.prop_seen_neg[pkey] = list()
def undo_set(entity=ent, prop_key=pkey):
del entity.prop_seen_neg[prop_key]
kb_state.undo_changes.append(undo_set)
if pval not in ent.prop_seen_neg[pkey]:
ent.prop_seen_neg[pkey].append(pval)
def undo_neg_val(entity=ent, prop_key=pkey, prop_val=pval):
entity.prop_seen_neg[prop_key].remove(prop_val)
kb_state.undo_changes.append(undo_neg_val)
[docs]
def remove_prop_seen_neg(kb_state, ent, pkey, pval):
""" If one of the agents observes that the entity's property key is equal to the property value,
the value gets removed from the prop_seen_neg field. For example, let's say that the agent observes
the change of Otto's nickname to 'tuxedo'. If 'tuxedo' exists in the prop_seen_neg['nickname'] field
from previous observations, it gets removed.
"""
if pkey in ent.prop_seen_neg:
if pval in ent.prop_seen_neg[pkey]:
ent.prop_seen_neg[pkey].remove(pval)
def undo(prop_val=pval, entity=ent):
entity.prop_seen_neg[pkey].append(prop_val)
kb_state.undo_changes.append(undo)
[docs]
def add_attr_seen(kb_state, ent, attr, neg):
""" It adds the attr to the :attr:`ent.attr_seen <pydialogue.environment.entities.Entity.attr_seen>` or
:attr:`ent.attr_seen_neg <pydialogue.environment.entities.Entity.attr_seen_neg>` depending on
whether the agent observes one of the following information:
- The <entity> is <attr>.
- The <entity> is not <attr>.
"""
if neg is None:
attr_seen = ent.attr_seen
else:
attr_seen = ent.attr_seen_neg
if attr not in attr_seen:
attr_seen[attr] = None
def undo(attribute=attr, ent_attr_seen=attr_seen):
del ent_attr_seen[attribute]
kb_state.undo_changes.append(undo)
[docs]
def remove_attr_seen(kb_state, ent, attr, neg):
""" It removes the attr from the :attr:`ent.attr_seen <pydialogue.environment.entities.Entity.attr_seen>` or
:attr:`ent.attr_seen_neg <pydialogue.environment.entities.Entity.attr_seen_neg>`.
This can happen if the agent observes an updated version of the entity's attribute.
For example, if the door is no longer open and the agent observes this,
the 'open' attribute is removed from the attr:`ent.attr_seen <pydialogue.environment.entities.Entity.attr_seen>`.
"""
if neg is None:
attr_seen = ent.attr_seen
else:
attr_seen = ent.attr_seen_neg
if attr in attr_seen:
del attr_seen[attr]
def undo(attribute=attr, ent_attr_seen=attr_seen):
ent_attr_seen[attribute] = None
kb_state.undo_changes.append(undo)