def _validate_start(self, attr, val): # pylint:disable=unused-argument check_arg( self.start < self.end, "Start offset must be strictly less then end offset but " "got [%s,%s)", (self.start, self.end), )
def sampled( situation_template: Phase1SituationTemplate, *, ontology: Ontology, chooser: SequenceChooser, max_to_sample: int, default_addressee_node: OntologyNode = LEARNER, block_multiple_of_the_same_type: bool, ) -> Iterable[HighLevelSemanticsSituation]: """ Gets *max_to_sample* instantiations of *situation_template* with *ontology* """ check_arg(max_to_sample >= 0) return list( take( max_to_sample, _Phase1SituationTemplateGenerator( ontology=ontology, variable_assigner=_SamplingVariableAssigner(), block_multiple_objects_of_the_same_type= block_multiple_of_the_same_type, ).generate_situations( situation_template, chooser=chooser, default_addressee_node=default_addressee_node, ), ))
def create_at_random_position_scaled( *, min_distance_from_origin: float, max_distance_from_origin: float, object_scale: torch.Tensor, ): check_arg(min_distance_from_origin > 0.0) check_arg(min_distance_from_origin < max_distance_from_origin) # we first generate a random point on the unit sphere by # generating a random vector in cube... center = np.random.randn(3, 1).squeeze() # and then normalizing. center /= np.linalg.norm(center) # then we scale according to the distances above scale_factor = np.random.uniform(min_distance_from_origin, max_distance_from_origin) center *= scale_factor return AxisAlignedBoundingBox( Parameter( torch.tensor(center, dtype=torch.float), # pylint: disable=not-callable requires_grad=True, ), torch.diag(object_scale), offset=None, )
def __attrs_post_init__(self) -> None: for relation in self.relations: check_arg( not relation.negated, "Negated relations cannot appear in perceptual " "representations but got %s", (relation, ), )
def test_check_arg_interpolation(self): with self.assertRaisesRegex( ValueError, "Expected height to exceed 48 but got 41" ): height = 41 reference = 48 check_arg( height > reference, "Expected height to exceed %s but got %s", (reference, height), )
def __attrs_post_init__(self) -> None: # you either need a path operator # or an orientation change around an axis # (e.g. for rotation without translation) # weird conditional to make mypy happy if (not self.reference_object and not self.reference_axis and not self.orientation_changed): raise RuntimeError( "A path must have at least one of a reference objects, " "a reference axis, or an orientation change") if self.reference_axis: check_arg(isinstance(self.reference_axis, (GeonAxis, AxisFunction)))
def put(self, key: str, value: V) -> None: check_state(self._zip_file, "Must use zip key-value sink as a context manager") check_not_none(key) check_not_none(value) if key in self._keys: raise ValueError( "Zip-backed key-value sinks do not support duplicate puts on the " "same key" ) self._keys.add(key) filename = self._filename_function(key) check_arg(isinstance(value, str) or isinstance(value, bytes)) self._zip_file.writestr(filename, value) # type: ignore
def corners_onto_axes_projections(self, axes: torch.Tensor) -> torch.Tensor: """ Projects each of 8 corners onto each of three axes. Args: axes: (3,3) tensor -> the three axes we are projecting points onto Returns: (3, 8) tensor -> each point projected onto each of three dimensions """ check_arg(axes.shape == (3, 3)) corners = self.get_corners() return axes.matmul(corners.transpose(0, 1))
def annotated_text( self, text: str, annotations: Collection[AnnotatedSpan], *, text_offsets: Optional[Span] = None, ) -> str: """ Mark annotations on text in an HTML-like style. Each annotation will becomes an HTML tag wrapping the text at the corresponding offsets. Any attributes will become HTML attributes. This does not add any other HTML annotations (`head`, `body`, etc.), so if desired the user should add them afterwards. If `text_offsets` is specified, the annotations are assumed to have offsets with respect to some larger string, where `text` is a substring of that string with offsets `text_offsets` relative to it. You might use this, for example, to render a single paragraph from a document. """ if not text_offsets: text_offsets = Span.from_inclusive_to_exclusive(0, len(text)) check_arg( len(text_offsets) == len(text), f"Text offsets length {len(text_offsets)} " f"does not match text length {len(text)}", ) # we process the annotations to (a) ensure they all fit within the requested snippet # and (b) shift their offsets so that all offsets are relative to the text being # formatted processed_annotations = self._clip_to_offsets_and_shift( annotations, text_offsets) ret = io.StringIO() last_uncopied_offset = 0 for tag in self._tag_sequence(processed_annotations): if last_uncopied_offset < tag.offset: ret.write(text[last_uncopied_offset:tag.offset]) last_uncopied_offset = tag.offset ret.write(tag.string) # get any trailing text after last tag if last_uncopied_offset < text_offsets.end: ret.write(text[last_uncopied_offset:text_offsets.end]) return ret.getvalue()
def windowed(it, window_size: int, *, partial_windows: bool = False): # noqa: F811 check_arg(window_size >= 1) if not hasattr(it, "__next__"): return _WindowedIterable(wrapped_iterable=it, window_size=window_size, partial_windows=partial_windows) # we know at this point that it is an Iterable, but mypy might not, hence the ignores below if partial_windows: return _possibly_incomplete_windows(it, window_size) else: return _complete_windows(it, window_size)
def __attrs_post_init__(self) -> None: # disabled warning below is due to a PyCharm bug # noinspection PyTypeChecker for property_ in self.properties: if not isinstance(property_, OntologyNode): raise ValueError( f"Situation object property {property_} is not an " f"OntologyNode") for concrete_axis in self.schema_axis_to_object_axis.values(): check_arg(concrete_axis in self.axes.all_axes) # Every object should either have axes mapped to the axes of a schema object, # or should have WORLD_AXES, which is what we use by default for things # like substances which have no particular shape. check_arg( self.schema_axis_to_object_axis or self.axes == WORLD_AXES, "Axes must be aligned to a scheme or else be WORLD_AXES", )
def get_min_max_overlaps(min_max_proj_0: torch.Tensor, min_max_proj_1: torch.Tensor) -> torch.Tensor: """ Given min/max corner projections onto 3 axes from two different objects, return an interval for each dimension representing the degree of overlap or separation between the two objects. Args: min_max_proj_0: Tensor(3,2) min_max_projections for box 0 min_max_proj_1: Tensor(3,2) min_max projections for box 1 Returns: (3, 2) tensor -> ranges (start, end) of overlap OR separation in each of three dimensions. If (start - end) is positive, this indicates that the boxes do not overlap along this dimension, otherwise, a negative value indicates an overlap along that dimension. """ check_arg(min_max_proj_0.shape == (3, 2)) check_arg(min_max_proj_1.shape == (3, 2)) # see https://github.com/pytorch/pytorch/issues/24807 re: pylint issue dims = torch.tensor([0, 1, 2], dtype=torch.int) # pylint: disable=not-callable mins_0 = min_max_proj_0.gather(1, torch.zeros((3, 1), dtype=torch.long)) mins_1 = min_max_proj_1.gather(1, torch.zeros((3, 1), dtype=torch.long)) combined_mins = torch.stack((mins_0, mins_1), 1).squeeze() max_indices = torch.max(combined_mins, 1) maximum_mins = torch.take(combined_mins, max_indices[1] + (dims * 2)) # should stick together the minimum parts and the maximum parts # with columns like: # [ min0x min1x # min0y min1y # min0z min1z # ] # then find the maximum element from each row # repeat the process for the min of the max projections maxs_0 = min_max_proj_0.gather(1, torch.ones((3, 1), dtype=torch.long)) maxs_1 = min_max_proj_1.gather(1, torch.ones((3, 1), dtype=torch.long)) combined_maxes = torch.stack((maxs_0, maxs_1), 1).squeeze() min_indices = torch.min(combined_maxes, 1) minimum_maxes = torch.take(combined_maxes, min_indices[1] + (dims * 2)) return torch.stack((maximum_mins, minimum_maxes), 1)
def drop(it, num_to_skip: int): # noqa: F811 """ Skip `num_to_skip` elements of an ``Iterable`` or ``Iterator``. If `it` is an ``Iterator``, makes an ``Iterator`` which returns elements of the original iterator starting from the `num_to_skip+1`th element. If `it` is an ``Iterable``, makes an ``Iterable`` which returns elements of the original iterable starting from the `num_to_skip+1`th element. `num_to_skip` must be non-negative or a `ValueError` will be raised. """ check_arg( num_to_skip >= 0, "Number of items to skip must be positive but got %s", (num_to_skip, ), ) if hasattr(it, "__next__"): return itertools.islice(it, num_to_skip, None) else: return _DropIterable(it, num_to_skip)
def __init__( self, path: Path, *, filename_function: Callable[[str], str] = _identity, keys_in_function: Callable[[ZipFile], Optional[AbstractSet[str]]] = None, keys_out_function: Callable[[ZipFile, AbstractSet[str]], None] = None, overwrite: bool = True, ) -> None: self._path = path self._zip_file: Optional[ZipFile] = None self._filename_function = filename_function self._overwrite = overwrite self._keys_in_function = keys_in_function self._keys_out_function = keys_out_function self._keys: Set[str] = set() check_arg( self._keys_out_function or not self._keys_in_function, "If you specify a key output function, you should also specify a key input" " function", )
def __attrs_post_init__(self) -> None: check_arg(self.salient_object_variables, "A situation must contain at least one object") # ensure all objects referenced anywhere are on the object list objects_referenced_accumulator = list(self.salient_object_variables) for relation in chain(self.constraining_relations, self.asserted_always_relations): relation.accumulate_referenced_objects( objects_referenced_accumulator) for action in self.actions: action.accumulate_referenced_objects( objects_referenced_accumulator) unique_objects_referenced = immutableset( objects_referenced_accumulator) missing_objects = unique_objects_referenced - self.all_object_variables if missing_objects: raise RuntimeError( f"Set of referenced objects {unique_objects_referenced} does not match " f"object variables {self.all_object_variables} for template {self}: " f"the following are missing {missing_objects}")
def __attrs_post_init__(self) -> None: for cycle in simple_cycles(self._graph): raise ValueError( f"The ontology graph may not have cycles but got {cycle}") for required_node in REQUIRED_ONTOLOGY_NODES: check_arg( required_node in self, f"Ontology lacks required {required_node.handle} node", ) # every sub-type of THING must either have a structural schema # or be a sub-type of something with a structural schema for thing_node in dfs_preorder_nodes(self._graph.reverse(copy=False), THING): if not any(node in self._structural_schemata for node in self.ancestors(thing_node)): # e.g. "milk" does not have a structural schema if self.has_all_properties(thing_node, [ CAN_FILL_TEMPLATE_SLOT ]) and not self.has_all_properties(thing_node, [IS_SUBSTANCE]): raise RuntimeError( f"No structural schema is available for {thing_node}")
def get_min_max_corner_projections(projections: torch.Tensor): """ Retrieve the minimum and maximum corner projection (min/max extent in that dimension) for each axis Args: projections: Tensor(3, 8) -> corner projections onto each of three dimensions Returns: Tensor(3, 2) -> (min, max) values for each of three dimensions """ check_arg(projections.shape == (3, 8)) min_indices = torch.min(projections, 1) max_indices = torch.max(projections, 1) # these are tuples of (values, indices), both of which are tensors # helper variable for representing dimension numbers # see https://github.com/pytorch/pytorch/issues/24807 re: pylint issue dims = torch.tensor([0, 1, 2], dtype=torch.int) # pylint: disable=not-callable # select the indexed items (from a 24 element tensor) minima = torch.take(projections, min_indices[1] + (dims * 8)) maxima = torch.take(projections, max_indices[1] + (dims * 8)) # stack the minim return torch.stack((minima, maxima), 1)
def overlap_penalty(min_max_overlaps: torch.Tensor) -> torch.Tensor: """ Return penalty depending on degree of overlap between two 3d boxes. Args: min_max_overlaps: (3, 2) tensor -> intervals describing degree of overlap between the two boxes Returns: Tensor with a positive scalar of the collision penalty, or tensor with zero scalar for no collision. """ check_arg(min_max_overlaps.shape == (3, 2)) # subtract each minimum max from each maximum min: overlap_distance = min_max_overlaps[:, 0] - min_max_overlaps[:, 1] # as long as at least one dimension's overlap distance is positive (not overlapping), # then the boxes are not colliding for dim in range(3): if overlap_distance[dim] >= 0: return torch.zeros(1, dtype=torch.float) # otherwise the penetration distance is the maximum negative value # (the smallest translation that would disentangle the two # overlap is represented by a negative value, which we return as a positive penalty return overlap_distance.max() * -1 * COLLISION_PENALTY
def variable_assignments( self, *, ontology: Ontology, # pylint:disable=unused-argument object_variables: AbstractSet["TemplateObjectVariable"], property_variables: AbstractSet["TemplatePropertyVariable"], action_variables: AbstractSet["TemplateActionTypeVariable"], chooser: SequenceChooser, # pylint:disable=unused-argument ) -> Iterable[TemplateVariableAssignment]: check_arg( all(obj_var in self.assignment.object_variables_to_fillers for obj_var in object_variables)) check_arg( all(prop_var in self.assignment.property_variables_to_fillers for prop_var in property_variables)) check_arg( all(action_var in self.assignment.action_variables_to_fillers for action_var in action_variables)) return (self.assignment, )
def __attrs_post_init__(self) -> None: check_arg( self.distance or self.direction, "A region must have either a distance or direction specified.", )
def __attrs_post_init__(self) -> None: check_arg(isinstance(self.relative_to_axis, (GeonAxis, AxisFunction)))
def __attrs_post_init__(self) -> None: check_arg(callable(self.learner_factory), "Learner factory must be callable")
def __attrs_post_init__(self) -> None: if self.geon: check_arg(self.geon.axes == self.axes)
def __attrs_post_init__(self) -> None: for node in self._ontology_node_to_word: check_arg( node in self.ontology, f"Ontology lexicon refers to non-ontology node {node}", )
def __attrs_post_init__(self) -> None: check_arg(not isinstance(self.second_slot, CanRemapObjects) or self.relation_type == IN_REGION)
def __attrs_post_init__(self) -> None: check_arg( len(self._sub_selectors) > 1, "_And requires at least two sub-selectors")
def _select_nodes(self, ontology: Ontology) -> AbstractSet[OntologyNode]: for node in self._nodes: check_arg(node in ontology, f"{node} is not in the ontology") return self._nodes
def __attrs_post_init__(self) -> None: check_arg(self.salient_objects, "A situation must contain at least one object") for relation in chain( self.always_relations, self.before_action_relations, self.after_action_relations, ): if not isinstance(relation.first_slot, SituationObject) or not isinstance( relation.second_slot, (SituationObject, Region) ): raise RuntimeError( f"Relation fillers for situations must be situation objects " f"but got {relation}" ) if ( isinstance(relation.second_slot, Region) and relation.second_slot.reference_object not in self.all_objects ): raise RuntimeError( f"Any object referred to by a region must be included in the " f"set of situation objects but region {relation.second_slot}" f" with situation objects {self.all_objects}" ) if relation.relation_type == IN_REGION and not isinstance( relation.second_slot, Region ): raise RuntimeError( f"Any relation of relation type IN_REGION must have a second slot " f"which is a region but got {relation.second_slot} instead" ) self.is_dynamic = len(self.actions) > 0 for action in self.actions: for action_role_filler in flatten( action.argument_roles_to_fillers.value_groups() ): if ( isinstance(action_role_filler, SituationObject) and action_role_filler not in self.all_objects ): raise RuntimeError( "Any object filling a semantic role must be included in the " "set of situation objects." ) elif ( isinstance(action_role_filler, Region) and action_role_filler.reference_object not in self.all_objects ): raise RuntimeError( "Any object referred to by a region must be included in the " "set of situation objects." ) if not self.actions and ( self.before_action_relations or self.after_action_relations ): raise RuntimeError( "Cannot specify relations to hold before or after actions " "if there are no actions" ) # A situation cannot have multiple instances of the same recognized particular. # This blocks e.g. Dad gave Dad a house. recognized_particular_count = Counter( object_.ontology_node for object_ in self.all_objects if is_recognized_particular(self.ontology, object_.ontology_node) ) for (recognized_particular, count) in recognized_particular_count.items(): if count > 1: raise RuntimeError( f"Cannot have two instances of a recognized particular in a " f"situation, but got {count} instances of {recognized_particular}" f" in {self}" ) for object_ in self.gazed_objects: if isinstance(object_, Region): raise RuntimeError( f"Cannot have a Region as a gazed object in a situation, got" f"{object_} which is a region." )
def generate_situations( self, template: Phase1SituationTemplate, *, chooser: SequenceChooser = Factory(RandomChooser.for_seed), # pylint:disable=unused-argument default_addressee_node: OntologyNode = LEARNER, ) -> Iterable[HighLevelSemanticsSituation]: check_arg(isinstance(template, Phase1SituationTemplate)) try: # gather property variables from object variables property_variables = immutableset( property_ for obj_var in template.salient_object_variables for property_ in obj_var.asserted_properties if isinstance(property_, TemplatePropertyVariable)) action_type_variables = immutableset( action.action_type for action in template.actions if isinstance(action.action_type, TemplateActionTypeVariable)) failures_in_a_row = 0 for variable_assignment in self._variable_assigner.variable_assignments( ontology=self.ontology, object_variables=template.all_object_variables, property_variables=property_variables, action_variables=action_type_variables, chooser=chooser, ): # instantiate all objects in the situation according to the variable assignment. object_var_to_instantiations = self._instantiate_objects( template, variable_assignment, default_addressee_node=default_addressee_node, ) # Cannot have multiple instantiations of the same recognized particular. # e.g. "Dad gave Dad a box" if self._has_multiple_recognized_particulars( object_var_to_instantiations): continue if self.block_multiple_objects_of_the_same_type: object_instantiations_ontology_nodes = [ object_instantiation.ontology_node for object_instantiation in object_var_to_instantiations.values() ] if len(set(object_instantiations_ontology_nodes)) != len( object_instantiations_ontology_nodes): # There must be two objects of the same ontology type. continue # use them to instantiate the entire situation situation = self._instantiate_situation( template, variable_assignment, object_var_to_instantiations) if self._satisfies_constraints(template, situation, object_var_to_instantiations): failures_in_a_row = 0 yield situation else: failures_in_a_row += 1 if failures_in_a_row >= 250: raise RuntimeError( f"Failed to find a satisfying variable assignment " f"for situation template constraints after " f"{failures_in_a_row} consecutive attempts." f"Try shifting constraints from relations to properties." ) continue except Exception as e: raise RuntimeError( f"Exception while generating from situation template {template}" ) from e