Fix several problems with new DynamicBias
[amarok:rengelss-amarok.git] / src / dynamic / BiasSolver.h
1 /****************************************************************************************
2  * Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com>                       *
3  * Copyright (c) 2010 Ralf Engels <ralf-engels@gmx.de>                                  *
4  *                                                                                      *
5  * This program is free software; you can redistribute it and/or modify it under        *
6  * the terms of the GNU General Public License as published by the Free Software        *
7  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
8  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
9  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
10  * version 3 of the license.                                                            *
11  *                                                                                      *
12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
14  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
15  *                                                                                      *
16  * You should have received a copy of the GNU General Public License along with         *
17  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
18  ****************************************************************************************/
19
20 #ifndef AMAROK_BIASSOLVER_H
21 #define AMAROK_BIASSOLVER_H
22
23 #include "Bias.h"
24 #include "core/meta/Meta.h"
25 #include "TrackSet.h"
26
27 #include <threadweaver/Job.h>
28
29 #include <QMutex>
30 #include <QWaitCondition>
31
32 namespace Dynamic
33 {
34
35     /** A playlist/energy pair, used by ga_optimize to sort lists of playlists by their energy.
36     */
37     class SolverList;
38
39     /**
40      * A class to implement the optimization algorithm used to generate
41      * playlists from a set of biases. The method used here is slightly
42      * complicated. It uses a a combination of heuristics, genetic algorithms,
43      * and simulated annealing. The steps involved are documented in the source
44      * code of BiasSolver::run.
45
46        The whole operation is a little bit tricky since the bias solver runs as
47        seperate job without it's own event queue.
48
49        The signals and slots are all handled via the UI threads event queue.
50        Since the BiasSolver and all biases are created from the UI thread this
51        is also the event queue of all objects.
52        Bottom line: don't create objects in the bias solver that use signals.
53
54
55      * Playlist generation is treated as a minimization problem. We try to
56      * produce a playlist with the lowest "energy", which can be thought of as
57      * the badness of the playlist. High energy means the biases are poorly
58      * satisfied, low energy means they are well satisfied. The energy value
59      * is an average of all the energy values from all the bias in the system.
60      *
61      * For a better, more comprehensive description of the problem of playlist
62      * generation, read M.P.H. Vossen's masters thesis: "Local Search for
63      * Automatic Playlist Generation."
64      *
65      * To use the solver:
66      * Create an instance
67      * enqueue the job. When the job is finished, call solution to get the
68      * playlist produced.
69      */
70     class BiasSolver : public ThreadWeaver::Job
71     {
72         Q_OBJECT
73
74         public:
75
76             /**
77              * Create and prepare the solver. The constructor returns
78              *
79              * @param n The size of the playlist to generate.
80              * @param biases The system of biases being applied.
81              * @param context The tracks (if any) that precede the playlist
82              * being generated.
83              */
84             BiasSolver( int n, BiasPtr bias, Meta::TrackList context );
85
86             ~BiasSolver();
87
88             /// Returns the playlist generated after the job has finished.
89             Meta::TrackList solution();
90
91             /// Politely asks the thread to give up and finish ASAP.
92             void requestAbort();
93
94             /**
95              * Returns true if the solver was successful, false if it was
96              * aborted or encountered some other error.
97              */
98             bool success() const;
99
100             /**
101              * Choose whether the BiasSolver instance should delete itself after the query.
102              * By passing true the instance will delete itself after emitting done, failed.
103              * Otherwise it is the responsibility of the owner to delete the instance
104              * when it is not needed anymore.
105              *
106              * Defaults to false, i.e. the BiasSolver instance will not delete itself.
107              */
108             void setAutoDelete( bool autoDelete );
109
110             /**
111              * Return the universe set (a list of the uid of every track being
112              * considered) being used.
113              */
114             static const QList<QByteArray>& universe();
115
116             /**
117              * Mark the universe set as out of date (i.e. it needs to be
118              * updated).
119              */
120             static void outdateUniverse();
121
122         protected:
123             void run();
124
125         signals:
126             /// A [0,100] value emitted occasionally to indicate progress being made.
127             void statusUpdate( int progress );
128
129         private slots:
130             void biasResultReady( const Dynamic::TrackSet &set );
131
132             void trackCollectionResultsReady( QString collectionId, QStringList );
133             void trackCollectionDone();
134
135
136         private:
137             double epsilon() const
138             { return 0.05; }
139
140             /** Returns the TrackSet of tracks fitting in the indicated position.
141                 The function blocks until the result is received */
142             TrackSet matchingTracks( int position, const Meta::TrackList& playlist ) const;
143
144             /** Query for the universe set (the set of all tracks in the collection being considered.
145                 This function needs to be called from a thread with an event loop.
146             */
147             void getTrackCollection();
148
149
150             /**
151              * Try to produce better playlist by replacing all tracks by tracks that fulfill the bias
152              */
153             void simpleOptimize( SolverList *list );
154
155             /**
156              * Optimize a playlist using simulated annealing.
157              * If the given initial playlist is already optimal it does not do anything.
158              *
159              * @param initialPlaylist The starting playlist for the algorithm.
160              * @param iterationLimit Maximum iterations allowed.
161              * @param updateStatus Enable/disable statusUpdate signals.
162              */
163             void annealingOptimize( SolverList *list, int iterationLimit, bool updateStatus = true );
164
165             /**
166              * Try to produce an optimal playlist using a genetic algorithm, and
167              * return the best playlist produced.
168              *
169              * @param iterationLimit Maximum iterations allowed.
170              * @param updateStatus Enable/disable statusUpdate signals.
171              */
172             void geneticOptimize( SolverList *list, int iterationLimit, bool updateStatus = true );
173
174             /** Returns a simple random playlist without caring about any bias */
175             SolverList generateInitialPlaylist() const;
176
177             /**
178              * Get the track referenced by the uid stored in the given
179              * QByteArray.
180              * @param uid The uid
181              */
182             Meta::TrackPtr trackForUid( const QString& uid ) const;
183
184             /**
185              * Return a random track from the domain.
186              */
187             Meta::TrackPtr getMutation();
188
189             /**
190              * Return a random track from the given subset.
191              * @param subset A list (representing a set) of uids stored in
192              * QByteArrays.
193              */
194             Meta::TrackPtr getRandomTrack( const TrackSet& subset ) const;
195
196
197             /**
198              * Used each iteration of the genetic algorithm. Choose
199              * MATING_POPULATION_SIZE playlists from the given population to
200              * produce offspring.
201              */
202             QList<int> generateMatingPopulation( const QList<SolverList>& );
203
204             /** Returns a TrackSet with all duplicates removed (except the one at "position")
205                 This helper function can be used in special cases if needed and
206                 AmarokConfig::dynamicDuplicates() == false
207                 Normally the BiasSolver will call it at for the top most bias.
208                 @param onlyBackwards if true it will not consider tracks after "position"
209             */
210             static TrackSet withoutDuplicate( int position,
211                                               const Meta::TrackList& playlist,
212                                               const Dynamic::TrackSet& oldSet,
213                                               bool onlyBackwards = true );
214
215             int m_n;                    //!< size of playlist to generate
216             Dynamic::BiasPtr m_bias; // bias used to determine tracks. not owned by solver
217             Meta::TrackList m_context;  //!< tracks that precede the playlist
218
219             Meta::TrackList m_solution;
220             Meta::TrackList m_mutationPool; //!< a queue of tracks used by getMutation
221
222             bool m_abortRequested; //!< flag set when the thread is aborted
223
224             mutable QMutex m_biasResultsMutex;
225             mutable QWaitCondition m_biasResultsReady;
226             mutable Dynamic::TrackSet m_tracks; // tracks just received from the bias.
227
228             mutable QMutex m_collectionResultsMutex;
229             mutable QWaitCondition m_collectionResultsReady;
230
231             /** All uids of all the tracks in the collection */
232             QStringList m_collectionUids;
233             TrackCollectionPtr m_trackCollection;
234
235             bool m_allowDuplicates;
236
237
238             // GENETIC ALGORITHM CONSTANTS
239
240             static const int    GA_ITERATION_LIMIT;        //!< iteration limit for the genetic phase
241             static const int    GA_POPULATION_SIZE;        //!< population size for genetic phase
242             static const int    GA_MATING_POPULATION_SIZE; //!< how many offspring are produced each generation
243             static const double GA_MUTATION_PROBABILITY;   //!< the chance that an offspring gets mutated
244             static const int    GA_GIVE_UP_LIMIT;          //!< if we can't reduce the energy after this many iterations, give up
245
246             // SIMULATE ANNEALING CONSTANTS
247
248             static const int    SA_ITERATION_LIMIT;     //!< iteration limit for the annealing phase
249             static const double SA_INITIAL_TEMPERATURE; //!< initial value of T
250             static const double SA_COOLING_RATE;        //!< the factor by which T is multiplied each iteration.
251             static const int    SA_GIVE_UP_LIMIT;       //!< if we con't reduce the energy after this many iterations, give up
252
253     };
254
255 }
256
257 #endif