Repository: isi-vista/adam

Issue:
Allow marking an object in the situation representation as the speaker

Comments:

Patch:
diff --git a/adam/ontology/phase1_ontology.py b/adam/ontology/phase1_ontology.py
index 3a16ec0a..7549b4cb 100644
--- a/adam/ontology/phase1_ontology.py
+++ b/adam/ontology/phase1_ontology.py
@@ -61,7 +61,7 @@ subtype(PERCEIVABLE, META_PROPERTY)
 BINARY = OntologyNode("binary")
 subtype(BINARY, META_PROPERTY)
 
-# properties of objects
+# properties of objects which can be perceived by the learner
 PERCEIVABLE_PROPERTY = OntologyNode("perceivable-property", [PERCEIVABLE])
 subtype(PERCEIVABLE_PROPERTY, PROPERTY)
 ANIMATE = OntologyNode("animate", [BINARY])
@@ -82,7 +82,19 @@ The prototypical cases here are *Mom* and *Dad*.
 
 subtype(RECOGNIZED_PARTICULAR, PERCEIVABLE_PROPERTY)
 
-CAN_MANIPULATE_OBJECTS = OntologyNode("sentient")
+IS_SPEAKER = OntologyNode("is-speaker", [BINARY])
+"""
+Indicates that the marked object is the one who is speaking 
+the linguistic description of the situation. 
+This will not be present for all situations.
+It only makes sense to apply this to sub-types of PERSON,
+but this is not currently enforced.
+"""
+subtype(IS_SPEAKER, PERCEIVABLE_PROPERTY)
+
+# Properties not perceived by the learner, but useful for situation generation
+
+CAN_MANIPULATE_OBJECTS = OntologyNode("can-manipulate-objects")
 subtype(CAN_MANIPULATE_OBJECTS, PROPERTY)
 
 
diff --git a/adam/perception/high_level_semantics_situation_to_developmental_primitive_perception.py b/adam/perception/high_level_semantics_situation_to_developmental_primitive_perception.py
index 84ebd38d..1acd51df 100644
--- a/adam/perception/high_level_semantics_situation_to_developmental_primitive_perception.py
+++ b/adam/perception/high_level_semantics_situation_to_developmental_primitive_perception.py
@@ -1,30 +1,30 @@
-from typing import Dict, List, Optional
-
 from attr import Factory, attrib, attrs
 from attr.validators import instance_of
 from immutablecollections import ImmutableDict, immutabledict
-from more_itertools import only
+from more_itertools import only, quantify
+from typing import Dict, List, Optional
 from vistautils.preconditions import check_arg
 
 from adam.ontology import ObjectStructuralSchema, SubObject
 from adam.ontology.ontology import Ontology
 from adam.ontology.phase1_ontology import (
-    PART_OF,
-    PERCEIVABLE,
     BINARY,
     COLOR,
     COLORS_TO_RGBS,
     GAILA_PHASE_1_ONTOLOGY,
+    IS_SPEAKER,
+    PART_OF,
+    PERCEIVABLE,
 )
 from adam.perception import PerceptualRepresentation, PerceptualRepresentationGenerator
 from adam.perception.developmental_primitive_perception import (
     DevelopmentalPrimitivePerceptionFrame,
+    HasBinaryProperty,
+    HasColor,
     ObjectPerception,
-    RelationPerception,
     PropertyPerception,
-    HasBinaryProperty,
+    RelationPerception,
     RgbColorPerception,
-    HasColor,
 )
 from adam.random_utils import SequenceChooser
 from adam.situation import SituationObject
