<commit_before>/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 * Created on Jun 12, 2010
 * Author: <NAME> 
 *
 */

package org.biojava3.protmod.structure;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.biojava.bio.structure.Atom;
import org.biojava.bio.structure.Chain;
import org.biojava.bio.structure.Group;
import org.biojava.bio.structure.GroupType;
import org.biojava.bio.structure.ResidueNumber;
import org.biojava.bio.structure.Structure;
import org.biojava.bio.structure.StructureException;


import org.biojava3.protmod.Component;
import org.biojava3.protmod.ComponentType;
import org.biojava3.protmod.ModificationCategory;
import org.biojava3.protmod.ModificationCondition;
import org.biojava3.protmod.ModificationLinkage;
import org.biojava3.protmod.ProteinModification;
import org.biojava3.protmod.ProteinModificationRegistry;

/**
 * Identify attachment modification in a 3-D structure.
 * 
 * @author <NAME>ao
 * @since 3.0
 */
public class ProteinModificationIdentifier {
	
	private double bondLengthTolerance = 0.4;
	private boolean recordUnidentifiableModifiedCompounds = false;
	private boolean recordAdditionalAttachments = true;
	
	private Set<ModifiedCompound> identifiedModifiedCompounds = null;
	private Set<StructureAtomLinkage> unidentifiableAtomLinkages = null;
	private Set<StructureGroup> unidentifiableModifiedResidues = null;
	
	/**
	 * 
	 * @param bondLengthTolerance tolerance of error (in Angstroms) of the
	 *  covalent bond length, when calculating the atom distance threshold.
	 */
	public void setbondLengthTolerance(final double bondLengthTolerance) {
		if (bondLengthTolerance<0) {
			throw new IllegalArgumentException("bondLengthTolerance " +
					"must be positive.");
		}
		this.bondLengthTolerance = bondLengthTolerance;
	}
	
	/**
	 * 
	 * @param recordUnidentifiableAtomLinkages true if choosing to record unidentifiable
	 *  atoms; false, otherwise.
	 * @see #getRecordUnidentifiableCompounds
	 * @see #getUnidentifiableModifiedResidues
	 * @see #getUnidentifiableAtomLinkages
	 */
	public void setRecordUnidentifiableCompounds(boolean recordUnidentifiableModifiedCompounds) {
		this.recordUnidentifiableModifiedCompounds = recordUnidentifiableModifiedCompounds;
	}
	
	/**
	 * 
	 * @return true if choosing to record unidentifiable
	 *  atoms; false, otherwise.
	 * @see #setRecordUnidentifiableCompounds
	 * @see #getUnidentifiableModifiedResidues
	 * @see #getUnidentifiableAtomLinkages
	 */
	public boolean getRecordUnidentifiableCompounds() {
		return recordUnidentifiableModifiedCompounds;
	}
	
	/**
	 * 
	 * @param recordAdditionalAttachments true if choosing to record additional attachments
	 *  that are not directly attached to a modified residue.
	 * @see #getRecordAdditionalAttachments
	 */
	public void setRecordAdditionalAttachments(boolean recordAdditionalAttachments) {
		this.recordAdditionalAttachments = recordAdditionalAttachments;
	}
	
	/**
	 * 
	 * @return true if choosing to record additional attachments
	 *  that are not directly attached to a modified residue.
	 * @see #setRecordAdditionalAttachments
	 */
	public boolean getRecordAdditionalAttachments() {
		return recordAdditionalAttachments;
	}
	
	/**
	 * 
	 * @return a set of identified {@link ModifiedCompound}s from
	 *  the last parse result.
	 * @see ModifiedCompound
	 */
	public Set<ModifiedCompound> getIdentifiedModifiedCompound() {
		if (identifiedModifiedCompounds==null) {
			throw new IllegalStateException("No result available. Please call parse() first.");
		}
		
		return identifiedModifiedCompounds;
	}
	
