docs: def construct_pdb_to_rosetta_residue_map(self, rosetta_scripts_path, rosetta_database_path = None, extra_command_flags = None, cache_dir = None):
          ''' Uses the features database to create a mapping from Rosetta-numbered residues to PDB ATOM residues.
              Next, the object's rosetta_sequences (a dict of Sequences) element is created.
              Finally, a SequenceMap object is created mapping the Rosetta Sequences to the ATOM Sequences.
  
              The extra_command_flags parameter expects a string e.g. "-ignore_zero_occupancy false".
  
              If cache_dir is passed then the file <self.pdb_id>.
          '''
  
          ## Create a mapping from Rosetta-numbered residues to PDB ATOM residues
  
          import json
  
          # Apply any PDB-specific hacks
          specific_flag_hacks = None
          if self.pdb_id and HACKS_pdb_specific_hacks.get(self.pdb_id):
              specific_flag_hacks = HACKS_pdb_specific_hacks[self.pdb_id]
  
          skeletal_chains = sorted([k for k in self.chain_types.keys() if self.chain_types[k] == 'Protein skeleton'])
          if skeletal_chains:
              raise PDBMissingMainchainAtomsException('The PDB to Rosetta residue map could not be created as chains %s only have CA atoms present.' % ", ".join(skeletal_chains))
  
          # Get the residue mapping using the features database
          mapping = None
          cached_json_mapping_filepath = None
          if cache_dir:
              cached_json_mapping_filepath = os.path.join(cache_dir, '{0}.rosetta2pdb.rawmap.json'.format(self.pdb_id)) # note: the resmap.json file created by self.get_atom_sequence_to_rosetta_json_map is more involved - rawmap is simply what is returned by get_pdb_contents_to_pose_residue_map
          if self.pdb_id and cache_dir and os.path.exists(cached_json_mapping_filepath):
              # Read cached file
              try:
                  mapping = json.loads(read_file(cached_json_mapping_filepath))
              except: pass
          if mapping == None:
              pdb_file_contents = "\n".join(self.structure_lines)
              success, mapping = get_pdb_contents_to_pose_residue_map(pdb_file_contents, rosetta_scripts_path, rosetta_database_path = rosetta_database_path, pdb_id = self.pdb_id, extra_flags = ((specific_flag_hacks or '') + ' ' + (extra_command_flags or '')).strip())
              if not success:
                  raise colortext.Exception("An error occurred mapping the PDB ATOM residue IDs to the Rosetta numbering.\n%s" % "\n".join(mapping))
              if self.pdb_id and cache_dir:
                  write_file(cached_json_mapping_filepath, json.dumps(mapping, indent = 4, sort_keys = True))
  
          ## Create Sequences for the Rosetta residues (self.rosetta_sequences)
  
          # Initialize maps
          rosetta_residues = {}
          rosetta_sequences = {}
          for chain_id in self.atom_chain_order:
              chain_type = self.chain_types[chain_id]
              rosetta_residues[chain_id] = {}
              rosetta_sequences[chain_id] = Sequence(chain_type)
  
          # Create a map rosetta_residues, Chain -> Rosetta residue ID -> Rosetta residue information
          rosetta_pdb_mappings = {}
          for chain_id in self.atom_chain_order:
              rosetta_pdb_mappings[chain_id] = {}
          for k, v in mapping.iteritems():
              rosetta_residues[k[0]][v['pose_residue_id']] = v
              rosetta_pdb_mappings[k[0]][v['pose_residue_id']] = k
  
          # Create rosetta_sequences map Chain -> Sequence(Residue)
          for chain_id, v in sorted(rosetta_residues.iteritems()):
              chain_type = self.chain_types[chain_id]
              for rosetta_id, residue_info in sorted(v.iteritems()):
                  short_residue_type = None
  
                  residue_type = None
                  if chain_type == 'Protein':
                      residue_type = residue_info['name3'].strip()
                      short_residue_type = residue_type_3to1_map.get(residue_type, 'X') # some HETATMs can be passed here e.g. MG so we can not map those cases
                  else:
                      residue_type = residue_info['res_type'].strip()
                      if chain_type == 'DNA':
                          if residue_type.find('UpperDNA') != -1 or residue_type.find('LowerDNA') != -1:
                              residue_type = residue_type[:3]
                          short_residue_type = dna_nucleotides_3to1_map.get(residue_type) # Commenting this out since Rosetta does not seem to handle these "or non_canonical_dna.get(residue_type)"
                      else:
                          assert(chain_type == 'RNA')
                          if residue_type.find('UpperRNA') != -1 or residue_type.find('LowerRNA') != -1 or (len(residue_type) > 3 and residue_type[3] == ':'):
                              residue_type = residue_type[:3]
                          short_residue_type = rna_nucleotides_3to1_map.get(residue_type)
  
                  if short_residue_type == None:
                      raise colortext.Exception('Could not determine the one-letter code of the residue: chain {0}, chain_type "{1}", residue "{2}", residue type "{3}".'.format(chain_id, chain_type, rosetta_id, residue_type))
  
                  rosetta_sequences[chain_id].add(Residue(chain_id, rosetta_id, short_residue_type, chain_type))
  
  
          ## Create SequenceMap objects to map the Rosetta Sequences to the ATOM Sequences
          rosetta_to_atom_sequence_maps = {}
          for chain_id, rosetta_pdb_mapping in rosetta_pdb_mappings.iteritems():
              rosetta_to_atom_sequence_maps[chain_id] = SequenceMap.from_dict(rosetta_pdb_mapping)
  
          self.rosetta_to_atom_sequence_maps = rosetta_to_atom_sequence_maps
          self.rosetta_sequences = rosetta_sequences