1414)
1515
1616from .entity_class import EntityClass
17+ from .entity_space import EntityMapping , EntitySpace , OptionalMappingManager
1718
1819logger = logging .getLogger (__name__ )
1920
@@ -23,133 +24,200 @@ class Annotation:
2324 """General annotation."""
2425
2526 name : str
27+ entity_spaces : dict [EntityClass , EntitySpace ]
2628 alias : str | None = None
2729 description : str | None = None
28- _entity_class_to_values : dict [EntityClass , list [ Scalar ]] = field (default_factory = dict [ EntityClass , list [ Scalar ]] )
30+ _entity_managers : dict [EntityClass , OptionalMappingManager ] = field (default_factory = dict )
2931
3032 @classmethod
3133 def entity_class_enabled (cls , entity_class : EntityClass ) -> bool :
3234 """Check if the given entity class is enabled for this annotation type. Can be overridden by subclasses to restrict which entity classes the annotation subclass can be applied to."""
3335 return True
3436
35- def __str__ (self ) -> str :
36- s = f"Annotation(name={ self .name !r} , alias={ self .alias !r} , description={ self .description !r} , values={{\n "
37- indent_str = " "
38- for entity_class , values in self ._entity_class_to_values .items ():
39- s += f"{ indent_str } { entity_class } : { values } \n "
40- s += "})"
41- return s
37+ def __post_init__ (self ):
38+ for entity_class in EntityClass :
39+ assert entity_class in self .entity_spaces , f"Entity space for { entity_class } must be provided"
40+ self ._entity_managers [entity_class ] = OptionalMappingManager [Scalar ](
41+ name = f"{ self .name } ::{ entity_class .value } _to_value" ,
42+ domain = self .entity_spaces [entity_class ],
43+ can_have_mapping = lambda entity_class = entity_class : self .entity_class_enabled (entity_class ),
44+ )
45+ self .entity_spaces [entity_class ]._subscribe (
46+ self , lambda * args : self ._entity_managers [entity_class ].auto_manage ()
47+ )
4248
4349 @property
44- def mappings (self ) -> dict [EntityClass , list [Scalar ]]:
45- """Alias for entity_class_to_values."""
46- return self ._entity_class_to_values
50+ def _state_values_manager (self ) -> OptionalMappingManager [Scalar ]:
51+ return self ._entity_managers [EntityClass .STATES ]
4752
4853 @property
49- def has_values (self ) -> bool :
50- """Check if the annotation has any values set."""
51- return len (self ._entity_class_to_values ) > 0
54+ def _choice_values_manager (self ) -> OptionalMappingManager [Scalar ]:
55+ return self ._entity_managers [EntityClass .CHOICES ]
5256
5357 @property
54- def entity_classes (self ) -> set [EntityClass ]:
55- """Get the set of entity classes for which this annotation has values."""
56- return set (self ._entity_class_to_values .keys ())
58+ def _branch_values_manager (self ) -> OptionalMappingManager [Scalar ]:
59+ return self ._entity_managers [EntityClass .BRANCHES ]
5760
58- def has_values_for ( self , entity_class : EntityClass ) -> bool :
59- """Check if the annotation has values for the given entity class."""
60- return self ._entity_class_to_values . get ( entity_class ) is not None
61+ @ property
62+ def _player_values_manager ( self ) -> OptionalMappingManager [ Scalar ]:
63+ return self ._entity_managers [ EntityClass . PLAYERS ]
6164
62- def get_values_for (self , entity_class : EntityClass ) -> list [Scalar ]:
63- """
64- Get the values for the given entity class.
65- :raises KeyError: if no values are set for the given entity class
66- """
67- if not self .has_values_for (entity_class ):
68- raise KeyError (f"Annotation has no values for entity class { entity_class } " )
69- return self ._entity_class_to_values [entity_class ]
65+ @property
66+ def _observation_values_manager (self ) -> OptionalMappingManager [Scalar ]:
67+ return self ._entity_managers [EntityClass .OBSERVATIONS ]
7068
71- def set_values_for (self , entity_class : EntityClass , values : Sequence [Scalar ]) -> None :
72- """Set the values for the given entity class."""
73- if not self .entity_class_enabled (entity_class ):
74- raise ValueError (f"Entity class { entity_class } is not enabled for this annotation type" )
75- self ._entity_class_to_values [entity_class ] = list (values )
69+ def add_state_values (self ):
70+ self ._state_values_manager .create_mapping ()
7671
77- def unset_values_for (self , entity_class : EntityClass ) -> None :
78- """Unset the values for the given entity class."""
79- if not self .has_values_for (entity_class ):
80- raise KeyError (f"Annotation has no values for entity class { entity_class } " )
81- del self ._entity_class_to_values [entity_class ]
72+ def remove_state_values (self ):
73+ self ._state_values_manager .remove_mapping ()
8274
83- ### Convenience properties and methods for each entity class ###
8475 @property
8576 def has_state_values (self ) -> bool :
86- return self .has_values_for (EntityClass .STATES )
77+ return self ._state_values_manager .has_mapping
78+
79+ @property
80+ def state_values (self ) -> EntityMapping [Scalar ]:
81+ return self ._state_values_manager .mapping
82+
83+ @state_values .setter
84+ def state_values (self , values : Sequence [Scalar ] | None ) -> None :
85+ self ._state_values_manager .mapping = values
86+
87+ def add_choice_values (self ):
88+ self ._choice_values_manager .create_mapping ()
89+
90+ def remove_choice_values (self ):
91+ self ._choice_values_manager .remove_mapping ()
8792
8893 @property
8994 def has_choice_values (self ) -> bool :
90- return self .has_values_for (EntityClass .CHOICES )
95+ return self ._choice_values_manager .has_mapping
96+
97+ @property
98+ def choice_values (self ) -> EntityMapping [Scalar ]:
99+ return self ._choice_values_manager .mapping
100+
101+ @choice_values .setter
102+ def choice_values (self , values : Sequence [Scalar ] | None ) -> None :
103+ self ._choice_values_manager .mapping = values
104+
105+ def add_branch_values (self ):
106+ self ._branch_values_manager .create_mapping ()
107+
108+ def remove_branch_values (self ):
109+ self ._branch_values_manager .remove_mapping ()
91110
92111 @property
93112 def has_branch_values (self ) -> bool :
94- return self .has_values_for ( EntityClass . BRANCHES )
113+ return self ._branch_values_manager . has_mapping
95114
96115 @property
97- def has_observation_values (self ) -> bool :
98- return self .has_values_for (EntityClass .OBSERVATIONS )
116+ def branch_values (self ) -> EntityMapping [Scalar ]:
117+ return self ._branch_values_manager .mapping
118+
119+ @branch_values .setter
120+ def branch_values (self , values : Sequence [Scalar ] | None ) -> None :
121+ self ._branch_values_manager .mapping = values
122+
123+ def add_player_values (self ):
124+ self ._player_values_manager .create_mapping ()
125+
126+ def remove_player_values (self ):
127+ self ._player_values_manager .remove_mapping ()
99128
100129 @property
101130 def has_player_values (self ) -> bool :
102- return self .has_values_for ( EntityClass . PLAYERS )
131+ return self ._player_values_manager . has_mapping
103132
104133 @property
105- def state_values (self ) -> list [Scalar ]:
106- return self .get_values_for ( EntityClass . STATES )
134+ def player_values (self ) -> EntityMapping [Scalar ]:
135+ return self ._player_values_manager . mapping
107136
108- @property
109- def choice_values (self ) -> list [Scalar ]:
110- return self .get_values_for ( EntityClass . CHOICES )
137+ @player_values . setter
138+ def player_values (self , values : Sequence [Scalar ] | None ) -> None :
139+ self ._player_values_manager . mapping = values
111140
112- @property
113- def branch_values (self ) -> list [Scalar ]:
114- return self .get_values_for (EntityClass .BRANCHES )
141+ def add_observation_values (self ):
142+ self ._observation_values_manager .create_mapping ()
143+
144+ def remove_observation_values (self ):
145+ self ._observation_values_manager .remove_mapping ()
115146
116147 @property
117- def observation_values (self ) -> list [ Scalar ] :
118- return self .get_values_for ( EntityClass . OBSERVATIONS )
148+ def has_observation_values (self ) -> bool :
149+ return self ._observation_values_manager . has_mapping
119150
120151 @property
121- def player_values (self ) -> list [Scalar ]:
122- return self .get_values_for ( EntityClass . PLAYERS )
152+ def observation_values (self ) -> EntityMapping [Scalar ]:
153+ return self ._observation_values_manager . mapping
123154
124- def set_state_values (self , values : Sequence [Scalar ]):
125- self .set_values_for (EntityClass .STATES , values )
155+ @observation_values .setter
156+ def observation_values (self , values : Sequence [Scalar ] | None ) -> None :
157+ self ._observation_values_manager .mapping = values
126158
127- def set_choice_values (self , values : Sequence [Scalar ]):
128- self .set_values_for (EntityClass .CHOICES , values )
159+ def validate (self ):
160+ for manager in self ._entity_managers .values ():
161+ manager .validate ()
129162
130- def set_branch_values (self , values : Sequence [Scalar ]):
131- self .set_values_for (EntityClass .BRANCHES , values )
163+ def __str__ (self ) -> str :
164+ s = f"Annotation(name={ self .name !r} , alias={ self .alias !r} , description={ self .description !r} , values={{\n "
165+ indent_str = " "
166+ for entity_class , values in self ._entity_class_to_values .items ():
167+ s += f"{ indent_str } { entity_class } : { values } \n "
168+ s += "})"
169+ return s
132170
133- def set_observation_values (self , values : Sequence [Scalar ]):
134- self .set_values_for (EntityClass .OBSERVATIONS , values )
171+ ### legacy methods
135172
136- def set_player_values (self , values : Sequence [Scalar ]):
137- self .set_values_for (EntityClass .PLAYERS , values )
173+ @property
174+ def _entity_class_to_values (self ) -> dict [EntityClass , EntityMapping [Scalar ]]:
175+ """Get a mapping from entity class to the corresponding values mapping for this annotation."""
176+ entity_class_to_values = {}
177+ for entity_class , manager in self ._entity_managers .items ():
178+ if manager .has_mapping :
179+ entity_class_to_values [entity_class ] = manager .mapping
180+ return entity_class_to_values
181+
182+ # @property
183+ # def mappings(self) -> dict[EntityClass, EntityMapping[Scalar]]:
184+ # """Alias for entity_class_to_values."""
185+ # return self._entity_class_to_values
138186
139- def unset_state_values (self ):
140- self .unset_values_for (EntityClass .STATES )
187+ @property
188+ def has_values (self ) -> bool :
189+ """Check if the annotation has any values set."""
190+ return len (self ._entity_class_to_values ) > 0
141191
142- def unset_choice_values (self ):
143- self .unset_values_for (EntityClass .CHOICES )
192+ @property
193+ def entity_classes (self ) -> set [EntityClass ]:
194+ """Get the set of entity classes for which this annotation has values."""
195+ return set (self ._entity_class_to_values .keys ())
144196
145- def unset_branch_values (self ):
146- self .unset_values_for (EntityClass .BRANCHES )
197+ def has_values_for (self , entity_class : EntityClass ) -> bool :
198+ """Check if the annotation has values for the given entity class."""
199+ return self ._entity_class_to_values .get (entity_class ) is not None
147200
148- def unset_observation_values (self ):
149- self .unset_values_for (EntityClass .OBSERVATIONS )
201+ def get_values_for (self , entity_class : EntityClass ) -> EntityMapping [Scalar ]:
202+ """
203+ Get the values for the given entity class.
204+ :raises KeyError: if no values are set for the given entity class
205+ """
206+ if not self .has_values_for (entity_class ):
207+ raise KeyError (f"Annotation has no values for entity class { entity_class } " )
208+ return self ._entity_class_to_values [entity_class ]
209+
210+ def set_values_for (self , entity_class : EntityClass , values : Sequence [Scalar ]) -> None :
211+ """Set the values for the given entity class."""
212+ if not self .entity_class_enabled (entity_class ):
213+ raise ValueError (f"Entity class { entity_class } is not enabled for this annotation type" )
214+ self ._entity_managers [entity_class ].mapping = values
150215
151- def unset_player_values (self ):
152- self .unset_values_for (EntityClass .PLAYERS )
216+ def remove_values_for (self , entity_class : EntityClass ) -> None :
217+ """Remove the values for the given entity class."""
218+ if not self .has_values_for (entity_class ):
219+ raise KeyError (f"Annotation has no values for entity class { entity_class } " )
220+ del self ._entity_class_to_values [entity_class ]
153221
154222 def __eq__ (self , other : object ) -> bool :
155223 if not isinstance (other , Annotation ):
@@ -168,22 +236,6 @@ def get_common_type(self) -> ScalarType:
168236 """
169237 return scalar_promotion_type_of (itertools .chain .from_iterable (self ._entity_class_to_values .values ()))
170238
171- def validate (self ):
172- """Validate the annotation data."""
173- return
174-
175- def _permute_entities (self , entity_class : EntityClass , new_to_old : list [int ]) -> None :
176- """Reorder the values for the given entity class according to the new order.
177-
178- :param entity_class: the entity class for which to reorder values
179- :param new_to_old: a list mapping new indices to old indices, where new_to_old[new_index] = old_index
180- """
181- if not self .has_values_for (entity_class ):
182- return
183- values = self ._entity_class_to_values [entity_class ]
184- # we don't check the order
185- self ._entity_class_to_values [entity_class ] = [values [i ] for i in new_to_old ]
186-
187239
188240class RewardAnnotation (Annotation ):
189241 """Reward annotation is a numeric annotation that can be associated with states, choices, or branches."""
@@ -221,9 +273,10 @@ def validate(self):
221273class ObservationAnnotation (Annotation ):
222274 """Observation annotation is an integer annotation that can only be associated with either states or branches."""
223275
224- def __init__ (self , num_observations : int ) -> None :
225- super ().__init__ (name = "observation" )
226- self .num_observations = num_observations
276+ def __init__ (self , ** kwargs ) -> None :
277+ super ().__init__ (name = "observation" , ** kwargs )
278+ self ._entity_managers [EntityClass .STATES ].codomain = self .entity_spaces [EntityClass .OBSERVATIONS ]
279+ self ._entity_managers [EntityClass .BRANCHES ].codomain = self .entity_spaces [EntityClass .OBSERVATIONS ]
227280
228281 @classmethod
229282 def entity_class_enabled (cls , entity_class : EntityClass ) -> bool :
@@ -248,15 +301,11 @@ def set_values_for(self, entity_class: EntityClass, values: Sequence[Scalar]) ->
248301
249302 def validate (self ) -> None :
250303 super ().validate ()
251- if not (0 < self .num_observations ):
252- raise ValueError (f"num_observations must be a positive integer, got { self .num_observations } " )
253- if self .get_common_type () != NumericPrimitiveType .INT :
254- raise ValueError (f"Observation annotation type must be INT, got { self .get_common_type ()} " )
255- for item , obs in enumerate (self .values ):
256- if not 0 <= obs < self .num_observations : # type: ignore
257- raise ValueError (f"observation mapping[{ item } ] = { obs } is out of range [0, { self .num_observations } )" )
304+ datatype = self .get_common_type ()
305+ if datatype != NumericPrimitiveType .INT :
306+ raise ValueError (f"Observation annotation type must be INT, got { datatype } " )
258307
259308 def __eq__ (self , other : object ) -> bool :
260309 if not isinstance (other , ObservationAnnotation ):
261310 return False
262- return super ().__eq__ (other ) and self . num_observations == other . num_observations
311+ return super ().__eq__ (other )
0 commit comments