content: /*
  * Copyright (c) 1997 - 2016
  * Actelion Pharmaceuticals Ltd.
  * Gewerbestrasse 16
  * CH-4123 Allschwil, Switzerland
  *
  * All rights reserved.
  *
  * 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 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 OWNER 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.
  *
  */
  
  package com.actelion.research.chem.forcefield.mmff;
  
  import com.actelion.research.chem.StereoMolecule;
  import com.actelion.research.chem.forcefield.AbstractForceField;
  
  import java.util.*;
  
  /*
   * The MMFF ForceField class is the top level class used to perform
   * energy calculations/minimisation on a molecule. It accepts an
   * ExtendedMolecule and the string name of the parameter tables to use.
   * Options can also be passed to control which energy terms should be
   * included in the force field equation (default: all 7 terms are
   * included), which non-bonded cutoff should be used (default: 100.0
   * angstrom), which dielectric constant should be used (default: 1.0),
   * and whether a constant or a distance-dependent dielectric model should
   * be used (default: linear).
   *
   * Tables: The Tables object contains a collection of parameter tables.
   * These are added statically to the ForceField class to allow all
   * ForceField instances to select which single instance of parameter
   * Tables to use. For example, there would be one tables instance for
   * MMFF94 and another tables instance for MMFF94s. Then when constructing
   * a ForceField object either MMFF94 or MMFF94s can be selected by passing
   * the string identifier used when loading that table with "loadTable()".
   * Options:
   *  - "nonbonded cutoff": A double, limits the number of Electrostatic and
   *      VanDerWaals energy terms to those involving atoms not further
   *      apart than "nonbonded cutoff" (default: 100.0 angstrom).
   *  - "dielectric constant": A double, the dielectric constant value
   *      (default: 1.0)
   *  - "dielectric model": A string for the dielectric model. "distance"
   *      selects a distance-dependent dielectric model, everything else
   *      selects the constant dielectric model (default: "constant")
   *  - "angle bend": A boolean, default True, for whether to include angle
   *      bending energy terms.
   *  - "bond stretch": A boolean, default True, for whether to include bond
   *      stretching energy terms.
   *  - "electrostatic": A boolean, default True, for whether to include the
   *      nonbonded electrostatic energy terms.
   *  - "out of plane": A boolean, default True, for whether to include out
   *      of plane energy terms.
   *  - "stretch bend": A boolean, default True, for whether to include
   *      stretch bending energy terms.
   *  - "torsion angle": A boolean, default True, for whether to include
   *      torsional angle energy terms.
   *  - "van der waals": A boolean, default True, for whether to include the
   *      nonbonded van der Waals energy terms.
   */
  /**
   * @author joel
   *
   */
  public final class ForceFieldMMFF94 extends AbstractForceField {
  	public static final String MMFF94 = "MMFF94";
  	public static final String MMFF94S = "MMFF94s";
      public static final String MMFF94SPLUS = "MMFF94s+";
  
  
      private final MMFFMolecule mMMFFMol;
      public static Map<String, Tables> mTables = new HashMap<>();
      private final List<EnergyTerm> mEnergies = new ArrayList<>();
  
      
      /**
       * Forcefield constructor.
       *  @param m The molecule to construct the forcefield on.
       *  @param tablename The string name for the Tables to be used. There
       *      must be a table with this name that has been loaded with
       *      "loadTable()".
       *  @param options A Map containing the ForceField options and values.
       *      See class description of a list of options.
       */
      
      public ForceFieldMMFF94(StereoMolecule m, String tablename, Map<String, Object> options)
  			throws BadAtomTypeException,BadRingAromException {
      	super(m);
      	mMMFFMol = new com.actelion.research.chem.forcefield.mmff.MMFFMolecule(m);
      	mMol.ensureHelperArrays(StereoMolecule.cHelperRings);
          Tables table = mTables.get(tablename);
  
          double nonBondedThresh = options.containsKey("nonbonded cutoff")
              ? (Double)options.get("nonbonded cutoff")
              : 100.0;
  
          double dielConst = options.containsKey("dielectric constant")
              ? (Double)options.get("dielectric constant") : 1.0;
  
          boolean dielModel = options.containsKey("dielectric model") && (options.get("dielectric model")).equals("distance");
  
          Separation sep = new Separation(mMMFFMol);
  
          if (!options.containsKey("angle bend")
                  || (Boolean)options.get("angle bend"))
              mEnergies.addAll(AngleBend.findIn(table, mMMFFMol));
  
          if (!options.containsKey("bond stretch")
                  || (Boolean)options.get("bond stretch"))
          	mEnergies.addAll(BondStretch.findIn(table, mMMFFMol));
  
          if (!options.containsKey("electrostatic")
                  || (Boolean)options.get("electrostatic"))
          	mEnergies.addAll(Electrostatic.findIn(table, mMMFFMol, sep,
                          nonBondedThresh, dielModel, dielConst));
  
          if (!options.containsKey("out of plane")
                  || (Boolean)options.get("out of plane"))
          	mEnergies.addAll(OutOfPlane.findIn(table, mMMFFMol));
  
          if (!options.containsKey("stretch bend")
                  || (Boolean)options.get("stretch bend"))
          	mEnergies.addAll(StretchBend.findIn(table,mMMFFMol));
  
          if (!options.containsKey("torsion angle")
                  || (Boolean)options.get("torsion angle"))
          	mEnergies.addAll(TorsionAngle.findIn(table, mMMFFMol));
  
          if (!options.containsKey("van der waals")
                  || (Boolean)options.get("van der waals"))
          	mEnergies.addAll(VanDerWaals.findIn(table, mMMFFMol, sep, nonBondedThresh));
      }
  
      /**
       * Forcefield constructor. Overloaded to pass the default (empty)
       * options to a ForceField.
       *  @param mol The molecule to construct the forcefield on.
       *  @param tablename The string name for the Tables to be used. There
       *      must be a table with this name that has been loaded with
       *      "loadTable()".
       */
      public ForceFieldMMFF94(StereoMolecule mol, String tablename) throws BadAtomTypeException,BadRingAromException {
          this(mol, tablename, new HashMap<>());
      }
  
      /**
       * Returns the total number of atoms in this force field.
       *  @return Total number of atoms.
       */
      public int size() {
          return mMMFFMol.getAllAtoms();
      }
  
      /**
       * Minimise the current molecule using default parameter values for
       * the number of iterations, energy tolerance and gradient tolerance.
       *  @return Return code, 0 on success.
       */
  
      @Override
      public double updateGradient() {
          mGrad = new double[mDim];
  
          for (EnergyTerm engy : mEnergies) 
              engy.getGradient(mPos, mGrad);
          double maxGrad = -1e8;
          double gradScale = 0.1;
          for (int i=0; i<mDim; i++) {
              mGrad[i] *= gradScale;
              if (mGrad[i] > maxGrad)
                  maxGrad = mGrad[i];
          }
  
          if (maxGrad > 10.0) {
              while (maxGrad*gradScale > 10.0)
                  gradScale *= 0.5;
  
              for (int i=0; i<mDim;i++)
                  mGrad[i] *= gradScale;
          }
          return gradScale;
      }
      
      @Override 
      public void zeroGradient() {
   		if (mFixedAtoms!=null) {
   			int[] hydrogenMap = mMMFFMol.getHydrogenMap();
   			for (int i:mFixedAtoms) {
   				int mappedIndex = hydrogenMap[i];
   				mGrad[3*mappedIndex] = 0.0;
  				mGrad[3*mappedIndex+1] = 0.0;
   				mGrad[3*mappedIndex+2] = 0.0;
   	   		}
      	}
      }
  
      @Override
      public double[] getCurrentPositions() {
      	double[] pos = Arrays.copyOf(mPos, mPos.length);
  
      	return getMappedPositions(pos);
      }
      
      private double[] getMappedPositions(double[] pos) {
      	int[] atomMap = mMMFFMol.getHydrogenMap();
      	double[] mappedPos = Arrays.copyOf(pos, pos.length);
      	for(int i=0;i<atomMap.length;i++) {
      		mappedPos[3*i] = pos[3*atomMap[i]];
      		mappedPos[3*i+1] = pos[3*atomMap[i]+1];
      		mappedPos[3*i+2] = pos[3*atomMap[i]+2];
      	}
      	return mappedPos;
      }
      
      /**
       * Gets the total energy of the molecule as the sum of the energy
       * terms.Requires the atomic positions to be in the correct order.
       *  @param pos The positions array representing the atoms positions in space.
       *  @return The total force field energy.
       */
  	public double getTotalEnergy(double[] pos) {
  		return getTotalEnergy(pos, null, false);
  	}
  
  	private double getTotalEnergy(double[] pos, StringBuilder detail, boolean skipHydrogen) {
  		if (detail != null)
  			detail.append("no\ttype\tis_property\topt_property\tatoms\tenergy\n");
  
          double total = 0.0;
  		int no = 1;
          for (EnergyTerm term : mEnergies) {
  			double energy = term.getEnergy(pos, detail, Integer.toString(no), skipHydrogen);
  			if (energy != 0.0) {
  				total += energy;
  				no++;
  			}
  		}
          return total;
      }
  
      /**
       * Gets the total energy of the molecule as the sum of the energy
       * terms. This function passes the force fields `pos` array to
       * getTotalEnergy().
       *  @return The total force field energy.
       */
      public double getTotalEnergy() {
      	return getTotalEnergy(mPos, null, false);
      }
  
  	/**
  	 * Gets the total energy of the molecule as the sum of the energy
  	 * terms. This function passes the force fields `pos` array to
  	 * getTotalEnergy().
  	 * @param detail if !=null received detailed break down of energy contributions
  	 *  @return The total force field energy.
  	 */
  	public double getTotalEnergy(StringBuilder detail, boolean skipHydrogen) {
  		return getTotalEnergy(mPos, detail, skipHydrogen);
  	}
  
  	public static void initialize(String tableSet) {
          ForceFieldMMFF94.loadTable(tableSet, Tables.newMMFF94(tableSet));
      }
  
      /**
       * Loads and registers a tables object with the ForceField class so it
       * can be used by new ForceField instances.
       *  @param name The string name used to identifiy the tables object.
       *  @param table The tables object.
       */
      public static synchronized void loadTable(String name, Tables table) {
          if (!mTables.containsKey(name))
              mTables.put(name, table);
      }
  
      /**
       * Returns a table given a table name.
       *  @param name The string name of a table.
       *  @return The tables object.
       */
      public static Tables table(String name) {
          return mTables.get(name);
      }
  
  	public MMFFMolecule getMMFFMolecule() {
  		return mMMFFMol;
  	}
  	
  	public void addEnergyTerm(EnergyTerm term) {
  		mEnergies.add(term);
  	}
  }