docs: def _align_with_substrings(self, chains_to_skip = set()):
          '''Simple substring-based matching'''
          for c in self.representative_chains:
              # Skip specified chains
              if c not in chains_to_skip:
                  #colortext.pcyan(c)
                  #colortext.warning(self.fasta[c])
                  fasta_sequence = self.fasta[c]
  
                  substring_matches = {}
  
                  for uniparc_id, uniparc_sequence in sorted(self.uniparc_sequences.iteritems()):
                      uniparc_sequence = str(uniparc_sequence)
                      idx = uniparc_sequence.find(fasta_sequence)
                      if idx != -1:
                          substring_matches[uniparc_id] = 0
                      elif len(fasta_sequence) > 30:
                          idx = uniparc_sequence.find(fasta_sequence[5:-5])
                          if idx != -1:
                              substring_matches[uniparc_id] = 5
                          else:
                              idx = uniparc_sequence.find(fasta_sequence[7:-7])
                              if idx != -1:
                                  substring_matches[uniparc_id] = 7
                      elif len(fasta_sequence) > 15:
                          idx = uniparc_sequence.find(fasta_sequence[3:-3])
                          if idx != -1:
                              substring_matches[uniparc_id] = 3
  
                  self.substring_matches[c] = substring_matches
  
          # Restrict the matches to a given set of UniParc IDs. This can be used to remove ambiguity when the correct mapping has been determined e.g. from the SIFTS database.
          colortext.pcyan('*' * 100)
          pprint.pprint(self.substring_matches)
          if self.restrict_to_uniparc_values:
              for c in self.representative_chains:
                  #print('HERE!')
                  #print(c)
                  if set(map(str, self.substring_matches[c].keys())).intersection(set(self.restrict_to_uniparc_values)) > 0:
                      # Only restrict in cases where there is at least one match in self.restrict_to_uniparc_values
                      # Otherwise, chains which are not considered in self.restrict_to_uniparc_values may throw away valid matches
                      # e.g. when looking for structures related to 1KTZ (A -> P10600 -> UPI000000D8EC, B -> P37173 -> UPI000011DD7E),
                      #      we find the close match 2PJY. However, 2PJY has 3 chains: A -> P10600, B -> P37173, and C -> P36897 -> UPI000011D62A
                      restricted_matches = dict((str(k), self.substring_matches[c][k]) for k in self.substring_matches[c].keys() if str(k) in self.restrict_to_uniparc_values)
                      if len(restricted_matches) != len(self.substring_matches[c]):
                          removed_matches = sorted(set(self.substring_matches[c].keys()).difference(set(restricted_matches)))
                          # todo: see above re:quiet colortext.pcyan('Ignoring {0} as those chains were not included in the list self.restrict_to_uniparc_values ({1}).'.format(', '.join(removed_matches), ', '.join(self.restrict_to_uniparc_values)))
                      self.substring_matches[c] = restricted_matches
          #pprint.pprint(self.substring_matches)
          #colortext.pcyan('*' * 100)
  
          # Use the representatives' alignments for their respective equivalent classes
          for c_1, related_chains in self.equivalence_fiber.iteritems():
              for c_2 in related_chains:
                  self.substring_matches[c_2] = self.substring_matches[c_1]