/** * Adds songs which did not get any rating data assigned for a long time (or never) to the * {proposedSongs} list. This ensures reaching regions in the music space which are not explored * yet (or have been forgotten about).<br> * The agents vote for these songs is set to {@value #LONG_NOT_RATED_SONG_VOTE}. * * @param proposedSongs The proposal list * @param agentVotes The votes list */ private void addLongTimeNotListenedSongProposals() { // Fetch long not rated songs List<BaseSong<BaseArtist, BaseAlbum>> longNotRatedSongs; try { longNotRatedSongs = statisticsProvider.getLongNotRatedSongs( LONG_NOT_RATED_SONG_COUNT, LONG_NOT_RATED_THRESHOLD); } catch (DataUnavailableException e) { // Just ignore the warning and do not add any songs Log.w(getTag(), e); longNotRatedSongs = new LinkedList<BaseSong<BaseArtist, BaseAlbum>>(); } // Prepare agent votes for songs Map<IAgent, Float> agentVotesForProposedSongs = new HashMap<IAgent, Float>(); for (IAgent agent : agentManager.getAgents()) { agentVotesForProposedSongs.put(agent, (float) LONG_NOT_RATED_SONG_VOTE); } // Add long not played songs as proposals for (BaseSong<BaseArtist, BaseAlbum> song : longNotRatedSongs) { proposedSongs.add(song); songVotes.put(song, LONG_NOT_RATED_SONG_VOTE); agentVotesBySong.put(song, agentVotesForProposedSongs); } }
/** * Returns the set of songs which are proposed by the agents. * * @param proposalTimes * @return The songs */ private List<BaseSong<BaseArtist, BaseAlbum>> getProposedSongs(AgentsTiming proposalTimes) { Set<IAgent> agents = agentManager.getAgents(); // Using set here to ensure no duplicate entries Set<BaseSong<BaseArtist, BaseAlbum>> proposedSongs = new HashSet<BaseSong<BaseArtist, BaseAlbum>>(SONG_PROPOSAL_USAGE_COUNT * agents.size()); for (IAgent agent : agents) { throwIfAborted(); StopWatch stopWatch = StopWatch.start(); List<BaseSong<BaseArtist, BaseAlbum>> agentProposals = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(agent.suggestSongs(SONG_PROPOSAL_COUNT)); // Get #SONG_PROPOSAL_USAGE_COUNT items of them at random while (agentProposals.size() > SONG_PROPOSAL_USAGE_COUNT) { agentProposals.remove(RandomProvider.getRandom().nextInt(agentProposals.size())); } proposedSongs.addAll(agentProposals); proposalTimes.addAgentTiming(agent, stopWatch.stop()); } return new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(proposedSongs); }
/** * Returns the average votes of the agents for the given songs. The votes gets weighted by the * importance of the agents. * * @param proposedSongs The proposed songs * @param agentsVotes (out) The votes for all the songs by agent * @param voteTimes * @return The weighted mean votes for the songs */ private Map<BaseSong<BaseArtist, BaseAlbum>, Double> getVotesForProposed( List<BaseSong<BaseArtist, BaseAlbum>> proposedSongs, Map<IAgent, List<SongVote>> agentsVotes, AgentsTiming voteTimes) { Set<IAgent> agents = agentManager.getAgents(); // Init the ratings list & the weight sum table Map<BaseSong<BaseArtist, BaseAlbum>, Double> votes = new HashMap<BaseSong<BaseArtist, BaseAlbum>, Double>(); Map<BaseSong<BaseArtist, BaseAlbum>, Double> weightSums = new HashMap<BaseSong<BaseArtist, BaseAlbum>, Double>(agents.size()); for (BaseSong<BaseArtist, BaseAlbum> song : proposedSongs) { votes.put(song, 0.0d); weightSums.put(song, 0.0d); } // Calculate the song ratings for (IAgent agent : agents) { throwIfAborted(); // Get the agent vote List<SongVote> agentVotes; StopWatch stopWatch = StopWatch.start(); { agentVotes = agent.vote(Collections.unmodifiableList(proposedSongs)); } voteTimes.addAgentTiming(agent, stopWatch.stop()); // Fill up the list to ensure that a vote for all songs exist List<BaseSong<BaseArtist, BaseAlbum>> noVote = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(proposedSongs); for (int i = 0; i < agentVotes.size(); ) { final SongVote vote = agentVotes.get(i); int idx = noVote.indexOf(vote.getSong()); if (idx >= 0) { noVote.remove(idx); ++i; } else { // No vote for this song is required agentVotes.remove(i); } } for (BaseSong<BaseArtist, BaseAlbum> song : noVote) { agentVotes.add(new SongVote(song, 0.0f)); } agentsVotes.put(agent, agentVotes); for (SongVote vote : agentVotes) { final BaseSong<BaseArtist, BaseAlbum> song = vote.getSong(); double oldRating = votes.get(song); double agentWeight = getAgentWeights().get(agent); double oldW = weightSums.get(song); double newW = oldW + agentWeight; double newRating = (oldRating * oldW + vote.getVote() * agentWeight) / newW; // Continuous, weighted mean calculation votes.put(song, newRating); weightSums.put(song, newW); } } return votes; }
@Override public void run() { try { throwIfAborted(); TimingLogger timingLogger = new TimingLogger(getTag(), "next"); // Begin an immediate transaction dbDataPortal.beginTransaction(); timingLogger.addSplit("Transaction start"); Set<IAgent> agents = agentManager.getAgents(); agentWeights = agentManager.getAgentWeights(); // Enter a fake rating entry to calculate the next song on the predicted future Integer meSongId = null; if (currentSong != null) { double fractionPlayed = (getCalculationCase() == Case.Positive) ? 0.66d : 0.33d; playLog.writeToPlayLog( new Date(), new PlaylistSong<BaseArtist, BaseAlbum>(currentSong, SongSource.SMART_SHUFFLE), true, (int) (currentSong.getDuration() * fractionPlayed)); try { meSongId = otherDataProvider.getMusicExplorerSongId(currentSong); } catch (DataUnavailableException e) { Log.w(getTag(), e); } } timingLogger.addSplit("Rating entry"); // Get the song proposals AgentsTiming proposalTimes = new AgentsTiming(); { proposedSongs = getProposedSongs(proposalTimes); } timingLogger.addSplit("Proposals"); // Get the agent votes for the proposals Map<IAgent, List<SongVote>> agentVotes; AgentsTiming voteTimes = new AgentsTiming(); { agentVotes = new HashMap<IAgent, List<SongVote>>(); songVotes = getVotesForProposed(proposedSongs, agentVotes, voteTimes); } timingLogger.addSplit("Votes"); // Fill the song->agentVotes map agentVotesBySong = new HashMap<BaseSong<BaseArtist, BaseAlbum>, Map<IAgent, Float>>(proposedSongs.size()); for (BaseSong<BaseArtist, BaseAlbum> song : proposedSongs) { agentVotesBySong.put(song, new HashMap<IAgent, Float>(agents.size())); } for (Map.Entry<IAgent, List<SongVote>> entry : agentVotes.entrySet()) { for (SongVote vote : entry.getValue()) { Map<IAgent, Float> agentVote = agentVotesBySong.get(vote.getSong()); agentVote.put(entry.getKey(), vote.getVote()); } } // Add long time not listened songs addLongTimeNotListenedSongProposals(); /*// Order the next songs by rating Collections.sort(proposedSongs, new Comparator<BaseSong<BaseArtist, BaseAlbum>>() { @Override public int compare(BaseSong<BaseArtist, BaseAlbum> left, BaseSong<BaseArtist, BaseAlbum> right) { Double leftVote = songVotes.get(left); Double rightVote = songVotes.get(right); return rightVote.compareTo(leftVote); } });*/ // Randomize the song proposals by their vote proposedSongs = getShuffledSongsAtWeightedRandom(songVotes); // FIXME @sämy: this is only for debugging StringBuffer proposalsSb = new StringBuffer(); proposalsSb.append("song ident:overall vote"); for (IAgent agent : agents) { proposalsSb.append(':'); proposalsSb.append(agent.getIdentifier()); proposalsSb.append(String.format(" (%.2f)", agentWeights.get(agent))); } proposalsSb.append('|'); for (BaseSong<BaseArtist, BaseAlbum> song : proposedSongs) { proposalsSb.append(song); // song ident proposalsSb.append(':'); proposalsSb.append(String.format("%.3f", songVotes.get(song))); // overall vote Map<IAgent, Float> agentVotes2 = agentVotesBySong.get(song); for (IAgent agent : agents) { proposalsSb.append(String.format(":%.3f", agentVotes2.get(agent))); } proposalsSb.append('|'); } Log.d(getTag(), "Proposals: " + proposalsSb.toString()); // end debug only // never ever call dbDataPortal.setTransactionSucessful() !! dbDataPortal .endTransaction(); // ROLLBACK the transaction. Things below that line get written // persistently to the db! // Write a log entry about this run NextSongCalculationLogEntry.Builder log = NextSongCalculationLogEntry.createInstance() .setPredictionCase(getCalculationCase()) .setCurrentSong((meSongId != null) ? meSongId : 0) .setProposalTimes(proposalTimes) .setVoteTimes(voteTimes); logManager.addLogEntry(log.build()); // Write the time used in the different parts to the debug log timingLogger.dumpToLog(); } catch (AbortException e) { // Just ignore it, we want to land here } finally { if (dbDataPortal.inTransaction()) { dbDataPortal.endTransaction(); // never ever call dbDataPortal.setTransactionSucessful() !! } } }