eu.smartenit.unada.sa.SocialAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for eu.smartenit.unada.sa.SocialAnalyzer.java

Source

/**
 * Copyright (C) 2014 The SmartenIT consortium (http://www.smartenit.eu)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.smartenit.unada.sa;

import com.javadocmd.simplelatlng.LatLng;
import com.javadocmd.simplelatlng.LatLngTool;
import com.javadocmd.simplelatlng.util.LengthUnit;
import eu.smartenit.unada.db.dao.impl.*;
import eu.smartenit.unada.db.dao.util.DAOFactory;
import eu.smartenit.unada.db.dto.*;
import eu.smartenit.unada.om.IOverlayManager;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Class which holds the SocialAnalyzer algorithm
 */
public final class SocialAnalyzer {

    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(SocialAnalyzer.class);

    /**
     * OverlayManager instance for retrieving Unada information
     */
    private final IOverlayManager overlayManager;

    /**
     * Creates a SocialAnalyzer instance
     *
     * @param overlayManager An OverlayManager instance for retrieving Unada information
     */
    public SocialAnalyzer(IOverlayManager overlayManager) {
        this.overlayManager = overlayManager;
    }

    /**
     * Invokes the prediction algorithm and returns a List of Content items, sorted by their score (ascending)
     *
     * @return Returns a List of Content items, sorted by their score (ascending)
     */
    public List<Content> predicting() {

        SortedSet<ContentItem> contentItems = new TreeSet<ContentItem>();

        List<FeedItem> feedItemList = getFeedItems();
        Set<Long> contentIDSet = createContentIDSet(feedItemList);
        Map<Long, FeedItem> feedItemMap = crateFeedItemMap(feedItemList);

        for (long contentID : contentIDSet) {
            logger.info("Calculating prediction score for content ID: " + contentID);
            VideoInfo videoInfo = getVideoInfo(contentID);
            if (videoInfo == null) {
                logger.warn("Ignoring contentID " + contentID + " (VideoInfo) was null");
                continue;
            }

            //age
            logger.debug("Calculating age for content ID " + contentID);
            double alpha = calculateAge(videoInfo.getPublishDate(), feedItemMap.get(contentID).getTime());
            if (alpha == -1) {
                logger.warn("Age for content ID " + contentID
                        + " could not be calculated. Setting age component to 0.");
            }
            logger.debug("Alpha: " + alpha);

            //distance
            logger.debug("Calculating distance for content ID " + contentID);
            double delta = getDistance(contentID);
            if (delta == -1) {
                logger.warn("Distance for content ID " + contentID
                        + " could not be calculated. Setting distance component to 0.");
            }
            logger.debug("Delta: " + delta);

            //history
            logger.debug("Obtaining number of accesses to the database for content ID " + contentID);
            long eta = getNumberOfAccesses(contentID);
            logger.debug("Eta: " + eta);

            //popularity
            logger.debug("Obtaining number of views from database for content ID " + contentID);
            long phi = videoInfo.getViewsNumber();
            if (phi == -1) {
                logger.warn("Popularity for content ID " + contentID
                        + " could not be calculated. Setting popularity component to 0.");
            }
            logger.debug("Phi: " + phi);

            //social
            logger.debug("Obtaining friends from database for current user");
            List<Friend> friendList = getFriends();
            if (logger.isDebugEnabled()) {
                for (Friend friend : friendList) {
                    logger.debug("Friend: " + friend.getFacebookID() + " " + friend.getFacebookName());
                }
            }
            logger.debug("Number of friends: " + friendList.size());

            logger.debug("Calculating social score for content ID " + contentID);
            double gamma = getSocial(friendList, contentID);
            logger.debug("Gamma: " + gamma);

            logger.debug("Calculating final score for content ID " + contentID);
            double score = calculateScore(alpha, delta, eta, phi, gamma);
            logger.debug("Score for conent ID " + contentID + ": " + score);

            contentItems.add(new ContentItem(contentID, score));
            logger.info("Done.");
        }

        logger.info("Sorting content by prediction score...");
        List<Content> sortedContent = new ArrayList<Content>();
        for (ContentItem item : contentItems) {
            Content content = new Content();
            content.setContentID(item.getContentID());
            content.setCacheScore(item.getScore());
            sortedContent.add(content);
        }
        logger.info("Done.");
        return sortedContent;
    }