@@ -132,6 +132,8 @@ class _PerceptionGeneration:
     """
 
     def do(self) -> PerceptualRepresentation[DevelopmentalPrimitivePerceptionFrame]:
+        self._sanity_check_situation()
+
         # The first step is to determine what objects are perceived.
         self._perceive_objects()
 
@@ -150,6 +152,19 @@ class _PerceptionGeneration:
             )
         )
 
+    def _sanity_check_situation(self) -> None:
+        if (
+            quantify(
+                property_ == IS_SPEAKER
+                for object_ in self._situation.objects
+                for property_ in object_.properties
+            )
+            > 1
+        ):
+            raise TooManySpeakersException(
+                f"Situations with multiple speakers are not supported: {self._situation}"
+            )
+
     def _perceive_property_assertions(self) -> None:
         for situation_object in self._situation.objects:
             # Add the perceivable properties of each situation object into the perception
@@ -269,3 +284,8 @@ class _PerceptionGeneration:
 GAILA_PHASE_1_PERCEPTION_GENERATOR = HighLevelSemanticsSituationToDevelopmentalPrimitivePerceptionGenerator(
     GAILA_PHASE_1_ONTOLOGY
 )
+
+
+@attrs(auto_exc=True, auto_attribs=True)
+class TooManySpeakersException(RuntimeError):
+    msg: str
diff --git a/requirements.txt b/requirements.txt
index f262a8bf..16f059b1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 # core requirements
-attrs==18.2.0
+attrs==19.1.0
 vistautils==0.12.0
 immutablecollections==0.8.0
 networkx==2.3


Test patch:
diff --git a/tests/perception/high_level_semantics_situation_to_developmental_primitive_perception_test.py b/tests/perception/high_level_semantics_situation_to_developmental_primitive_perception_test.py
index 6673f893..0da1e498 100644
--- a/tests/perception/high_level_semantics_situation_to_developmental_primitive_perception_test.py
+++ b/tests/perception/high_level_semantics_situation_to_developmental_primitive_perception_test.py
@@ -1,18 +1,25 @@
+import pytest
+from more_itertools import quantify
+
+from adam.ontology import OntologyNode
 from adam.ontology.phase1_ontology import (
     BALL,
     GAILA_PHASE_1_ONTOLOGY,
+    IS_SPEAKER,
     PERSON,
     RED,
-    JUICE,
     LIQUID,
+    JUICE,
 )
 from adam.perception.developmental_primitive_perception import (
-    PropertyPerception,
+    DevelopmentalPrimitivePerceptionFrame,
     HasBinaryProperty,
     HasColor,
+    PropertyPerception,
 )
 from adam.perception.high_level_semantics_situation_to_developmental_primitive_perception import (
     HighLevelSemanticsSituationToDevelopmentalPrimitivePerceptionGenerator,
+    TooManySpeakersException,
 )
 from adam.random_utils import RandomChooser
 from adam.situation import SituationObject
@@ -97,6 +104,46 @@ def test_person_and_ball_color():
     } == {("ball_0", "#f2003c")}
 
 
+def _some_object_has_binary_property(
+    perception_frame: DevelopmentalPrimitivePerceptionFrame, query_property: OntologyNode
+) -> bool:
+    return (
+        quantify(
+            isinstance(property_assertion, HasBinaryProperty)
+            and property_assertion.binary_property == query_property
+            for property_assertion in perception_frame.property_assertions
+        )
+        > 0
+    )
+
+
+def test_speaker_perceivable():
+    speaker_situation_perception = _PERCEPTION_GENERATOR.generate_perception(
+        HighLevelSemanticsSituation(
+            ontology=GAILA_PHASE_1_ONTOLOGY,
+            objects=[SituationObject(PERSON, [IS_SPEAKER])],
+        ),
+        chooser=RandomChooser.for_seed(0),
+    )
+    assert _some_object_has_binary_property(
+        speaker_situation_perception.frames[0], IS_SPEAKER
+    )
+
+
+def test_not_two_speakers():
+    with pytest.raises(TooManySpeakersException):
+        _PERCEPTION_GENERATOR.generate_perception(
+            HighLevelSemanticsSituation(
+                ontology=GAILA_PHASE_1_ONTOLOGY,
+                objects=[
+                    SituationObject(PERSON, [IS_SPEAKER]),
+                    SituationObject(PERSON, [IS_SPEAKER]),
+                ],
+            ),
+            chooser=RandomChooser.for_seed(0),
+        )
+
+
 def test_liquid_perceivable():
     juice_perception = _PERCEPTION_GENERATOR.generate_perception(
         HighLevelSemanticsSituation(