	/**
	 * 
	 * @return a set of atom linkages, which represent the 
	 *  atom bonds that were not covered by the identified 
	 *  {@link ModifiedCompound}s from the last parse result.
	 *  Each element of the list is a array containing two atoms.
	 * @see StructureAtomLinkage
	 * @see #setRecordUnidentifiableCompounds
	 */
	public Set<StructureAtomLinkage> getUnidentifiableAtomLinkages() {
		if (!recordUnidentifiableModifiedCompounds) {
			throw new UnsupportedOperationException("Recording unidentified atom linkages" +
					"is not supported. Please setRecordUnidentifiableCompounds(true) first.");
		}
		
		if (identifiedModifiedCompounds==null) {
			throw new IllegalStateException("No result available. Please call parse() first.");
		}
		
		return unidentifiableAtomLinkages;
	}
	
	/**
	 * 
	 * @return a set of modified residues that were not covered by
	 *  the identified ModifiedCompounds from the last parse 
	 *  result.
	 *  @see StructureGroup
	 *  @see #setRecordUnidentifiableCompounds
	 *  @see #getIdentifiedModifiedCompound
	 */
	public Set<StructureGroup> getUnidentifiableModifiedResidues() {
		if (!recordUnidentifiableModifiedCompounds) {
			throw new UnsupportedOperationException("Recording unidentified atom linkages" +
					"is not supported. Please setRecordUnidentifiableCompounds(true) first.");
		}
		
		if (identifiedModifiedCompounds==null) {
			throw new IllegalStateException("No result available. Please call parse() first.");
		}
		
		return unidentifiableModifiedResidues;
	}
	
	/**
	 * Identify all registered modifications in a structure.
	 * @param structure
	 */
	public void identify(final Structure structure) {
		identify(structure, ProteinModificationRegistry.allModifications());
	}

	/**
	 * Identify a set of modifications in a structure.
	 * @param structure query {@link Structure}.
	 * @param potentialModifications query {@link ProteinModification}s.
	 */
	public void identify(final Structure structure,
			final Set<ProteinModification> potentialModifications) {
		if (structure==null) {
			throw new IllegalArgumentException("Null structure.");
		}
		
		identify(structure.getChains(), potentialModifications);
	}
	
	/**
	 * Identify all registered modifications in a chain. 
	 * @param chain query {@link Chain}.
	 */
	public void identify(final Chain chain) {
		identify(Collections.singletonList(chain));
	}
	
	/**
	 * Identify all registered modifications in chains. 
	 * @param chains query {@link Chain}s.
	 */
	public void identify(final List<Chain> chains) {
		identify(chains, ProteinModificationRegistry.allModifications());
	}
	
	/**
	 * Identify a set of modifications in a a chains.
	 * @param chain query {@link Chain}.
	 * @param potentialModifications query {@link ProteinModification}s.
	 */
	public void identify(final Chain chain,
			final Set<ProteinModification> potentialModifications)  {
		identify(Collections.singletonList(chain), potentialModifications);
	}
	
