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

    Codes to shard the reified KB is presented in the function
    construct_relation_group().

    Triples are constructed in the same way as construct_relation_group()
    in NeuralQueryContext except that they are stored in multiple sparse
    relation matrices. Relations are also declared normally.

    For example, "rel_g_obj" is the variable for M_obj in Appendix D of
    the paper. "rel_g_obj" is sharded into n sparse matrices
    "rel_g_obj_0", ..., "rel_g_obj_(n-1)", where n is the number of GPUs
    available for computation. The i'th shard "rel_g_obj_i" corresponds
    to M_obj,1 discussed in the paper.

    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.
    """
    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):
      # if this type is already defined the members need to be in the
      # order associated with that type id
      group_members = sorted(
          group_members, key=lambda r: self.get_id(r, group_name))
    else:
      self.extend_type(group_name, group_members)

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

    group = nql.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, gpus=self.gpus)
    self.declare_relation(
        group.subject_rel, group.triple_type, domain_type, gpus=self.gpus)
    self.declare_relation(
        group.object_rel, group.triple_type, range_type, gpus=self.gpus)
    # relation i in this group has num_rows[i] rows
    num_rows = [self._np_initval[r].data.shape[0] for r in group.members]
    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)

    # GPU:0 is reserved for all other computation except for KB inference,
    # e.g. embedding lookup, losses, etc. So the total number of shards equals
    # to num_gpus - 1
    num_shards = self.gpus - 1
    shard_size = int(total_num_rows / num_shards) + 1
    for shard_id in range(num_shards):
      start = shard_id * shard_size
      end = min((shard_id + 1) * shard_size, total_num_rows)
      triple_indices = np.arange(end - start, dtype='int32')
      self._np_initval[self._sharded_rel_name(
          group.relation_rel, shard_id)] = scipy.sparse.coo_matrix(
              (weight_data[start:end],
               (rel_indices[start:end], triple_indices)),
              shape=(len(group.members), end - start),
              dtype='float32')
      self._np_initval[self._sharded_rel_name(
          group.subject_rel, shard_id)] = scipy.sparse.coo_matrix(
              (ones_data[start:end], (subj_indices[start:end], triple_indices)),
              shape=(self.get_max_id(domain_type), end - start),
              dtype='float32')
      self._np_initval[self._sharded_rel_name(
          group.object_rel, shard_id)] = scipy.sparse.coo_matrix(
              (ones_data[start:end], (obj_indices[start:end], triple_indices)),
              shape=(self.get_max_id(range_type), end - start),
              dtype='float32')
    return group
Declare a group of relations.

    Codes to shard the reified KB is presented in the function
    construct_relation_group().

    Triples are constructed in the same way as construct_relation_group()
    in NeuralQueryContext except that they are stored in multiple sparse
    relation matrices. Relations are also declared normally.

    For example, "rel_g_obj" is the variable for M_obj in Appendix D of
    the paper. "rel_g_obj" is sharded into n sparse matrices
    "rel_g_obj_0", ..., "rel_g_obj_(n-1)", where n is the number of GPUs
    available for computation. The i'th shard "rel_g_obj_i" corresponds
    to M_obj,1 discussed in the paper.

    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.
    
language/nql/nql/dist.py