    /**
     * Retrieves FeedItems from the database
     *
     * @return A List containing FeedItems from the database.
     */
    private List<FeedItem> getFeedItems() {
        FeedItemDAO feedItemDAO = DAOFactory.getFeedItemDAO();
        List<FeedItem> feedItemsFromDB = feedItemDAO.findAll();
        List<FeedItem> feedItems = new ArrayList<FeedItem>();
        for (FeedItem feedItem : feedItemsFromDB) {
            if (FeedItem.FeedType.FEED.equals(feedItem.getFeedType())) {
                feedItems.add(feedItem);
            }
        }
        return feedItems;
    }

    /**
     * Creates a Set of relevant content IDs from a given feed item List.
     *
     * @param feedItemList The List from which the content IDs are to be retrieved.
     * @return Returns a Set of relevant content IDs from a given feed item List.
     */
    private Set<Long> createContentIDSet(List<FeedItem> feedItemList) {
        Set<Long> contentIDSet = new HashSet<Long>();
        for (FeedItem item : feedItemList) {
            if (item.getContentID() != -1) {
                contentIDSet.add(item.getContentID());
            }
        }
        return contentIDSet;
    }

    /**
     * Creates a Map for looking up FeedItems by content ID.
     * @param feedItemList The List of FeedItems for which the Map shall be created.
     * @return Returns a Map for looking up FeedItems by content ID.
     */
    private Map<Long, FeedItem> crateFeedItemMap(List<FeedItem> feedItemList) {
        Map<Long, FeedItem> map = new HashMap<Long, FeedItem>();
        for (FeedItem feedItem : feedItemList) {
            if (feedItem.getContentID() != -1) {
                map.put(feedItem.getContentID(), feedItem);
            }
        }
        return map;
    }

    /**
     * Retrieves video information from the database
     *
     * @param contentID The content ID of the file for which video information is to be retrieved.
     * @return Returns video information from the database
     */
    private VideoInfo getVideoInfo(long contentID) {
        VideoInfoDAO videoInfoDAO = DAOFactory.getVideoInfoDAO();
        return videoInfoDAO.findById(contentID);
    }

    /**
     * Calculates the age of a video in weeks.
     *
     * @param publishDate Point in time at which the video was published.
     * @param feedDate Point in time at which the video was posted on the users wall.
     * @return The age of the video in weeks or -1 if it could not be calculated.
     */
    private double calculateAge(Date publishDate, Date feedDate) {
        if (publishDate == null || feedDate == null) {
            return -1;
        } else {
            LocalDateTime feedPublished = new LocalDateTime(feedDate);
            LocalDateTime videoPublished = new LocalDateTime(publishDate);

            double ageInMilliseconds = feedPublished.toDateTime().getMillis()
                    - videoPublished.toDateTime().getMillis();
            return ageInMilliseconds / 1000 / 60 / 60 / 24 / 7;
        }
    }

    /**
     * Calculates the distance of some content identified by content ID from the current Unada.
     * @param contentID The content ID for which video information is to be retrieved.
     * @return Returns the distance of some content identified by content ID from the current Unada.
     */
    private double getDistance(long contentID) {
        UnadaInfo localInfo = overlayManager.getuNaDaInfo();
        double localLatitude = localInfo.getLatitude();
        double localLongitude = localInfo.getLongitude();

        Set<UnadaInfo> closeProviders = overlayManager.getCloseProviders(contentID);
        //If no unada could be found, return -1, i.e. no valid value
        if (closeProviders.isEmpty()) {
            return -1;
        }

        UnadaInfoDistance uid = null;

        for (UnadaInfo unadaInfo : closeProviders) {
            double distance = calculateDistance(localLatitude, localLongitude, unadaInfo.getLatitude(),
                    unadaInfo.getLongitude());
            if (uid == null || distance < uid.getDistance()) {
                uid = new UnadaInfoDistance(unadaInfo, distance);
            }
        }

        if (uid != null) {
            return uid.getDistance();
        } else {
            return -1;
        }
    }

    /**
     * Calculates the distance between two geographical points
     *
     * @param localLatitude Latitude of local unada
     * @param localLongitude Longitude of local unada
     * @param remoteLatitude Latitude of remote unada
     * @param remoteLongitude Longitude of remote unada
     *
     * @return The distance between two geographical points.
     */
    private double calculateDistance(double localLatitude, double localLongitude, double remoteLatitude,
            double remoteLongitude) {
        LatLng localPoint = new LatLng(localLatitude, localLongitude);
        LatLng remotePoint = new LatLng(remoteLatitude, remoteLongitude);
        return LatLngTool.distance(localPoint, remotePoint, LengthUnit.KILOMETER);
    }

