def construct_relation_group(
      self,
      group_name,
      domain_type,
      range_type,
      group_members = None):
    """Declare a group of relations.

    This must be called after all the triples for all the relations in the group
    have already been defined.

    A group of relations is a special kind of type, which can be aggregated over
    with the follows(group) command.  Examples of group declarations:

      context.construct_relation_group(
        'dir_g', 'place_t', 'place_t')
      context.construct_relation_group(
         'vdir_g', 'place_t', 'place_t', ['n', 's'])

    After a group 'foo' is defined then you can use methods like
    x.follow(context.all('dir_g')) which is equivalent to (x.follow('r1') |
    ... | x.follow('rk')) where r1, ..., rk are the relations in the group 'foo'

    In general x.follow(g) is a weighted version of the above, ie, if g gives
    weights w1...wk to r1...rk, then

      x.follow(g) == w1*x.follow('r1') + ... + wk*x.follow('rk')

    How this is implemented: The group is basically just a type where the
    members are relation names, but creating the group also extends the schema,
    and the sparse matrices that define the knowledge graph.  If there is a
    group called 'foo_g', then the following relations and type will be created
    and populated:

    - foo_g: is the type for the relation names
    - foo_g_triple_t: a type for 'triples' for relation group foo.

     A triple is a conceptually a datastructure with three parts: (rel,
     subj,obj) represented with a triple id and these relations:

    - foo_g_sub, foo_g_obj, foo_g_rel: these relations map foo_g triple ids to
      their subjects, objects, and relations (the relation being of type
      'foo_rel_t', and the subj and obj being of types 'domain_type' and
      'range_type' respectively.

    - Finally, foo_g_weight is a relation mapping every triple id to itself,
      with weight equal to the weight of the original fact the triple was
      derived from.

    Args:
      group_name: a string of the to-be-constructed type, which will hold a set
        of relations
      domain_type: a string naming the subject entity type of all the relations
      range_type: a string naming the objects entity type of all the relations
      group_members: the relations to add to the group. By default members of
        group will be all declared relations with that type signature (ie, with
        the specified domain and range).

    Returns:
      a RelationGroup object which summarizes all of these names.

    Raises:
      ValueError: If dense relations are specified as they cannot be handled.
      RelationNameError: If a relation in the group hasn't been defined or if
        there already exists a relation with the name group_name.
    """
    if not group_members:
      group_members = sorted([
          rel for rel in self.get_relation_names()
          if self.get_domain(rel) == domain_type and
          self.get_range(rel) == range_type
      ])
    if self.is_type(group_name):
      raise RelationNameError(group_name, 'Group already exists.')

    self.declare_entity_type(
        group_name, fixed_vocab=group_members, unknown_marker=None)

    for r in group_members:
      if self.is_dense(r):
        raise ValueError('Dense relation %r is unsupported.' % r)

    group = RelationGroup(group_name, group_members)
    self._group[group_name] = group
    # declare the schema for the necessary extension to the KG
    self.declare_relation(group.relation_rel, group.triple_type, group_name)
    self.declare_relation(group.subject_rel, group.triple_type, domain_type)
    self.declare_relation(group.object_rel, group.triple_type, range_type)
    self.declare_relation(group.weight_rel, group.triple_type,
                          group.triple_type)
    # relation i in this group has num_rows[i] rows
    try:
      num_rows = [self._np_initval[r].data.shape[0] for r in group.members]
    except KeyError as err:
      raise RelationNameError(
          str(err), 'An undefined relation was encountered. '
          'All relations in a relation group must be defined before calling '
          'construct_relation_group.')
    total_num_rows = sum(num_rows)
    # names of all those triples
    self.extend_type(
        group.triple_type,
        [group.triple_prefix + str(i) for i in range(total_num_rows)])
    # now populate the sparse matrixes
    triple_indices = np.arange(total_num_rows, dtype='int32')
    rel_indices = np.hstack([
        np.ones(num_rows[i], dtype='int32') * i
        for i in range(len(group.members))
    ])
    subj_indices = np.hstack([self._np_initval[r].col for r in group.members])
    obj_indices = np.hstack([self._np_initval[r].row for r in group.members])
    weight_data = np.hstack([self._np_initval[r].data for r in group.members])
    ones_data = np.ones_like(weight_data)
    # weights are in a diagonal matrix
    self._np_initval[group.weight_rel] = scipy.sparse.coo_matrix(
        (weight_data, (triple_indices, triple_indices)),
        shape=(total_num_rows, total_num_rows),
        dtype='float32')
    self._np_initval[group.relation_rel] = scipy.sparse.coo_matrix(
        (weight_data, (rel_indices, triple_indices)),
        shape=(len(group.members), total_num_rows),
        dtype='float32')
    self._np_initval[group.subject_rel] = scipy.sparse.coo_matrix(
        (ones_data, (subj_indices, triple_indices)),
        shape=(self.get_max_id(domain_type), total_num_rows),
        dtype='float32')
    self._np_initval[group.object_rel] = scipy.sparse.coo_matrix(
        (ones_data, (obj_indices, triple_indices)),
        shape=(self.get_max_id(range_type), total_num_rows),
        dtype='float32')
    self.freeze(group.triple_type, unknown_marker=None)
    return group
Declare a group of relations.

    This must be called after all the triples for all the relations in the group
    have already been defined.

    A group of relations is a special kind of type, which can be aggregated over
    with the follows(group) command.  Examples of group declarations:

      context.construct_relation_group(
        'dir_g', 'place_t', 'place_t')
      context.construct_relation_group(
         'vdir_g', 'place_t', 'place_t', ['n', 's'])

    After a group 'foo' is defined then you can use methods like
    x.follow(context.all('dir_g')) which is equivalent to (x.follow('r1') |
    ... | x.follow('rk')) where r1, ..., rk are the relations in the group 'foo'

    In general x.follow(g) is a weighted version of the above, ie, if g gives
    weights w1...wk to r1...rk, then

      x.follow(g) == w1*x.follow('r1') + ... + wk*x.follow('rk')

    How this is implemented: The group is basically just a type where the
    members are relation names, but creating the group also extends the schema,
    and the sparse matrices that define the knowledge graph.  If there is a
    group called 'foo_g', then the following relations and type will be created
    and populated:

    - foo_g: is the type for the relation names
    - foo_g_triple_t: a type for 'triples' for relation group foo.

     A triple is a conceptually a datastructure with three parts: (rel,
     subj,obj) represented with a triple id and these relations:

    - foo_g_sub, foo_g_obj, foo_g_rel: these relations map foo_g triple ids to
      their subjects, objects, and relations (the relation being of type
      'foo_rel_t', and the subj and obj being of types 'domain_type' and
      'range_type' respectively.

    - Finally, foo_g_weight is a relation mapping every triple id to itself,
      with weight equal to the weight of the original fact the triple was
      derived from.

    Args:
      group_name: a string of the to-be-constructed type, which will hold a set
        of relations
      domain_type: a string naming the subject entity type of all the relations
      range_type: a string naming the objects entity type of all the relations
      group_members: the relations to add to the group. By default members of
        group will be all declared relations with that type signature (ie, with
        the specified domain and range).

    Returns:
      a RelationGroup object which summarizes all of these names.

    Raises:
      ValueError: If dense relations are specified as they cannot be handled.
      RelationNameError: If a relation in the group hasn't been defined or if
        there already exists a relation with the name group_name.
    
language/nql/nql/__init__.py