All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmolecules.chem.conf.gen.RotatableBond Maven / Gradle / Ivy

There is a newer version: 2024.12.1
Show newest version
/*
 * Copyright 2013-2020 Thomas Sander, openmolecules.org
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Thomas Sander
 */

package org.openmolecules.chem.conf.gen;

import com.actelion.research.chem.Coordinates;
import com.actelion.research.chem.Molecule;
import com.actelion.research.chem.StereoMolecule;
import com.actelion.research.chem.conf.*;

import java.util.Random;

/**
 * A RotatableBond knows the two rigid fragments within a molecule
 * that are connected by this bond. It also knows about possible torsion
 * states with associated likelyhoods, which are taken from COD statistics
 * and modified to account for collisions due to bulky groups in the molecule.
 * It knows the smaller half of the molecule and rotate the smaller half to
 * a given torsion angle.
 */
public class RotatableBond {
	private static final double ANGLE_TOLERANCE = 0.001f;	// limit for considering bonds as parallel

	// For every optimum torsion we check for collisions and in case we try, whether left or right
	// range ends are substantially better.
	private static final double ACCEPTABLE_CENTER_STRAIN = 0.05f;	// if we have less strain than this we don't check whether range edges improve strain
	private static final double NECESSARY_EDGE_STRAIN_IMPROVEMENT = 0.02f;	// necessary strain improvement to use edge torsion rather than center torsion
	private static final double MAXIMUM_CENTER_STRAIN = 0.2f;		// if center torsion is above this limit we refuse that torsion

	// If no acceptable torsion remains after checking and attempting to use range edge values,
	// we try to modify the least bad torsion stepwise until it is acceptable.
	private static final int ESCAPE_ANGLE = 8;  // degrees to rotate the rotatable bond to escape collisions
	private static final int ESCAPE_STEPS = 4;	// how often we apply this rotation trying to solve the collision
	private static final double MIN_ESCAPE_GAIN_PER_STEP = 0.05;

	private static final short[] SIXTY_DEGREE_TORSION = { 0, 60, 120, 180, 240, 300};
	private static final short[] SIXTY_DEGREE_FREQUENCY = { 17, 17, 17, 17, 17, 17};
	private static final short[][] SIXTY_DEGREE_RANGE = { {-20,20},{40,80},{100,140},{160,200},{220,260},{280,320}};

	private RigidFragment mFragment1,mFragment2;
	private Random mRandom;
	private int mRotationCenter,mBond,mFragmentNo1,mFragmentNo2;
	private boolean mBondAtomsInFragmentOrder;
	private float mBondRelevance;
	private short[] mTorsion;
	private short[] mFrequency;
	private short[][] mTorsionRange;
	private double[] mLikelyhood; // considering directly connected rigid fragments (frequency and collision strain)
	private int[] mTorsionAtom,mRearAtom,mSmallerSideAtomList;

	public RotatableBond(StereoMolecule mol, int bond, int[] fragmentNo, int[] disconnectedFragmentNo,
	                     int disconnectedFragmentSize, RigidFragment[] fragment, Random random) {
		this(mol, bond, fragmentNo, disconnectedFragmentNo, disconnectedFragmentSize, fragment, random, false);
		}

