shapeReduction.ShapeReductorByClustering.java Source code

Java tutorial

Introduction

Here is the source code for shapeReduction.ShapeReductorByClustering.java

Source

/*
Author:
  Fabrice Moriaud <fmoriaud@ultimatepdb.org>
    
  Copyright (c) 2016 Fabrice Moriaud
    
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
    
  You should have received a copy of the GNU Lesser General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
package shapeReduction;

import java.util.*;
import java.util.Map.Entry;

import math.MathTools;
import org.apache.commons.math3.geometry.euclidean.threed.SphericalCoordinates;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;

import math.ClusteringByLinkageWithAnUpdatedAndSortedCollectionOfClusterPairs;
import math.ClusteringByLinkageWithAnUpdatedAndSortedCollectionOfClusterPairs.ClusteringLinkageType;
import math.EquidistributionPhi;
import parameters.AlgoParameters;
import pointWithProperties.CollectionOfPointsWithPropertiesIfc;
import pointWithProperties.IdAndPointWithProperties;
import pointWithProperties.PointIfc;
import pointWithProperties.PointWithPropertiesIfc;
import pointWithProperties.StrikingProperties;
import pointWithProperties.StrikingPropertiesTools;

public class ShapeReductorByClustering implements ShapeReductorIfc {
    // -------------------------------------------------------------------
    // Class variables
    // -------------------------------------------------------------------
    private CollectionOfPointsWithPropertiesIfc shapeCollectionPoints;
    private AlgoParameters algoParameters;

    private List<Float> radiusValues;
    private boolean debug = false;

    private PointIfc barycenterShape;

    private Map<PointWithPropertiesIfc, Integer> mapPointToOriginalId;

    // -------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------
    public ShapeReductorByClustering(CollectionOfPointsWithPropertiesIfc shapeCollectionPoints,
            AlgoParameters algoParameters) {
        this.shapeCollectionPoints = shapeCollectionPoints;
        this.barycenterShape = ShapeReductorTools
                .computeBarycenterOfACollectionOfPointCoords(shapeCollectionPoints);
        this.mapPointToOriginalId = fillMap();
        this.algoParameters = algoParameters;
    }

    // -------------------------------------------------------------------
    // Public Interface
    // -------------------------------------------------------------------
    @Override
    public Map<Integer, PointWithPropertiesIfc> computeReducedCollectionOfPointsWithProperties() {

        Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> mapPropertyAndMapSectorAndPoints = distributePointsInSectorsAndGroupPerProperty();
        Map<Integer, PointWithPropertiesIfc> minishape = generateMiniShape(mapPropertyAndMapSectorAndPoints);
        return minishape;
    }

    // -------------------------------------------------------------------
    // Implementation
    // -------------------------------------------------------------------
    private Map<PointWithPropertiesIfc, Integer> fillMap() {

        Map<PointWithPropertiesIfc, Integer> mapPointToOriginalId = new LinkedHashMap<>();
        for (int i = 0; i < shapeCollectionPoints.getSize(); i++) {
            mapPointToOriginalId.put(shapeCollectionPoints.getPointFromId(i), i);
        }
        return mapPointToOriginalId;
    }

    private Map<Integer, PointWithPropertiesIfc> generateMiniShape(
            Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> mapPropertyAndMapSectorAndPoints) {

        Map<Integer, PointWithPropertiesIfc> collectionOfPointsWithProperties = doClustering(
                mapPropertyAndMapSectorAndPoints);
        Map<PointWithPropertiesIfc, PointWithPropertiesIfc> pairsPointCloseBy = findPointsTooCloseWithASharedStrikingProperties(
                collectionOfPointsWithProperties);

        for (Entry<PointWithPropertiesIfc, PointWithPropertiesIfc> pairPoint : pairsPointCloseBy.entrySet()) {
            Integer idOfPoint = mapPointToOriginalId.get(
                    ShapeReductorTools.returnPointWithLowerPriorityWhenThereIsAMatchingProperty(pairPoint.getKey(),
                            pairPoint.getValue()));
            collectionOfPointsWithProperties.remove(idOfPoint);
        }

        removePointsOfStrikingPropertiesNoneIfCloseEnoughToAnotherPointWithAnyStrikingPropertiesNotNone(
                collectionOfPointsWithProperties);

        return collectionOfPointsWithProperties;
    }

    /**
     * @param collectionOfPointsWithProperties
     * @return
     */
    private Map<PointWithPropertiesIfc, PointWithPropertiesIfc> findPointsTooCloseWithASharedStrikingProperties(
            Map<Integer, PointWithPropertiesIfc> collectionOfPointsWithProperties) {

        Map<PointWithPropertiesIfc, PointWithPropertiesIfc> pairsPointCloseBy = new LinkedHashMap<>();

        for (Entry<Integer, PointWithPropertiesIfc> entry : collectionOfPointsWithProperties.entrySet()) {
            A: for (Entry<Integer, PointWithPropertiesIfc> entry2 : collectionOfPointsWithProperties.entrySet()) {

                PointWithPropertiesIfc pointWithProperties1 = entry.getValue();
                PointWithPropertiesIfc pointWithProperties2 = entry2.getValue();

                if (pointWithProperties1 != pointWithProperties2) {
                    double distance = (float) MathTools.computeDistance(
                            pointWithProperties1.getCoords().getCoords(),
                            pointWithProperties2.getCoords().getCoords());
                    //MathTools.computeDistance(pointWithProperties1.getCoords().getCoords(), pointWithProperties2.getCoords().getCoords());
                    if (distance < ((algoParameters.getCELL_DIMENSION_OF_THE_PROBABILITY_MAP_ANGSTROM()
                            * Math.sqrt(3)) + 0.1)) {

                        int matchingPropertyCount = StrikingPropertiesTools.evaluatePointsMatchingWithAllProperties(
                                pointWithProperties1, pointWithProperties2);
                        if (matchingPropertyCount != 0) {
                            if (pairsPointCloseBy.containsKey(entry2.getValue())) {
                                if (pairsPointCloseBy.get(entry2.getValue()).equals(entry.getValue())) {
                                    // already there
                                    continue A;
                                }
                            }
                            pairsPointCloseBy.put(entry.getValue(), entry2.getValue());
                        }
                    }
                }
            }
        }
        return pairsPointCloseBy;
    }

    private Map<Integer, PointWithPropertiesIfc> doClustering(
            Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> mapPropertyAndMapSectorAndPoints) {

        Map<Integer, PointWithPropertiesIfc> miniShape = new LinkedHashMap<>();

        for (Entry<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> mapSectorAndPoint : mapPropertyAndMapSectorAndPoints
                .entrySet()) {

            List<PointWithPropertiesIfc> listBarycenters = new ArrayList<>();
            for (Entry<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> sectorAndPoints : mapSectorAndPoint
                    .getValue().entrySet()) {

                PointWithPropertiesIfc pointWithProperties = ShapeReductorTools
                        .getPointClosestToBarycenter(sectorAndPoints.getValue(), miniShape);
                if (pointWithProperties != null) {
                    listBarycenters.add(pointWithProperties);
                }
            }

            if (listBarycenters.size() == 0) {
                continue;
            }

            Collections.sort(listBarycenters, new PointOnlyDistanceToBarycenterComparator(this.barycenterShape));

            ClusteringByLinkageWithAnUpdatedAndSortedCollectionOfClusterPairs completeLinkageClustering = new ClusteringByLinkageWithAnUpdatedAndSortedCollectionOfClusterPairs(
                    listBarycenters, algoParameters, ClusteringLinkageType.SINGLE_LINKAGE);
            List<List<PointWithPropertiesIfc>> clusteredPoints = completeLinkageClustering.getClusteredPoints();
            if (debug == true) {
                System.out
                        .println("completeLinkageClustering of property : " + mapSectorAndPoint.getKey().toString()
                                + " from " + listBarycenters.size() + " to " + clusteredPoints.size());
            }

            for (List<PointWithPropertiesIfc> clusterForThisProperty : clusteredPoints) {

                PointWithPropertiesIfc onePointPerCluster = ShapeReductorTools
                        .selectOnePointFromAClusterButNotAlreadyInMinishapeIfPossible(clusterForThisProperty,
                                miniShape);
                if (onePointPerCluster != null) {
                    Integer idOfPoint = mapPointToOriginalId.get(onePointPerCluster);
                    //onePointPerCluster.setMiniShapeStrikingProperty(mapSectorAndPoint.getKey()); // not used afterwards but maybe useful
                    if (miniShape.containsKey(idOfPoint)) {
                        System.out.println(
                                "miniShape.containsKey(idOfPoint) so will override, which one to choose ?? ");
                    }
                    miniShape.put(idOfPoint, onePointPerCluster);
                }
            }
        }
        return miniShape;
    }

    private void removePointsOfStrikingPropertiesNoneIfCloseEnoughToAnotherPointWithAnyStrikingPropertiesNotNone(
            Map<Integer, PointWithPropertiesIfc> collectionOfPointsWithProperties) {

        double threshold = algoParameters.getTHRESHOLD_DISTANCE_TO_KEEP_NEIGHBORING_NONE_STRIKING_PROPERTY();
        double distance;
        Set<Integer> setSomeNonePointsToRemove = new HashSet<>();

        A: for (Entry<Integer, PointWithPropertiesIfc> point : collectionOfPointsWithProperties.entrySet()) {

            List<StrikingProperties> listStrikingPropertiesForThisPoint = point.getValue().getStrikingProperties();
            if ((listStrikingPropertiesForThisPoint.size() == 1)
                    && listStrikingPropertiesForThisPoint.get(0).equals(StrikingProperties.NONE)) {

                for (Entry<Integer, PointWithPropertiesIfc> neighBohrpoint : collectionOfPointsWithProperties
                        .entrySet()) {
                    if (neighBohrpoint == point) {
                        continue;
                    }
                    List<StrikingProperties> listStrikingPropertiesForThisNeighBohrpoint = neighBohrpoint.getValue()
                            .getStrikingProperties();
                    if ((listStrikingPropertiesForThisNeighBohrpoint.size() != 0)
                            && (!listStrikingPropertiesForThisNeighBohrpoint.contains(StrikingProperties.NONE))) {

                        distance = MathTools.computeDistance(point.getValue().getCoords().getCoords(),
                                neighBohrpoint.getValue().getCoords().getCoords());
                        if (distance < threshold) {
                            setSomeNonePointsToRemove.add(point.getKey());
                            continue A;
                        }
                    }
                }
            }
        }

        for (Integer pointToRemove : setSomeNonePointsToRemove) {
            collectionOfPointsWithProperties.remove(pointToRemove);
        }
    }

    // -------------------------------------------------------------------
    // Private & Implementation Methods
    // -------------------------------------------------------------------
    /**
     * Each sector is multiply by different striking properties found in
     *
     * @return
     */
    private Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> distributePointsInSectorsAndGroupPerProperty() {

        Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> mapSectorAndPointsAllSectors = groupPoints();
        Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> mapSectorAndPoints = removeEmptySectors(
                mapSectorAndPointsAllSectors);

        // group point having exactly the same properties, inlcuding 1 or 2 or more
        Map<PhiThetaRadiusInterval, List<StrikingProperties>> mapSectorAndListProperties = computePropertiesOfGroups(
                mapSectorAndPoints);
        Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> mapPropertyAndMapSectorAndPoints = multiplySectorByProperty(
                mapSectorAndPoints, mapSectorAndListProperties);
        return mapPropertyAndMapSectorAndPoints;
    }

    private Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> multiplySectorByProperty(
            Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> mapSectorAndPoints,
            Map<PhiThetaRadiusInterval, List<StrikingProperties>> mapSectorAndListProperties) {

        Map<StrikingProperties, Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>>> mapPropertyAndMapSectorAndPoints = new LinkedHashMap<>();

        // Initialize the returned Map with all predefined properties
        StrikingProperties[] allProperties = StrikingProperties.values();

        for (StrikingProperties strikingProperty : allProperties) {
            Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> instanciatedMap = new LinkedHashMap<>();

            mapPropertyAndMapSectorAndPoints.put(strikingProperty, instanciatedMap);
        }

        // TODO if I use mapSectorAndListProperties only here then I should modify to get directly splitted sectors without making mapSectorAndListProperties
        for (Entry<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> entry : mapSectorAndPoints
                .entrySet()) {

            PhiThetaRadiusInterval sector = entry.getKey();
            Map<Integer, PointWithPropertiesIfc> points = entry.getValue();

            List<StrikingProperties> strikingPropertiesFoundInThisSector = mapSectorAndListProperties.get(sector);

            for (StrikingProperties strikingProperty : strikingPropertiesFoundInThisSector) {
                Map<Integer, PointWithPropertiesIfc> pointsWithTheStrikingProperty = StrikingPropertiesTools
                        .extractPointsHavingTheProperty(points, strikingProperty);

                Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> mapWhereIwantToAdd = mapPropertyAndMapSectorAndPoints
                        .get(strikingProperty);
                mapWhereIwantToAdd.put(sector, pointsWithTheStrikingProperty);
            }
        }
        return mapPropertyAndMapSectorAndPoints;
    }

    private Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> groupPoints() {

        double deltaOnlyForTheta = Math.PI / (double) algoParameters.getCOUNT_OF_INCREMENT_ANGLE();

        int countOfIncrementAngle = algoParameters.getCOUNT_OF_INCREMENT_ANGLE();
        EquidistributionPhi equidistributionPhi = new EquidistributionPhi();
        List<Double> phiValues = equidistributionPhi.getMapCountOfIntervalsAndPointValues()
                .get(countOfIncrementAngle);
        // theta in map ranges from -pi to +pi in agreement with apache spherical coodinates
        List<Double> tethaValues = ShapeReductorTools.doBinningThetaValues(deltaOnlyForTheta,
                algoParameters.getCOUNT_OF_INCREMENT_ANGLE());

        double maxRinThisShape = findMaxRadiusInThisShape(barycenterShape);
        radiusValues = doBinningRadiusValues(maxRinThisShape);

        SectorsIfc sectors = generateSector(deltaOnlyForTheta, phiValues, tethaValues, radiusValues);

        Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> groupPoints = new LinkedHashMap<>();

        Iterator<PhiThetaRadiusInterval> it = sectors.iterator();
        while (it.hasNext()) {
            PhiThetaRadiusInterval sector = it.next();
            Map<Integer, PointWithPropertiesIfc> collectionOfPointsWithProperties = new LinkedHashMap<>();
            groupPoints.put(sector, collectionOfPointsWithProperties);
        }

        for (int i = 0; i < shapeCollectionPoints.getSize(); i++) {

            Integer pointIDToBeKept = i;

            float[] point = shapeCollectionPoints.getPointFromId(i).getCoords().getCoords();

            float[] pointRelativeToBarycenter = MathTools.v1minusV2(point, barycenterShape.getCoords());
            Vector3D pointRelativeToBarycenterV3d = new Vector3D(pointRelativeToBarycenter[0],
                    pointRelativeToBarycenter[1], pointRelativeToBarycenter[2]);

            SphericalCoordinates pointShericalRelative = new SphericalCoordinates(pointRelativeToBarycenterV3d);

            PhiThetaRadiusInterval intervalForThisPoint = sectors
                    .getIntervalFromSphericalCoordinates(pointShericalRelative);

            if (intervalForThisPoint == null) { // it could be that some points doesnt fit so I should make the binning a bit larger I guess
                continue;
            }
            Map<Integer, PointWithPropertiesIfc> groupWherePointToAdd = groupPoints.get(intervalForThisPoint);
            groupWherePointToAdd.put(pointIDToBeKept, shapeCollectionPoints.getPointFromId(i));
        }

        return groupPoints;
    }

    private SectorsIfc generateSector(double deltaOnlyForTheta, List<Double> phiValues, List<Double> tethaValues,
            List<Float> radiusValues) {

        SectorsIfc sectors = new Sectors();

        for (int i = 0; i < phiValues.size() - 1; i++) { // phi values are from 0 to PI by construction so I dont take PI
            double minPhi = phiValues.get(i);
            double maxPhi = phiValues.get(i + 1);

            for (Double tetha : tethaValues) {

                for (int k = 0; k < radiusValues.size() - 1; k++) {

                    double minR = radiusValues.get(k);
                    double maxR = radiusValues.get(k + 1);
                    double minTheta = tetha;
                    double maxTheta = tetha + deltaOnlyForTheta;
                    PhiThetaRadiusInterval phiThetaWithRinterval = new PhiThetaRadiusInterval(minPhi, maxPhi,
                            minTheta, maxTheta, minR, maxR);
                    sectors.addSector(phiThetaWithRinterval);
                }
            }
        }
        return sectors;
    }

    private double findMaxRadiusInThisShape(PointIfc center) {

        double maxRadius = Double.MIN_VALUE;

        for (int i = 0; i < shapeCollectionPoints.getSize(); i++) {

            PointIfc point = shapeCollectionPoints.getPointFromId(i).getCoords();

            double distance = MathTools.computeDistance(center.getCoords(), point.getCoords());

            if (distance > maxRadius) {
                maxRadius = distance;
            }
        }

        return maxRadius;
    }

    private List<Float> doBinningRadiusValues(double maxRinThisShape) {

        List<Float> radiusValues = new ArrayList<>();

        radiusValues.add(0.0f);
        radiusValues.add(algoParameters.getFIRST_RADIUS_INCREMENT_IN_SHAPE_REDUCTION());

        int index = 2;
        float nextRadius;
        do {
            nextRadius = (float) (2.0 * Math.pow(radiusValues.get(index - 1), 3)
                    - Math.pow(radiusValues.get(index - 2), 3));
            nextRadius = (float) Math.pow(nextRadius, 1.0 / 3.0);
            radiusValues.add(nextRadius);
            if (index == 99) {
                radiusValues.add(1000.0f);
                break;
            }
            index += 1;
        } while (nextRadius < maxRinThisShape);

        return radiusValues;
    }

    private Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> removeEmptySectors(
            Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> mapSectorAndPoints) {

        Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> newMap = new LinkedHashMap<>();

        for (Entry<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> entry : mapSectorAndPoints
                .entrySet()) {

            int sizeOfGroupOfPoints = entry.getValue().size();

            if (sizeOfGroupOfPoints > 0) {
                newMap.put(entry.getKey(), entry.getValue());
            }
        }

        return newMap;
    }

    private Map<PhiThetaRadiusInterval, List<StrikingProperties>> computePropertiesOfGroups(
            Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> groupedPoints) {

        // TODO investigate why reproducibility is fixed by doing this treemap (and compare in PhiThethaInterval)
        Map<PhiThetaRadiusInterval, List<StrikingProperties>> mapGroupPropertiesForThisGroup = new TreeMap<>();

        for (Entry<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> entry : groupedPoints.entrySet()) {

            Map<Integer, PointWithPropertiesIfc> pointsWithPropertiesInThisGroup = entry.getValue();
            List<StrikingProperties> propertiesForThisGroup = StrikingPropertiesTools
                    .computeStrikingPropertiesOfAShape(pointsWithPropertiesInThisGroup);
            mapGroupPropertiesForThisGroup.put(entry.getKey(), propertiesForThisGroup);
        }

        return mapGroupPropertiesForThisGroup;
    }

    private class PointOnlyDistanceToBarycenterComparator implements Comparator<PointWithPropertiesIfc> {

        private PointIfc barycenter;

        public PointOnlyDistanceToBarycenterComparator(PointIfc barycenter) {
            this.barycenter = barycenter;
        }

        @Override
        public int compare(PointWithPropertiesIfc point1, PointWithPropertiesIfc point2) {

            float distancePoint1ToBarycenter = MathTools.computeDistance(barycenter.getCoords(),
                    point1.getCoords().getCoords());
            float distancePoint2ToBarycenter = MathTools.computeDistance(barycenter.getCoords(),
                    point2.getCoords().getCoords());
            if (distancePoint1ToBarycenter < distancePoint2ToBarycenter) {
                return -1;
            }
            if (distancePoint1ToBarycenter > distancePoint2ToBarycenter) {
                return 1;
            }
            return 0;
        }
    }
}