/************************************************************************* * * * This file is part of the 20n/act project. * * 20n/act enables DNA prediction for synthetic biology/bioengineering. * * Copyright (C) 2017 20n Labs, Inc. * * * * Please direct all queries to act@20n.com. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * *************************************************************************/ package com.act.analysis.surfactant; import chemaxon.marvin.calculations.logPPlugin; import org.apache.commons.lang3.tuple.Pair; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; public class AtomSplit { Set<Integer> leftIndices; Set<Integer> rightIndices; double leftSum = 0.0, rightSum = 0.0; double weightedLeftSum = 0.0, weightedRightSum = 0.0; int leftPosCount = 0, leftNegCount = 0; int rightPosCount = 0, rightNegCount = 0; double leftMin = 0.0, leftMax = 0.0; double rightMin = 0.0, rightMax = 0.0; protected AtomSplit() { } public AtomSplit(AtomSplit toCopy) { this.leftIndices = new HashSet<>(toCopy.leftIndices); this.rightIndices = new HashSet<>(toCopy.rightIndices); this.leftSum = toCopy.leftSum; this.rightSum = toCopy.rightSum; this.weightedLeftSum = toCopy.weightedLeftSum; this.weightedRightSum = toCopy.weightedRightSum; this.leftPosCount = toCopy.leftPosCount; this.leftNegCount = toCopy.leftNegCount; this.rightPosCount = toCopy.rightPosCount; this.rightNegCount = toCopy.rightNegCount; this.leftMin = toCopy.leftMin; this.leftMax = toCopy.leftMax; this.rightMin = toCopy.rightMin; this.rightMax = toCopy.rightMax; assert(this.equals(toCopy)); } public Set<Integer> getLeftIndices() { return leftIndices; } public Set<Integer> getRightIndices() { return rightIndices; } public double getLeftSum() { return leftSum; } public double getRightSum() { return rightSum; } public double getWeightedLeftSum() { return weightedLeftSum; } public double getWeightedRightSum() { return weightedRightSum; } public int getLeftPosCount() { return leftPosCount; } public int getLeftNegCount() { return leftNegCount; } public int getRightPosCount() { return rightPosCount; } public int getRightNegCount() { return rightNegCount; } public double getLeftMin() { return leftMin; } public double getLeftMax() { return leftMax; } public double getRightMin() { return rightMin; } public double getRightMax() { return rightMax; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AtomSplit that = (AtomSplit) o; if (Double.compare(that.leftSum, leftSum) != 0) return false; if (Double.compare(that.rightSum, rightSum) != 0) return false; if (Double.compare(that.weightedLeftSum, weightedLeftSum) != 0) return false; if (Double.compare(that.weightedRightSum, weightedRightSum) != 0) return false; if (leftPosCount != that.leftPosCount) return false; if (leftNegCount != that.leftNegCount) return false; if (rightPosCount != that.rightPosCount) return false; if (rightNegCount != that.rightNegCount) return false; if (Double.compare(that.leftMin, leftMin) != 0) return false; if (Double.compare(that.leftMax, leftMax) != 0) return false; if (Double.compare(that.rightMin, rightMin) != 0) return false; if (Double.compare(that.rightMax, rightMax) != 0) return false; if (leftIndices != null ? !leftIndices.equals(that.leftIndices) : that.leftIndices != null) return false; return !(rightIndices != null ? !rightIndices.equals(that.rightIndices) : that.rightIndices != null); } @Override public int hashCode() { int result; long temp; result = leftIndices != null ? leftIndices.hashCode() : 0; result = 31 * result + (rightIndices != null ? rightIndices.hashCode() : 0); temp = Double.doubleToLongBits(leftSum); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(rightSum); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(weightedLeftSum); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(weightedRightSum); result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + leftPosCount; result = 31 * result + leftNegCount; result = 31 * result + rightPosCount; result = 31 * result + rightNegCount; temp = Double.doubleToLongBits(leftMin); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(leftMax); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(rightMin); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(rightMax); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } public static Pair<AtomSplit, AtomSplit> computePlaneSplitsForIntersectingAtom( Collection<Integer> leftAtoms, Collection<Integer> rightAtoms, Integer planeIntersectingAtom, logPPlugin plugin, Map<Integer, Integer> surfaceComponentCounts) { /* Compute variants of the split with the current atom on either side of its plane. * * This isn't the most clever way to compute the optimal planes, since adjacent split points will end up creating * one redundant plane with each computation (if a and b are adjacent, a to the left of the border and b to the * right create the same split sets). However, it's easy to reason about the correctness of this approach, so * we'll just live with the inefficiency for now. * * TODO: extend this to handle an arbitrary set of co-planar points that define the split, and apply the same * technique to all triples of atoms. * */ AtomSplit ps = new AtomSplit(); ps.leftIndices = new HashSet<>(leftAtoms); ps.rightIndices = new HashSet<>(rightAtoms); for (Integer a : leftAtoms) { if (planeIntersectingAtom.equals(a)) { // Skip the split point, though it could contribute to the "best" solution. continue; } Double logP = plugin.getAtomlogPIncrement(a); Double weightedLogP = logP * surfaceComponentCounts.get(a).doubleValue(); ps.leftSum += logP; ps.weightedLeftSum += weightedLogP; if (logP < ps.leftMin) { ps.leftMin = logP; } if (logP > ps.leftMax) { ps.leftMax = logP; } if (logP >= 0.000) { ps.leftPosCount++; } else { ps.leftNegCount++; } } for (Integer a : rightAtoms) { if (planeIntersectingAtom.equals(a)) { continue; } Double logP = plugin.getAtomlogPIncrement(a); Double weightedLogP = logP * surfaceComponentCounts.get(a); ps.rightSum += logP; ps.weightedRightSum += weightedLogP; if (logP < ps.rightMin) { ps.rightMin = logP; } if (logP > ps.rightMax) { ps.rightMax = logP; } if (logP >= 0.000) { ps.rightPosCount++; } else { ps.rightNegCount++; } } // Create variants with this point added to left and right sides of split plane. AtomSplit leftVariant = ps; AtomSplit rightVariant = new AtomSplit(ps); Double logP = plugin.getAtomlogPIncrement(planeIntersectingAtom); Double weightedLogP = logP * surfaceComponentCounts.get(planeIntersectingAtom).doubleValue(); leftVariant.leftIndices.add(planeIntersectingAtom); leftVariant.leftSum += logP; leftVariant.weightedLeftSum += weightedLogP; if (logP >= 0.000) { leftVariant.leftPosCount++; } else { leftVariant.leftNegCount++; } if (logP < leftVariant.leftMin) { leftVariant.leftMin = logP; } if (logP > leftVariant.leftMax) { leftVariant.leftMax = logP; } rightVariant.rightIndices.add(planeIntersectingAtom); rightVariant.rightSum += logP; rightVariant.weightedRightSum += weightedLogP; if (logP >= 0.000) { rightVariant.rightPosCount++; } else { rightVariant.rightNegCount++; } if (logP < rightVariant.rightMin) { rightVariant.rightMin = logP; } if (logP > rightVariant.rightMax) { rightVariant.rightMax = logP; } return Pair.of(leftVariant, rightVariant); } }