	/**
	 * Identify a set of modifications in a a list of chains.
	 * @param chains query {@link Chain}s.
	 * @param potentialModifications query {@link ProteinModification}s.
	 */
	public void identify(final List<Chain> chains,
			final Set<ProteinModification> potentialModifications) {
		
		if (chains==null) {
			throw new IllegalArgumentException("Null structure.");
		}
		
		if (potentialModifications==null) {
			throw new IllegalArgumentException("Null potentialModifications.");
		}
		
			
		identifiedModifiedCompounds = new LinkedHashSet<ModifiedCompound>();
		if (recordUnidentifiableModifiedCompounds) {
			unidentifiableAtomLinkages = new LinkedHashSet<StructureAtomLinkage>();
			unidentifiableModifiedResidues = new LinkedHashSet<StructureGroup>();
		}
		
		if (potentialModifications.isEmpty()) {
			return;
		}
		
		Map<String, Chain> mapChainIdChain = new HashMap<String, Chain>(chains.size());
		List<Group> residues = new ArrayList<Group>();
		List<Group> ligands = new ArrayList<Group>();
		Map<Component, Set<Group>> mapCompGroups = new HashMap<Component, Set<Group>>();
		
		for (Chain chain : chains) {
			mapChainIdChain.put(chain.getChainID(), chain);
			List<Group> ress = StructureUtil.getAminoAcids(chain);
			List<Group> ligs = chain.getAtomLigands();
			residues.addAll(ress);
			ligands.addAll(ligs);
			addModificationGroups(potentialModifications, ress, ligs, mapCompGroups);
		}
		
		if (residues.isEmpty())
			System.err.println("WARNING: no amino acids found. Did you parse PDB file with alignSEQRES records?");
		
		List<ModifiedCompound> modComps = new ArrayList<ModifiedCompound>();
		
		for (ProteinModification mod : potentialModifications) {
			ModificationCondition condition = mod.getCondition();
			List<Component> components = condition.getComponents();
			if (!mapCompGroups.keySet().containsAll(components)) {
				// not all components exist for this mod.
				continue;
			}
			
			int sizeComps = components.size();
			if (sizeComps==1) {
				// modified residue
				// TODO: is this the correct logic for CROSS_LINK_1?
				Set<Group> modifiedResidues = mapCompGroups.get(components.get(0));
				if (modifiedResidues != null) {
					for (Group residue : modifiedResidues) {
						StructureGroup strucGroup = StructureUtil.getStructureGroup(residue, ComponentType.AMINOACID);
						ModifiedCompound modRes = new ModifiedCompoundImpl(mod, strucGroup);
						modComps.add(modRes);
					}
				}
			} else {
				// for multiple components
				
				// find linkages first
				List<List<Atom[]>> matchedAtomsOfLinkages =
						getMatchedAtomsOfLinkages(condition, mapCompGroups);
				
				if (matchedAtomsOfLinkages.size() != condition.getLinkages().size()) {
					continue;
				}
				
				assembleLinkages(matchedAtomsOfLinkages, mod, modComps);
			}
		}

		if (recordAdditionalAttachments) {
			// identify additional groups that are not directly attached to amino acids.
			for (ModifiedCompound mc : modComps) {
				identifyAdditionalAttachments(mc, ligands, mapChainIdChain);
			}
		}
		
		mergeModComps(modComps);
		
		identifiedModifiedCompounds.addAll(modComps);
		
		
		// record unidentifiable linkage
		if (recordUnidentifiableModifiedCompounds) {
			recordUnidentifiableAtomLinkages(modComps, residues, ligands);
			recordUnidentifiableModifiedResidues(modComps, residues);
		}
	}
	