    /**
     * Counts the number of times some contend identified by content ID was accessed.
     * @param contentID The content ID for which access number is to be retrieved.
     * @return Counts the number of times some contend identified by content ID was accessed.
     */
    private long getNumberOfAccesses(long contentID) {
        ContentAccessDAO contentAccessDAO = DAOFactory.getContentAccessDAO();
        List<ContentAccess> contentAccessList = contentAccessDAO.findByContentID(contentID);

        long numberOfAccesses = 0;
        for (ContentAccess contentAccessFromDB : contentAccessList) {
            if (contentID == contentAccessFromDB.getContentID()) {
                numberOfAccesses++;
            }
        }

        return numberOfAccesses;
    }

    /**
     * Returns the friends of the current user.
     * @return Returns the friends of the current user.
     */
    private List<Friend> getFriends() {
        FriendDAO friendDAO = DAOFactory.getFriendDAO();
        return friendDAO.findAll();
    }

    /**
     * Calculates the social parameter.
     *
     * @param friendList List of friends.
     * @param contentID The content ID for which the parameter shall be calculated.
     * @return The social parameter for a given content ID.
     */
    private double getSocial(List<Friend> friendList, long contentID) {
        Set<String> friendsHoldingFeed = new HashSet<String>();
        FeedItemDAO feedItemDAO = DAOFactory.getFeedItemDAO();
        List<FeedItem> feedItems = feedItemDAO.findAll();

        Set<String> friendIds = new HashSet<>();
        for (Friend friend : friendList) {
            friendIds.add(friend.getFacebookID());
        }
        for (FeedItem feedItem : feedItems) {
            if (friendIds.contains(feedItem.getUserID()) && feedItem.getContentID() == contentID) {
                friendsHoldingFeed.add(feedItem.getUserID());
            }
        }

        return 100 * friendsHoldingFeed.size() / friendList.size();
    }

    /**
     * Calculates the prediction score for a given set of parameters.
     *
     * @param alpha The age in weeks
     * @param delta The distance in km
     * @param eta The number of historical accesses
     * @param phi The popularity of a video
     * @param gamma The social parameter
     * @return The prediction score for a given set of parameters.
     */
    public double calculateScore(double alpha, double delta, double eta, double phi, double gamma) {

        double fAge = calcFAge(alpha);
        double fDistance = calcFDistance(delta);
        double fHistory = calcFHistory(eta);
        double fPopularity = calcFPouplarity(phi);
        double fSocial = calcFSocial(gamma);

        return calculateFinalScore(fAge, fDistance, fHistory, fPopularity, fSocial);
        //double pRetweet = transformToProbability(score);
        //boolean predicting = pRetweet >= 0.5;
    }

    /**
     * Caluclates the age component.
     *
     * @param alpha The age in weeks.
     * @return Returns the age component or 0 if it could not be calculated.
     */
    private double calcFAge(double alpha) {
        if (alpha == -1) {
            return 0;
        }
        return 4.898 * Math.pow(10, -4) * Math.pow(Math.E, -2.951 * alpha) - 2.961 * Math.pow(10, -7) * alpha
                + 1.111 * Math.pow(10, -4);
    }

    /**
     * Calculates the distance component.
     *
     * @param delta The distance in km.
     * @return Returns the distance component or 0 if it could not be calculated.
     */
    private double calcFDistance(double delta) {
        if (delta == -1) {
            return 0;
        }
        return 2.309 * Math.pow(10, -3) * Math.pow(Math.E, -0.01 * delta) - 2.45 * Math.pow(10, -9) * delta
                + 7.92 * Math.pow(10, -5);
    }

    /**
     * Calculates the history component.
     *
     * @param eta The number of accesses
     * @return Returns the history component.
     */
    private double calcFHistory(double eta) {
        if (eta >= 500) {
            return 0.1377 * Math.log(0.2952 + 0.5 * eta) + 0.1683;
        } else {
            return 0.99;
        }
    }