	public RotatableBond(StereoMolecule mol, int bond, int[] fragmentNo, int[] disconnectedFragmentNo,
	                     int disconnectedFragmentSize, RigidFragment[] fragment, Random random, boolean use60degreeSteps) {
		mBond = bond;
		mRandom = random;
		mTorsionAtom = new int[4];
		mRearAtom = new int[2];
		TorsionDetail detail = new TorsionDetail();
		if (TorsionDB.getTorsionID(mol, bond, mTorsionAtom, detail) != null) {
			mRearAtom[0] = detail.getRearAtom(0);
			mRearAtom[1] = detail.getRearAtom(1);
			}
		else {
			predictAtomSequence(mol);
			}

		mFragmentNo1 = fragmentNo[mTorsionAtom[1]];
		mFragmentNo2 = fragmentNo[mTorsionAtom[2]];
		mFragment1 = fragment[mFragmentNo1];
		mFragment2 = fragment[mFragmentNo2];

		mBondAtomsInFragmentOrder = (fragmentNo[mol.getBondAtom(0, bond)] == mFragmentNo1);

		if (use60degreeSteps) {
			mTorsion = SIXTY_DEGREE_TORSION;
			mFrequency = SIXTY_DEGREE_FREQUENCY;
			mTorsionRange = SIXTY_DEGREE_RANGE;
			}
		else {
			mTorsion = TorsionDB.getTorsions(detail.getID());
			if (mTorsion == null) {
				TorsionPrediction prediction = new TorsionPrediction(mol, mTorsionAtom);
				mTorsion = prediction.getTorsions();
				mFrequency = prediction.getTorsionFrequencies();
				mTorsionRange = prediction.getTorsionRanges();
			} else {
				mFrequency = TorsionDB.getTorsionFrequencies(detail.getID());
				mTorsionRange = TorsionDB.getTorsionRanges(detail.getID());
			}
		}

		removeIllegalTorsions(mol);
		removeEquivalentTorsions(mol);
		mLikelyhood = new double[mTorsion.length];

		findSmallerSideAtomList(mol, disconnectedFragmentNo, disconnectedFragmentSize);
		}

	public RigidFragment getFragment(int i) {
		return (i == 0) ? mFragment1 : mFragment2;
		}

	public int getFragmentNo(int i) {
		return (i == 0) ? mFragmentNo1 : mFragmentNo2;
		}