	/**
	 * identify additional groups that are not directly attached to amino acids.
	 * @param mc {@link ModifiedCompound}.
	 * @param chain a {@link Chain}.
	 * @return a list of added groups.
	 */
	private void identifyAdditionalAttachments(ModifiedCompound mc, 
			List<Group> ligands, Map<String, Chain> mapChainIdChain) {
		if (ligands.isEmpty()) {
			return;
		}
		
		// TODO: should the additional groups only be allowed to the identified 
		// ligands or both amino acids and ligands? Currently only on ligands
		// ligands to amino acid bonds for same modification of unknown category
		// will be combined in mergeModComps()
		// TODO: how about chain-chain links?
		List<Group> identifiedGroups = new ArrayList<Group>();
		for (StructureGroup num : mc.getGroups(ComponentType.LIGAND)) {
			Group group;
			try {
				//String numIns = "" + num.getResidueNumber();
				//if (num.getInsCode() != null) {
				//	numIns += num.getInsCode();
				//}
				ResidueNumber resNum = new ResidueNumber();
				resNum.setChainId(num.getChainId());
				resNum.setSeqNum(num.getResidueNumber());
				resNum.setInsCode(num.getInsCode());
				//group = chain.getGroupByPDB(numIns);
				group = mapChainIdChain.get(num.getChainId()).getGroupByPDB(resNum);
			} catch (StructureException e) {
				// should not happen
				continue;
			}<commit_msg>more transparent warning message<commit_after>/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 * Created on Jun 12, 2010
 * Author: <NAME> 
 *
 */

package org.biojava3.protmod.structure;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.biojava.bio.structure.Atom;
import org.biojava.bio.structure.Chain;
import org.biojava.bio.structure.Group;
import org.biojava.bio.structure.GroupType;
import org.biojava.bio.structure.ResidueNumber;
import org.biojava.bio.structure.Structure;
import org.biojava.bio.structure.StructureException;


import org.biojava3.protmod.Component;
import org.biojava3.protmod.ComponentType;
import org.biojava3.protmod.ModificationCategory;
import org.biojava3.protmod.ModificationCondition;
import org.biojava3.protmod.ModificationLinkage;
import org.biojava3.protmod.ProteinModification;
import org.biojava3.protmod.ProteinModificationRegistry;

/**
 * Identify attachment modification in a 3-D structure.
 * 
 * @author <NAME>
 * @since 3.0
 */
public class ProteinModificationIdentifier {
	
	private double bondLengthTolerance = 0.4;
	private boolean recordUnidentifiableModifiedCompounds = false;
	private boolean recordAdditionalAttachments = true;
	
	private Set<ModifiedCompound> identifiedModifiedCompounds = null;
	private Set<StructureAtomLinkage> unidentifiableAtomLinkages = null;
	private Set<StructureGroup> unidentifiableModifiedResidues = null;
	
	/**
	 * 
	 * @param bondLengthTolerance tolerance of error (in Angstroms) of the
	 *  covalent bond length, when calculating the atom distance threshold.
	 */
	public void setbondLengthTolerance(final double bondLengthTolerance) {
		if (bondLengthTolerance<0) {
			throw new IllegalArgumentException("bondLengthTolerance " +
					"must be positive.");
		}
		this.bondLengthTolerance = bondLengthTolerance;
	}
	
	/**
	 * 
	 * @param recordUnidentifiableAtomLinkages true if choosing to record unidentifiable
	 *  atoms; false, otherwise.
	 * @see #getRecordUnidentifiableCompounds
	 * @see #getUnidentifiableModifiedResidues
	 * @see #getUnidentifiableAtomLinkages
	 */
	public void setRecordUnidentifiableCompounds(boolean recordUnidentifiableModifiedCompounds) {
		this.recordUnidentifiableModifiedCompounds = recordUnidentifiableModifiedCompounds;
	}
	
	/**
	 * 
	 * @return true if choosing to record unidentifiable
	 *  atoms; false, otherwise.
	 * @see #setRecordUnidentifiableCompounds
	 * @see #getUnidentifiableModifiedResidues
	 * @see #getUnidentifiableAtomLinkages
	 */
	public boolean getRecordUnidentifiableCompounds() {
		return recordUnidentifiableModifiedCompounds;
	}
	
	/**
	 * 
	 * @param recordAdditionalAttachments true if choosing to record additional attachments
	 *  that are not directly attached to a modified residue.
	 * @see #getRecordAdditionalAttachments
	 */
	public void setRecordAdditionalAttachments(boolean recordAdditionalAttachments) {
		this.recordAdditionalAttachments = recordAdditionalAttachments;
	}
	
	/**
	 * 
	 * @return true if choosing to record additional attachments
	 *  that are not directly attached to a modified residue.
	 * @see #setRecordAdditionalAttachments
	 */
	public boolean getRecordAdditionalAttachments() {
		return recordAdditionalAttachments;
	}
	
	/**
	 * 
	 * @return a set of identified {@link ModifiedCompound}s from
	 *  the last parse result.
	 * @see ModifiedCompound
	 */
	public Set<ModifiedCompound> getIdentifiedModifiedCompound() {
		if (identifiedModifiedCompounds==null) {
			throw new IllegalStateException("No result available. Please call parse() first.");
		}
		
		return identifiedModifiedCompounds;
	}
	
	/**
	 * 
	 * @return a set of atom linkages, which represent the 
	 *  atom bonds that were not covered by the identified 
	 *  {@link ModifiedCompound}s from the last parse result.
	 *  Each element of the list is a array containing two atoms.
	 * @see StructureAtomLinkage
	 * @see #setRecordUnidentifiableCompounds
	 */
	public Set<StructureAtomLinkage> getUnidentifiableAtomLinkages() {
		if (!recordUnidentifiableModifiedCompounds) {
			throw new UnsupportedOperationException("Recording unidentified atom linkages" +
					"is not supported. Please setRecordUnidentifiableCompounds(true) first.");
		}
		
		if (identifiedModifiedCompounds==null) {
			throw new IllegalStateException("No result available. Please call parse() first.");
		}
		
		return unidentifiableAtomLinkages;
	}
	
	/**
	 * 
	 * @return a set of modified residues that were not covered by
	 *  the identified ModifiedCompounds from the last parse 
	 *  result.
	 *  @see StructureGroup
	 *  @see #setRecordUnidentifiableCompounds
	 *  @see #getIdentifiedModifiedCompound
	 */
	public Set<StructureGroup> getUnidentifiableModifiedResidues() {
		if (!recordUnidentifiableModifiedCompounds) {
			throw new UnsupportedOperationException("Recording unidentified atom linkages" +
					"is not supported. Please setRecordUnidentifiableCompounds(true) first.");
		}
		
		if (identifiedModifiedCompounds==null) {
			throw new IllegalStateException("No result available. Please call parse() first.");
		}
		
		return unidentifiableModifiedResidues;
	}
	
	/**
	 * Identify all registered modifications in a structure.
	 * @param structure
	 */
	public void identify(final Structure structure) {
		identify(structure, ProteinModificationRegistry.allModifications());
	}

	/**
	 * Identify a set of modifications in a structure.
	 * @param structure query {@link Structure}.
	 * @param potentialModifications query {@link ProteinModification}s.
	 */
	public void identify(final Structure structure,
			final Set<ProteinModification> potentialModifications) {
		if (structure==null) {
			throw new IllegalArgumentException("Null structure.");
		}
		
		identify(structure.getChains(), potentialModifications);
	}
	
	/**
	 * Identify all registered modifications in a chain. 
	 * @param chain query {@link Chain}.
	 */
	public void identify(final Chain chain) {
		identify(Collections.singletonList(chain));
	}
	
	/**
	 * Identify all registered modifications in chains. 
	 * @param chains query {@link Chain}s.
	 */
	public void identify(final List<Chain> chains) {
		identify(chains, ProteinModificationRegistry.allModifications());
	}
	
	/**
	 * Identify a set of modifications in a a chains.
	 * @param chain query {@link Chain}.
	 * @param potentialModifications query {@link ProteinModification}s.
	 */
	public void identify(final Chain chain,
			final Set<ProteinModification> potentialModifications)  {
		identify(Collections.singletonList(chain), potentialModifications);
	}
	
	/**
	 * Identify a set of modifications in a a list of chains.
	 * @param chains query {@link Chain}s.
	 * @param potentialModifications query {@link ProteinModification}s.
	 */
	public void identify(final List<Chain> chains,
			final Set<ProteinModification> potentialModifications) {
		
		if (chains==null) {
			throw new IllegalArgumentException("Null structure.");
		}
		
		if (potentialModifications==null) {
			throw new IllegalArgumentException("Null potentialModifications.");
		}
		
			
		identifiedModifiedCompounds = new LinkedHashSet<ModifiedCompound>();
		if (recordUnidentifiableModifiedCompounds) {
			unidentifiableAtomLinkages = new LinkedHashSet<StructureAtomLinkage>();
			unidentifiableModifiedResidues = new LinkedHashSet<StructureGroup>();
		}
		
		if (potentialModifications.isEmpty()) {
			return;
		}
		
		Map<String, Chain> mapChainIdChain = new HashMap<String, Chain>(chains.size());
		List<Group> residues = new ArrayList<Group>();
		List<Group> ligands = new ArrayList<Group>();
		Map<Component, Set<Group>> mapCompGroups = new HashMap<Component, Set<Group>>();
		
		for (Chain chain : chains) {
			mapChainIdChain.put(chain.getChainID(), chain);
			List<Group> ress = StructureUtil.getAminoAcids(chain);
			List<Group> ligs = chain.getAtomLigands();
			residues.addAll(ress);
			ligands.addAll(ligs);
			addModificationGroups(potentialModifications, ress, ligs, mapCompGroups);
		}
		
		if (residues.isEmpty()) {
			String pdbId = "?";
			if ( chains.size() > 0) {
				Structure struc = chains.get(0).getParent();
				if ( struc != null)
					pdbId = struc.getPDBCode(); 
			}
			System.err.println("WARNING: no amino acids found for "+ pdbId + ". Either you did not parse the PDB file with alignSEQRES records, or this record does not contain any amino acids.");
		}
		List<ModifiedCompound> modComps = new ArrayList<ModifiedCompound>();
		
		for (ProteinModification mod : potentialModifications) {
			ModificationCondition condition = mod.getCondition();
			List<Component> components = condition.getComponents();
			if (!mapCompGroups.keySet().containsAll(components)) {
				// not all components exist for this mod.
				continue;
			}
			
			int sizeComps = components.size();
			if (sizeComps==1) {
				
				processCrosslink1(mapCompGroups, modComps, mod, components);
			
			} else {
				
				processMultiCrosslink(mapCompGroups, modComps, mod, condition);
			}
		}

		if (recordAdditionalAttachments) {
			// identify additional groups that are not directly attached to amino acids.
			for (ModifiedCompound mc : modComps) {
				identifyAdditionalAttachments(mc, ligands, mapChainIdChain);
			}
		}
		
		mergeModComps(modComps);
		
		identifiedModifiedCompounds.addAll(modComps);
		
		
		// record unidentifiable linkage
		if (recordUnidentifiableModifiedCompounds) {
			recordUnidentifiableAtomLinkages(modComps, residues, ligands);
			recordUnidentifiableModifiedResidues(modComps, residues);
		}
	}

	private void processMultiCrosslink(
			Map<Component, Set<Group>> mapCompGroups,
			List<ModifiedCompound> modComps, ProteinModification mod,
			ModificationCondition condition) {
		// for multiple components
		
		// find linkages first
		List<List<Atom[]>> matchedAtomsOfLinkages =
				getMatchedAtomsOfLinkages(condition, mapCompGroups);
		
		if (matchedAtomsOfLinkages.size() != condition.getLinkages().size()) {
			return;
		} 
		
		assembleLinkages(matchedAtomsOfLinkages, mod, modComps);
		
	}

	private void processCrosslink1(Map<Component, Set<Group>> mapCompGroups,
			List<ModifiedCompound> modComps, ProteinModification mod,
			List<Component> components) {
		// modified residue
		// TODO: is this the correct logic for CROSS_LINK_1?
		Set<Group> modifiedResidues = mapCompGroups.get(components.get(0));
		if (modifiedResidues != null) {
			for (Group residue : modifiedResidues) {
				StructureGroup strucGroup = StructureUtil.getStructureGroup(residue, ComponentType.AMINOACID);
				ModifiedCompound modRes = new ModifiedCompoundImpl(mod, strucGroup);
				modComps.add(modRes);
			}
		}
	}
	
	/**
	 * identify additional groups that are not directly attached to amino acids.
	 * @param mc {@link ModifiedCompound}.
	 * @param chain a {@link Chain}.
	 * @return a list of added groups.
	 */
	private void identifyAdditionalAttachments(ModifiedCompound mc, 
			List<Group> ligands, Map<String, Chain> mapChainIdChain) {
		if (ligands.isEmpty()) {
			return;
		}
		
		// TODO: should the additional groups only be allowed to the identified 
		// ligands or both amino acids and ligands? Currently only on ligands
		// ligands to amino acid bonds for same modification of unknown category
		// will be combined in mergeModComps()
		// TODO: how about chain-chain links?
		List<Group> identifiedGroups = new ArrayList<Group>();
		for (StructureGroup num : mc.getGroups(ComponentType.LIGAND)) {
			Group group;
			try {
				//String numIns = "" + num.getResidueNumber();
				//if (num.getInsCode() != null) {
				//	numIns += num.getInsCode();
				//}
				ResidueNumber resNum = new ResidueNumber();
				resNum.setChainId(num.getChainId());
				resNum.setSeqNum(num.getResidueNumber());
				resNum.setInsCode(num.getInsCode());
				//group = chain.getGroupByPDB(numIns);
				group = mapChainIdChain.get(num.getChainId()).getGroupByPDB(resNum);
			} catch (StructureException e) {
				e.printStackTrace();
				// should not happen
				continue;
			}
{'fraction_non_alphanumeric': 0.09489613761765661, 'fraction_numerical': 0.0020285621551444336, 'mean_word_length': 4.352660152008686, 'pattern_counts': {'":': 0, '<': 88, '<?xml version=': 0, '>': 87, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5111950', 'n_tokens_mistral': 8012, 'n_tokens_neox': 6840, 'n_words': 1876}