    /**
     * Calculates the popularity component.
     * @param phi The video popularity
     * @return Returns the popularity component or 0 if it could not be calculated.
     */
    private double calcFPouplarity(double phi) {
        if (phi == -1) {
            return 0;
        }
        return 3.98 * Math.pow(10, -2) * Math.pow(Math.E, -6.66 * Math.pow(10, -8) * phi) + 2.4 * Math.pow(10, -6);
    }

    /**
     * Calculates the social component
     *
     * @param gamma The social parameter
     * @return Returns the social component.
     */
    private double calcFSocial(double gamma) {
        return 1.892 * Math.pow(10, -3) * Math.pow(Math.E, 5.641 * Math.pow(10, -2) * gamma)
                + 1.511 * Math.pow(10, -2);
    }

    /**
     * Transforms a prediction score to a probability
     *
     * @param score The popularity score
     * @return A probability for a given prediction score.
     */
    private double transformToProbability(double score) {
        return 1 / (1 + Math.pow(Math.E, score));
    }

    /**
     * Calculates the final score from a set of precalculated parameter values.
     *
     * @param fAge The age component
     * @param fDistance The popularity component
     * @param fHistory The history component
     * @param fPopularity The popularity component
     * @param fSocial The social component
     * @return The final score from a set of precalculated parameter values.
     */
    private double calculateFinalScore(double fAge, double fDistance, double fHistory, double fPopularity,
            double fSocial) {

        SocialPredictionParametersDAO sppDAO = DAOFactory.getSocialPredictionParametersDAO();
        SocialPredictionParameters parameters = sppDAO.findLast();

        /**
         * Calculation parameters
         */
        double lambda1;
        double lambda2;
        double lambda3;
        double lambda4;
        double lambda5;
        double lambda6;

        //If the values is not set in a database, set default values
        if (parameters == null) {
            logger.warn("Lambda i parameters seem not the be configured. Assuming default values.");
            lambda1 = -3646.8405;
            lambda2 = -348.238;
            lambda3 = -174.6666;
            lambda4 = 32.539;
            lambda5 = -1.0468;
            lambda6 = 1.8463;
            logger.info("Done.");
        } else {
            logger.info("Configuring lambda i...");
            lambda1 = parameters.getLambda1();
            lambda2 = parameters.getLambda2();
            lambda3 = parameters.getLambda3();
            lambda4 = parameters.getLambda4();
            lambda5 = parameters.getLambda5();
            lambda6 = parameters.getLambda6();
            logger.info("Done.");
        }

        return lambda1 * fAge + lambda2 * fDistance + lambda3 * fHistory + lambda4 * fPopularity + lambda5 * fSocial
                + lambda6;
    }

    /**
     * Helper class used for sorting Content items by prediction score.
     */
    private class ContentItem implements Comparable<ContentItem> {
        /**
         * The content ID of the Content.
         */
        private final long contentID;

        /**
         * The prediction score.
         */
        private final double score;

        /**
         * Creates a new ContentItem instance.
         * @param contentID content ID
         * @param score prediction score
         */
        private ContentItem(long contentID, double score) {
            this.contentID = contentID;
            this.score = score;
        }

        /**
         * Returns the content ID
         * @return Returns the content ID
         */
        public long getContentID() {
            return contentID;
        }

        /**
         * Returns the prediction score.
         * @return Returns the prediction score.
         */
        public double getScore() {
            return score;
        }

        @Override
        public int compareTo(ContentItem o) {
            if (this.getScore() < o.getScore()) {
                return -1;
            } else if (this.getScore() > o.getScore()) {
                return 1;
            }
            return 0;
        }
    }

    /**
     * Helper class for sorting unadas by distance.
     */
    private class UnadaInfoDistance {
        /**
         * The unada to be considered
         */
        private final UnadaInfo unadaInfo;

        /**
         * The distance in km.
         */
        private final double distance;

        /**
         * Creates a new UnadaInfoDistance instance.
         *
         * @param unadaInfo The unada to be considered
         * @param distance The distance in km.
         */
        private UnadaInfoDistance(UnadaInfo unadaInfo, double distance) {
            this.unadaInfo = unadaInfo;
            this.distance = distance;
        }

        /**
         * Returns the unada in question.
         * @return Returns the unada in question.
         */
        public UnadaInfo getUnadaInfo() {
            return unadaInfo;
        }

        /**
         * Returns the distance in km.
         * @return Returns the distance in km.
         */
        public double getDistance() {
            return distance;
        }
    }

}