	private void predictAtomSequence(StereoMolecule mol) {
        for (int i=0; i<2; i++) {
    		int centralAtom = mol.getBondAtom(i, mBond);
        	int rearAtom = mol.getBondAtom(1-i, mBond);

        	// walk along sp-chains to first sp2 or sp3 atom
        	while (mol.getAtomPi(centralAtom) == 2
        		&& mol.getConnAtoms(centralAtom) == 2
        		&& mol.getAtomicNo(centralAtom) < 10) {
        		for (int j=0; j<2; j++) {
        			int connAtom = mol.getConnAtom(centralAtom, j);
        			if (connAtom != rearAtom) {
        				rearAtom = centralAtom;
        				centralAtom = connAtom;
        				break;
        				}
        			}
        		}

        	mTorsionAtom[i+1] = centralAtom;
           	mRearAtom[i] = rearAtom;
        	}

    	// A TorsionPrediction does not distinguish hetero atoms from carbons a positions 0 and 3.
        // Therefore we can treat two sp2 neighbors as equivalent when predicting torsions.
        if (mol.getAtomPi(mTorsionAtom[1]) == 0 && mol.getConnAtoms(mTorsionAtom[1]) == 3) {
			mTorsionAtom[0] = -1;
        	}
        else {
			for (int i=0; i disconnectedFragmentSize-alkyneAtoms-memberCount) {
			memberCount = disconnectedFragmentSize-alkyneAtoms-memberCount;
			invert = true;
			}

		// if invert, then flag all linear alkyne atoms to be avoided
		if (invert && alkyneAtoms != 0) {
			int spAtom = mRearAtom[0];
			int backAtom = mTorsionAtom[1];
        	while (mol.getAtomPi(spAtom) == 2
           		&& mol.getConnAtoms(spAtom) == 2
           		&& mol.getAtomicNo(spAtom) < 10) {
        		isMember[spAtom] = true;
           		for (int j=0; j<2; j++) {
           			int connAtom = mol.getConnAtom(spAtom, j);
           			if (connAtom != backAtom) {
           				backAtom = spAtom;
           				spAtom = connAtom;
           				break;
           				}
           			}
           		}
			}

		int memberNo = 0;
		int fragmentNo = disconnectedFragmentNo[mTorsionAtom[1]];
		mSmallerSideAtomList = new int[memberCount];
		for (int atom=0; atom mFragment2.getCoreSize()) ? mFragment1 : mFragment2;
            int largerFragmentNo = (mFragment1.getCoreSize() > mFragment2.getCoreSize()) ? mFragmentNo1 : mFragmentNo2;
			isAttached[largerFragmentNo] = true;
			int fragmentConformer = (fragmentPermutation == null) ? 0 : fragmentPermutation[largerFragmentNo];
			for (int i=0; i Math.PI / 2) ?
							froot.subC(fragment.getExtendedCoordinates(fragmentConformer, i))
						  : fragment.getExtendedCoordinates(fragmentConformer, i).subC(froot);
				}
			}
		else {
			Coordinates rotationAxis;
			if (alpha < Math.PI - ANGLE_TOLERANCE) {    // normal case, just rotate around the plane orthogonal
				rotationAxis = uv.cross(fuv);
				}
			else {    // special case both axes anti-parallel: for cross-product we need any vector being different from uv
				if (Math.abs(uv.x) >= Math.abs(uv.y) && Math.abs(uv.x) >= Math.abs(uv.z))
					rotationAxis = new Coordinates(-(uv.y + uv.z) / uv.x, 1.0, 1.0);
				else if (Math.abs(uv.y) >= Math.abs(uv.x) && Math.abs(uv.y) >= Math.abs(uv.z))
					rotationAxis = new Coordinates(1.0, -(uv.x + uv.z) / uv.y, 1.0);
				else
					rotationAxis = new Coordinates(1.0, 1.0, -(uv.x + uv.y) / uv.z);
				}

			double[][] m = getRotationMatrix(rotationAxis.unit(), alpha);
	
			for (int i=0; i 1)
								 || (mFragment2.getConformerCount() > 1);

//System.out.print("connectFragments() original torsions:"); for (int t=0; t NECESSARY_EDGE_STRAIN_IMPROVEMENT) {
						if (isFirstAlternative) {
							mTorsion[t] = mTorsionRange[t][r];
							mFrequency[t] = (short)((mFrequency[t]+1) / 2);
							double relativeStrain = rangeStrain / MAXIMUM_CENTER_STRAIN;
							mLikelyhood[t] = mFrequency[t] * (1f - relativeStrain * relativeStrain);
							usedStrain = rangeStrain;
							torsionEdgeUsed = r+1;
							isFirstAlternative = false;
							}
						else {
							double relativeStrain = rangeStrain / MAXIMUM_CENTER_STRAIN;
							insertTorsion(t, r, relativeStrain);
							if (mLikelyhood[t+1] > mLikelyhood[t]) {
								usedStrain = rangeStrain;
								torsionEdgeUsed = r+1;
								}
							t++;
							}
						foundAlternative = true;
						}
					}
				if (!foundAlternative /* && strain < MAXIMUM_CENTER_STRAIN really bad ones should get a negative likelyhood */) {
					double relativeStrain = centerStrain / MAXIMUM_CENTER_STRAIN;
					mLikelyhood[t] = mFrequency[t] * (1f - relativeStrain * relativeStrain);
					}
				}
			if (mLikelyhood[bestTorsionIndex] < mLikelyhood[t]) {
				bestTorsionIndex = t;
				bestTorsionEdgeUsed = torsionEdgeUsed;
				bestTorsionStrain = usedStrain;	// this is the strain with the highest likelyhood (not necessarily the lowest strain)
				}
			}

		double totalLikelyhood = 0f;
		for (int t=0; t 0f)
				totalLikelyhood += mLikelyhood[t];

		// make sure, we have at least one torsion with positive likelyhood, because only those are considered later
		if (mLikelyhood[bestTorsionIndex] <= 0f) {
			mLikelyhood[bestTorsionIndex] = 1.0f;
			int angle = bestTorsionEdgeUsed == 1 ? -ESCAPE_ANGLE
					  : bestTorsionEdgeUsed == 2 ? ESCAPE_ANGLE
					  : (mRandom.nextDouble() < 0.5) ? -ESCAPE_ANGLE : ESCAPE_ANGLE;
			for (int step=1; step<=ESCAPE_STEPS; step++) {
				currentTorsion = (short)(mTorsion[bestTorsionIndex]+angle*step);
				double[][] m = getRotationMatrix(uv, Math.PI * currentTorsion / 180 - startTorsion);
				for (int i=0; i 0f)
				count++;

		short[] newTorsion = new short[count];
		short[] newFrequency = new short[count];
		double[] newLikelyhood = new double[count];
		for (int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy