#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module contains the functions that update the KnowledgeBase.
"""
from ..language import components as lc
from ..language import describers as tdescribers
from ..language import sentences as tsentences
from . import kn_helpers
from . import kn_parsers
from ..environment import entities as em
[docs]
def basic_updater(kb_state, sent):
"""
Add the sentence to the list of factual sentences (in the sent_db) and
remove the opposite sentence if it exists. The sentence has to come from a trusted source.
Parameters
----------
kb_state : KnowledgeBase
The knowledge base to be potentially updated.
sent : Sentence
The sentence that is considered to be added.
Returns
-------
None.
"""
if sent not in kb_state.sent_db:
kb_state.sent_db.add(sent)
def undo_add(know_base=kb_state, sentence=sent):
know_base.sent_db.discard(sentence)
kb_state.undo_changes.append(undo_add)
opposite_sent = kn_helpers.create_oppos_sent(sent)
if opposite_sent in kb_state.sent_db:
kb_state.sent_db.remove(opposite_sent)
def undo_remove(know_base=kb_state, sentence=opposite_sent):
know_base.sent_db.add(sentence)
kb_state.undo_changes.append(undo_remove)
[docs]
def property_updater(kb_state, sent):
"""
Extract the entity, property key, property value, and the negation from the following sentence:
..
<entity> 's' <property_key> is <property_value>
or
<entity> is <attribute>
and calls the :func:`property_update_alt() <pydialogue.state.kn_updaters.property_update_alt>` function.
In case the updater can extract additional information from the sentence,
it updates the knowledge base with further information, too.
For example, if a door is locked, it means that the door is not open as well.
Parameters
----------
kb_state : KnowledgeBase
The knowledge base to be updated.
sent : Sentence
The sentence in the aforementioned forms.
Returns
-------
None.
"""
mem = kn_helpers.check_prop(sent)
if mem is not None:
ent, pkey, pval, pneg = mem
property_update_alt(kb_state, ent, pkey, pval, pneg)
ent = getattr(kb_state.world, ent.get_property("var_name"), None)
if ent is not None:
if pkey is None:
if pval == 'locked' and pneg is None and 'locked' in ent.attributes:
property_update_alt(kb_state, ent, None, "open", "not")
else:
if pkey == 'type' and pneg is None and ent.properties['type'] == pval:
if 'place' in ent.attributes:
if 'location' in ent.properties:
property_update_alt(kb_state, ent, "location", ent.properties['location'], None)
return None
[docs]
def property_update_alt(kb_state, ent, pkey, pval, pneg):
"""
Updates one of the following dictionaries:
..
:attr:`ent.prop_seen <pydialogue.environment.entities.Entity.prop_seen>`,
:attr:`ent.prop_seen_neg <pydialogue.environment.entities.Entity.prop_seen_neg>`,
:attr:`ent.attr_seen <pydialogue.environment.entities.Entity.attr_seen>`,
:attr:`ent.attr_seen_neg <pydialogue.environment.entities.Entity.attr_seen_neg>`
where each of the entities is taken from kb_state.world in case the entity is a copy.
entity.prop_seen and entity.attr_seen are updated if the property
or the attribute is observed by one of the agents. The _neg version is updated if it's seen
that the entity's pkey is not pval or the entity does not contain the attribute.
Parameters
----------
kb_state : KnowledgeBase
The knowledge base is used to fetch the world.
ent : Entity
The entity to be updated.
pkey : str or None
The property key if a property is updated. For an attribute, this value is None.
pval : any
The property value if a property is updated. Otherwise, it represents an attribute.
pneg : str or None
If the pneg is present, the _neg version of the dictionaries is updated.
Returns
-------
None.
"""
if isinstance(ent, em.Entity):
world_ent = getattr(kb_state.world, ent.get_property("var_name"), None)
else:
world_ent = None
if world_ent is None:
return None
if pkey is not None:
if not ((isinstance(pkey, str) or isinstance(pkey, tuple)) and pkey in world_ent.properties
and ((pneg is None and world_ent.properties[pkey] == pval)
or (pneg is not None and world_ent.properties[pkey] != pval))):
return None
if pneg is None:
kn_helpers.add_prop_seen(kb_state, world_ent, pkey, pval)
kn_helpers.remove_prop_seen_neg(kb_state, world_ent, pkey, pval)
elif pneg in ['no', 'not']:
kn_helpers.add_prop_seen_neg(kb_state, world_ent, pkey, pval)
if pkey in world_ent.prop_seen and world_ent.prop_seen[pkey] == pval:
kn_helpers.remove_prop_seen(kb_state, world_ent, pkey)
else:
if not ((isinstance(pval, str) or isinstance(pval, tuple))
and ((pneg is None and pval in ent.attributes) or (pneg is not None and pval not in ent.attributes))):
return None
if pneg is None:
kn_helpers.add_attr_seen(kb_state, world_ent, pval, None)
kn_helpers.remove_attr_seen(kb_state, world_ent, pval, "not")
elif pneg in ['no', 'not']:
kn_helpers.add_attr_seen(kb_state, world_ent, pval, pneg)
kn_helpers.remove_attr_seen(kb_state, world_ent, pval, None)
return None
[docs]
def multiple_prop_updater(kb_state, sent):
mem = kn_helpers.check_prop(sent)
if mem is not None:
ent, pkey, pval, pneg = mem
if isinstance(pval, (set)):
for val in pval:
property_update_alt(kb_state, ent, pkey, val, pneg)
[docs]
def elem_exists_updater(kb_state, sent):
"""
Checks whether the sentence is in the form described in kn_parsers.elem_exists_parse
and if so, extracts the arguments and calls the update_elem_exists_alt function.
"""
parsed = kn_parsers.elem_exists_parse(sent)
if parsed is not None:
ent, elem, pneg = parsed
elem_exists_alt_updater(kb_state, ent, elem, pneg)
[docs]
def elem_exists_alt_updater(kb_state, ent, elem, pneg):
"""
Updates one of the following dictionaries:
:attr:`ent.elem_exists <pydialogue.environment.entities.Entity.elem_exists>` or
:attr:`ent.elem_not_exists <pydialogue.environment.entities.Entity.elem_not_exists>`
This happens when the agent observes that the entity has/hasn't a property or an attribute (element).
The information is checked for its truthfulness before the update is made.
Parameters
----------
kb_state : KnowledgeBase
The knowledge base is used to fetch the world.
ent : Entity
The entity to be updated.
elem : str
The property or attribute that the entity has/hasn't. An example can be
'color' (property) or 'locked' (attribute)
pneg : str
If pneg is not None, then the entity.elem_not_exists is updated
Returns
-------
None.
"""
world_ent = getattr(kb_state.world, ent.get_property("var_name"), None)
if world_ent is None:
return None
cond = False
if elem in world_ent.properties or elem in world_ent.attributes:
cond = True
cond = cond if pneg is None else not cond
if cond:
if pneg is None:
if elem not in world_ent.elem_exists:
world_ent.elem_exists.add(elem)
def undo_exists(entity=world_ent, element=elem):
entity.elem_exists.discard(element)
kb_state.undo_changes.append(undo_exists)
if elem in world_ent.elem_not_exists:
world_ent.elem_not_exists.discard(elem)
def undo_not_exists(entity=world_ent, element=elem):
entity.elem_not_exists.add(element)
kb_state.undo_changes.append(undo_not_exists)
elif pneg in ['no', 'not']:
if elem not in world_ent.elem_not_exists:
world_ent.elem_not_exists.add(elem)
def undo_not_exists(entity=world_ent, element=elem):
entity.elem_not_exists.discard(element)
kb_state.undo_changes.append(undo_not_exists)
if elem in world_ent.elem_exists:
world_ent.elem_exists.discard(elem)
def undo_elem_exists(entity=world_ent, element=elem):
entity.elem_exists.add(element)
kb_state.undo_changes.append(undo_elem_exists)
return None
[docs]
def have_updater(kb_state, sent):
"""
Marks the location of the entities that belong to a possessor as observed.
The sentence is in the form described in kn_parsers.have_parse.
It also marks the location of the entities as exclusive,
meaning they do not belong to any other entity in the world.
If the possessor does not have entities, the location of all objects in the world
is marked that it is not in the possessor's possession.
Parameters
----------
kb_state : KnowledgeBase
The knowledge base to be updated.
sent : Sentence
The sentence form is described in kn_parsers.have_parse
Returns
-------
None.
"""
mem = kn_parsers.have_parse(sent)
if mem is None:
return
owner, possession, neg, loc = mem
if loc is None:
loc = ['in', owner]
if possession == "items" and neg is not None and neg in ['no', 'not']:
all_objects = kb_state.world.obj_list
for obj in all_objects:
property_update_alt(kb_state, obj, "location", loc, "not")
else:
if isinstance(possession, set):
possession = list(possession)
elif not isinstance(possession, list):
possession = [possession]
for obj in possession:
property_update_alt(kb_state, obj, "location", loc, neg)
if neg is None:
all_objects = kb_state.world.obj_list
for obj in all_objects:
if obj not in possession:
property_update_alt(kb_state, obj, "location", loc, "not")
[docs]
def go_updater(kb_state, sent):
"""
Marks the player's location as observed if the following sentence appears:
..
<player> goes <direction> <start_point>
Also, it marks the <start_point>'s direction as observed.
For the sentence: The bear goes south from the barn to the main path,
the last seen location of the bear is updated (bear.prop_seen['location']=main_path),
and also it updates that the barn
has direction south to the main path (barn.prop_seen['south']=main_path)
Parameters
----------
kb_state : KnowledgeBase
The knowledge base.
sent : Sentence
The sentence in the form above.
Returns
-------
None.
"""
if len(sent.describers) > 0 and len(sent.describers) == 3:
describer = sent.describers[0]
rel = describer.get_arg("Rel")
if lc.verb_inf(rel.lower()) == "go" and lc.verb_tense(rel) in lc.PRESENT_TENSES:
player = describer.get_arg("Arg-PPT")
direction = describer.get_arg("AM-DIR")
start_point = describer.get_arg("Arg-DIR")
mapped_desc = tdescribers.go((player, None), (None, None), (None, None), (rel, None),
(direction, None), (start_point, None))
if mapped_desc == describer:
if not (isinstance(start_point, list) and start_point[0] == "from"
and isinstance(start_point[1], em.Entity)):
return None
player_look = sent.describers[1].get_arg("Arg-PAG")
thing_looked = sent.describers[1].get_arg("Arg-PPT")
rel_look = sent.describers[1].get_arg("Rel")
if lc.verb_inf(rel_look.lower()) == "look" and lc.verb_tense(rel_look) in lc.PRESENT_TENSES:
look_desc = tdescribers.look((player_look, None), (None, None), (None, None),
(rel_look, None), (thing_looked, None))
if (look_desc == sent.describers[1] and player == player_look and isinstance(direction, str)
and thing_looked[1] == start_point[-1].properties[direction]):
if direction is not None and player is not None:
property_update_alt(kb_state, start_point[-1], direction,
start_point[1].properties[direction], None)
old_loc = start_point[1]
property_update_alt(kb_state, old_loc, "location", old_loc.properties['location'], None)
property_update_alt(kb_state, player, "location", thing_looked, None)
looks_and_sees = lc.Sentence()
looks_and_sees.describers.extend(sent.describers[1:])
look_updater(kb_state, looks_and_sees)
return None
[docs]
def get_updater(kb_state, sent):
"""
Marks the location of the taken item as observed. The location of the player getting the item is marked as well.
The sentence should be in the following form:
..
<player> gets <entity> (from_location)
For example, Andy gets the spoon from the table.
Parameters
----------
kb_state : KnowledgeBase
The knowledge base.
sent : Sentence
The sentence in the form above.
Returns
-------
None.
"""
if len(sent.describers) > 0:
describer = sent.describers[0]
rel = describer.get_arg("Rel")
if lc.verb_inf(rel.lower()) != "get" or lc.verb_tense(rel) not in lc.PRESENT_TENSES:
return
player = describer.get_arg("Arg-PAG")
thing_gotten = describer.get_arg("Arg-PPT")
giver = describer.get_arg("Arg-DIR")
mapped_sent = lc.Sentence()
mapped_desc = tdescribers.get((player, None), (None, None), (None, None), (rel, None),
(thing_gotten, None), (giver, None))
mapped_sent.describers.append(mapped_desc)
if mapped_sent == sent:
if all(map(isinstance, [player, thing_gotten], [em.Entity, em.Entity])):
property_update_alt(kb_state, thing_gotten, "location", ["in", player], None)
[docs]
def drop_updater(kb_state, sent):
"""
Marks the location of the dropped item as observed. The location of the player dropping the item is marked as well.
The sentence should be in the following form:
..
<player> drops <entity> <at_location>
"""
if len(sent.describers) > 0:
describer = sent.describers[0]
rel = describer.get_arg("Rel")
if lc.verb_inf(rel.lower()) != "drop" or lc.verb_tense(rel) not in lc.PRESENT_TENSES:
return
player = describer.get_arg("Arg-PAG")
thing_dropped = describer.get_arg("Arg-PPT")
location = describer.get_arg("Arg-GOL")
mapped_sent = lc.Sentence()
mapped_desc = tdescribers.drop((player, None), (None, None), (None, None), (rel, None),
(thing_dropped, None), (location, None))
mapped_sent.describers.append(mapped_desc)
if mapped_sent == sent:
if (all(map(isinstance, [player, thing_dropped, location], [em.Entity, em.Entity, list]))
and len(location) == 2 and isinstance(location[-1], em.Entity)
and location[-2] in kb_state.world.location_positions):
for ent in [player, thing_dropped]:
property_update_alt(kb_state, ent, "location", location, None)
[docs]
def see_updater(kb_state, sent):
""" Marks the location of the player and the items that the player sees as observed.
The sentence should be in the following form:
..
<player> sees <entity_1>, ... , <entity_n> <at_location>
"""
if len(sent.describers) > 0:
describer = sent.describers[0]
rel = describer.get_arg("Rel")
if lc.verb_inf(rel.lower()) != "see" or lc.verb_tense(rel) not in lc.PRESENT_TENSES:
return
player = describer.get_arg("Arg-PAG")
things_seen = describer.get_arg("Arg-PPT")
location = describer.get_arg("AM-LOC")
mapped_sent = lc.Sentence()
mapped_desc = tdescribers.see((player, None), (None, None), (rel, None),
(things_seen, None), (location, None))
mapped_sent.describers.append(mapped_desc)
if mapped_sent == sent:
if isinstance(things_seen, set):
things_seen = list(things_seen)
elif not isinstance(things_seen, (set, tuple, list)):
things_seen = [things_seen]
if (isinstance(player, em.Entity)
and isinstance(location, list)
and len(location) == 2
and isinstance(location[-1], em.Entity)
and location[-2] in kb_state.world.location_positions):
for ent in things_seen + [player]:
property_update_alt(kb_state, ent, "location", location, None)
[docs]
def look_updater(kb_state, sent):
"""
Marks the location of the player and the item that it looks as observed.
The sentence should be in the following form:
..
<player> looks (preposition) <at_entity>
"""
if len(sent.describers) > 0:
describer = sent.describers[0]
rel = describer.get_arg("Rel")
if lc.verb_inf(rel.lower()) != "look" or lc.verb_tense(rel) not in lc.PRESENT_TENSES:
return
player = describer.get_arg("Arg-PAG")
thing_looked = describer.get_arg("Arg-PPT")
mapped_desc = tdescribers.look((player, None), (None, None), (None, None),
(rel, None), (thing_looked, None))
if describer == mapped_desc:
if (isinstance(thing_looked, list)
and thing_looked[0] in kb_state.world.location_positions
and all(map(isinstance, [thing_looked[1], player], [em.Entity, em.Entity]))):
for ent in [player, thing_looked[1]]:
property_update_alt(kb_state, ent, "location", thing_looked, None)
if len(sent.describers) == 2:
added = False
if sent.describers[1].get_arg("AM-LOC") is None:
sent.describers[1].args["AM-LOC"] = lc.Arg(thing_looked)
added = True
see_sent = lc.Sentence()
see_sent.describers.append(sent.describers[1])
see_updater(kb_state, see_sent)
if added:
del sent.describers[1].args["AM-LOC"]
[docs]
def val_is_key_updater(kb_state, sent):
""" Adds one of the following sentences in the database:
..
<property_value> is (not) <property_key>
or
<str> is (not) direction.
For example, "medium is not a color".
The sentence is first checked for its truthfulness.
"""
if len(sent.describers) > 0:
describer = sent.describers[0]
arg_ppt = describer.get_arg("Arg-PPT")
arg_prd = describer.get_arg("Arg-PRD")
am_neg = describer.get_arg("AM-NEG")
if (arg_ppt is None or arg_prd is None or
sent != tsentences.be((arg_ppt, None), ("is", None), (am_neg, None), (arg_prd, None))):
return None
if isinstance(arg_prd, list):
arg_prd = tuple(arg_prd)
additional_prds = [('player', prop) for prop in ['name', 'nickname', 'surname']]
if arg_prd not in kb_state.world.all_properties+['direction']+additional_prds:
return None
kn_checked = kb_state.world.check_val_is_key(arg_prd, arg_ppt)
if kn_checked is True:
if am_neg != "not":
basic_updater(kb_state, sent)
elif kn_checked is False:
if am_neg == "not":
basic_updater(kb_state, sent)
[docs]
def permit_updater(kb_state, sent):
""" Adds one of the following sentences in the database:
- Getting players is not permitted.
- Dropping the item in, on, or under itself is not permitted.
where element_key is 'color' or 'size'.
"""
if len(sent.describers) > 0:
describer = sent.describers[0]
rel = describer.get_arg("Rel")
if rel == 'permitted':
getting_players = tsentences.permit(tsentences.get(rel=("getting", None), entity=("players", None)),
neg=("not", None), rel=("permitted", None))
dropping_items = tsentences.drop(rel=("dropping", None),
entity=(["the", "item", set(kb_state.world.location_positions), "itself"], None),
)
dropping_not_permitted = tsentences.permit(action_allowed=dropping_items,
neg=("not", None),
rel=("permitted", None))
if sent in [getting_players, dropping_not_permitted]:
basic_updater(kb_state, sent)