File indexing completed on 2024-05-05 04:48:47

0001 /****************************************************************************************
0002  * Copyright (c) 2008-2012 Soren Harward <stharward@gmail.com>                          *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "APG::ConstraintSolver"
0018 
0019 
0020 #include "ConstraintSolver.h"
0021 
0022 #include "Constraint.h"
0023 
0024 #include "core/collections/QueryMaker.h"
0025 #include "core/meta/Meta.h"
0026 #include "core/support/Debug.h"
0027 #include "core/support/Components.h"
0028 #include "core/logger/Logger.h"
0029 #include "core-impl/collections/support/CollectionManager.h"
0030 #include "playlist/PlaylistModel.h"
0031 
0032 #include <KLocalizedString>
0033 #include <QRandomGenerator>
0034 
0035 #include <QHash>
0036 #include <QMutexLocker>
0037 #include <QRandomGenerator>
0038 #include <QStringList>
0039 #include <QTimer>
0040 #include <ThreadWeaver/ThreadWeaver>
0041 
0042 #include <algorithm> // STL algorithms
0043 #include <cmath>
0044 #include <typeinfo>
0045 
0046 const int APG::ConstraintSolver::QUALITY_RANGE = 10;
0047 
0048 APG::ConstraintSolver::ConstraintSolver( ConstraintNode* r, int qualityFactor )
0049         : QObject()
0050         , ThreadWeaver::Job()
0051         , m_satisfactionThreshold( 0.95 )
0052         , m_finalSatisfaction( 0.0 )
0053         , m_constraintTreeRoot( r )
0054         , m_domainReductionFailed( false )
0055         , m_readyToRun( false )
0056         , m_abortRequested( false )
0057         , m_maxGenerations( 100 )
0058         , m_populationSize( 40 )
0059         , m_suggestedPlaylistSize( 15 )
0060 {
0061     Q_UNUSED( qualityFactor); // FIXME
0062 
0063     m_serialNumber = QRandomGenerator::global()->generate();
0064 
0065     if ( !m_constraintTreeRoot ) {
0066         error() << "No constraint tree was passed to the solver.  Aborting.";
0067         m_readyToRun = true;
0068         m_abortRequested = true;
0069         return;
0070     }
0071 
0072     m_qm = CollectionManager::instance()->queryMaker();
0073     if ( m_qm ) {
0074         debug() << "New ConstraintSolver with serial number" << m_serialNumber;
0075         m_qm->setQueryType( Collections::QueryMaker::Track );
0076         connect( m_qm, &Collections::QueryMaker::newTracksReady,
0077                  this, &APG::ConstraintSolver::receiveQueryMakerData, Qt::QueuedConnection );
0078         connect( m_qm, &Collections::QueryMaker::queryDone,
0079                  this, &APG::ConstraintSolver::receiveQueryMakerDone, Qt::QueuedConnection );
0080         m_constraintTreeRoot->initQueryMaker( m_qm );
0081         m_qm->run();
0082     } else {
0083         debug() << "The ConstraintSolver could not find any queryable collections.  No results will be returned.";
0084         m_readyToRun = true;
0085         m_abortRequested = true;
0086     }
0087 }
0088 
0089 APG::ConstraintSolver::~ConstraintSolver()
0090 {
0091     if ( m_qm ) {
0092         m_qm->abortQuery();
0093         m_qm->deleteLater();
0094         m_qm = nullptr;
0095     }
0096 }
0097 
0098 Meta::TrackList
0099 APG::ConstraintSolver::getSolution() const
0100 {
0101     return m_solvedPlaylist;
0102 }
0103 
0104 bool
0105 APG::ConstraintSolver::satisfied() const
0106 {
0107     return m_finalSatisfaction > m_satisfactionThreshold;
0108 }
0109 
0110 bool
0111 APG::ConstraintSolver::canBeExecuted()
0112 {
0113 
0114     /* This is a hopefully superfluous check to ensure that the Solver job
0115      * doesn't get run until it's ready (ie, when QueryMaker has finished).
0116      * This shouldn't ever return false, because hopefully the job won't even
0117      * get queued until it's ready to run.  See the comments in
0118      * Preset::queueSolver() for more information. -- sth */
0119 
0120     return m_readyToRun;
0121 }
0122 
0123 void
0124 APG::ConstraintSolver::requestAbort()
0125 {
0126     if ( m_qm ) {
0127         m_qm->abortQuery();
0128         m_qm->deleteLater();
0129         m_qm = nullptr;
0130     }
0131     m_abortRequested = true;
0132 }
0133 
0134 bool
0135 APG::ConstraintSolver::success() const
0136 {
0137     return !m_abortRequested;
0138 }
0139 
0140 void
0141 APG::ConstraintSolver::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
0142 {
0143     Q_UNUSED(self);
0144     Q_UNUSED(thread);
0145 
0146     if ( !m_readyToRun ) {
0147         error() << "DANGER WILL ROBINSON!  A ConstraintSolver (serial no:" << m_serialNumber << ") tried to run before its QueryMaker finished!";
0148         m_abortRequested = true;
0149         return;
0150     }
0151 
0152     if ( m_domain.empty() ) {
0153         debug() << "The QueryMaker returned no tracks";
0154         return;
0155     } else {
0156         debug() << "Domain has" << m_domain.size() << "tracks";
0157     }
0158 
0159     debug() << "Running ConstraintSolver" << m_serialNumber;
0160 
0161     Q_EMIT totalSteps( m_maxGenerations );
0162 
0163     // GENETIC ALGORITHM LOOP
0164     Population population;
0165     quint32 generation = 0;
0166     Meta::TrackList* best = nullptr;
0167     while ( !m_abortRequested && ( generation < m_maxGenerations ) ) {
0168         quint32 s = m_constraintTreeRoot->suggestPlaylistSize();
0169         m_suggestedPlaylistSize = (s > 0) ? s : m_suggestedPlaylistSize;
0170         fill_population( population );
0171         best = find_best( population );
0172         if ( population.value( best ) < m_satisfactionThreshold ) {
0173             select_population( population, best );
0174             mutate_population( population );
0175             generation++;
0176             Q_EMIT incrementProgress();
0177         } else {
0178             break;
0179         }
0180     }
0181 
0182     if( best )
0183     {
0184         debug() << "solution at" << (void*)(best);
0185 
0186         m_solvedPlaylist = best->mid( 0 );
0187         m_finalSatisfaction = m_constraintTreeRoot->satisfaction( m_solvedPlaylist );
0188     }
0189 
0190     /* clean up */
0191     Population::iterator it = population.begin();
0192     while ( it != population.end() ) {
0193         delete it.key();
0194         it = population.erase( it );
0195     }
0196 
0197     Q_EMIT endProgressOperation( this );
0198 }
0199 
0200 void APG::ConstraintSolver::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
0201 {
0202     Q_EMIT started(self);
0203     ThreadWeaver::Job::defaultBegin(self, thread);
0204 }
0205 
0206 void APG::ConstraintSolver::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
0207 {
0208     ThreadWeaver::Job::defaultEnd(self, thread);
0209     if (!self->success()) {
0210         Q_EMIT failed(self);
0211     }
0212     Q_EMIT done(self);
0213 }
0214 
0215 void
0216 APG::ConstraintSolver::receiveQueryMakerData( const Meta::TrackList &results )
0217 {
0218     m_domainMutex.lock();
0219     m_domain += results;
0220     m_domainMutex.unlock();
0221 }
0222 
0223 void
0224 APG::ConstraintSolver::receiveQueryMakerDone()
0225 {
0226     m_qm->deleteLater();
0227     m_qm = nullptr;
0228 
0229     if (( m_domain.size() > 0 ) || m_domainReductionFailed ) {
0230         if ( m_domain.size() <= 0 ) {
0231             Amarok::Logger::shortMessage( i18n("The playlist generator failed to load any tracks from the collection.") );
0232         }
0233         m_readyToRun = true;
0234         Q_EMIT readyToRun();
0235     } else {
0236         Amarok::Logger::longMessage(
0237                     i18n("There are no tracks that match all constraints. " \
0238                          "The playlist generator will find the tracks that match best, " \
0239                 "but you may want to consider loosening the constraints to find more tracks.") );
0240         m_domainReductionFailed = true;
0241 
0242         // need a new query maker without constraints
0243         m_qm = CollectionManager::instance()->queryMaker();
0244         if ( m_qm ) {
0245             connect( m_qm, &Collections::QueryMaker::newTracksReady,
0246                      this, &APG::ConstraintSolver::receiveQueryMakerData, Qt::QueuedConnection );
0247             connect( m_qm, &Collections::QueryMaker::queryDone,
0248                      this, &APG::ConstraintSolver::receiveQueryMakerDone, Qt::QueuedConnection );
0249 
0250             m_qm->setQueryType( Collections::QueryMaker::Track );
0251             m_qm->run();
0252         }
0253     }
0254 }
0255 
0256 void
0257 APG::ConstraintSolver::fill_population( Population& population )
0258 {
0259     for ( int i = population.size(); quint32(i) < m_populationSize; i++ ) {
0260         Meta::TrackList* tl = new Meta::TrackList( sample( m_domain, playlist_size() ) );
0261         double s = m_constraintTreeRoot->satisfaction( (*tl) );
0262         population.insert( tl, s );
0263     }
0264 }
0265 
0266 Meta::TrackList* APG::ConstraintSolver::find_best(const APG::ConstraintSolver::Population& population ) const
0267 {
0268     Population::const_iterator it = std::max_element( population.constBegin(), population.constEnd(), &pop_comp );
0269     return it.key();
0270 }
0271 
0272 void
0273 APG::ConstraintSolver::select_population( APG::ConstraintSolver::Population& population, Meta::TrackList* best )
0274 {
0275     Population::Iterator it = population.begin();
0276     while ( it != population.end() ) {
0277         if ( it.key() == best ) {
0278             ++it;// Always keep the best solution, no matter how bad it is
0279             if ( it == population.end() )
0280                 break;
0281         }
0282         
0283         if ( select( it.value() ) ) {
0284             ++it;
0285         } else {
0286             delete it.key();
0287             it = population.erase( it );
0288         }
0289     }
0290 }
0291 
0292 void
0293 APG::ConstraintSolver::mutate_population( APG::ConstraintSolver::Population& population )
0294 {
0295     if ( population.size() < 1 )
0296         return;
0297     
0298     const double mutantPercentage = 0.35; // TODO: tune this parameter
0299     
0300     QList<Meta::TrackList*> parents( population.keys() );
0301     int maxMutants = (int)( mutantPercentage * (double)(m_populationSize) );
0302     for ( int i = parents.size(); i < maxMutants; i++ ) {
0303         int idx = QRandomGenerator::global()->generate() % parents.size();
0304         Meta::TrackList* child = new Meta::TrackList( *(parents.at( idx )) );
0305         int op = QRandomGenerator::global()->generate() % 5;
0306         int s = child->size();
0307         switch (op) {
0308             case 0:
0309                 child->removeAt( QRandomGenerator::global()->generate() % s );
0310                 break;
0311             case 1:
0312                 child->insert( QRandomGenerator::global()->generate() % ( s + 1 ), random_track_from_domain() );
0313                 break;
0314             case 2:
0315                 child->replace( QRandomGenerator::global()->generate() % s, random_track_from_domain() );
0316                 break;
0317             case 3:
0318                 child->swapItemsAt( QRandomGenerator::global()->generate() % s, QRandomGenerator::global()->generate() % s );
0319                 break;
0320             case 4:
0321                 child = crossover( child, parents.at( QRandomGenerator::global()->generate() % parents.size() ) );
0322                 break;
0323             default:
0324                 (void)0; // effectively a no-op. the default is here so that the compiler doesn't complain about missing default in switch
0325         }
0326         population.insert( child, m_constraintTreeRoot->satisfaction( *child ) );
0327     }
0328     return;
0329 }
0330 
0331 Meta::TrackList*
0332 APG::ConstraintSolver::crossover( Meta::TrackList* top, Meta::TrackList* bot ) const
0333 {
0334     const double crossoverPt = 0.5; // TODO: choose different values
0335 
0336     int topV = (int)( crossoverPt * (double)top->size() );
0337     int botV = (int)( crossoverPt * (double)bot->size() );
0338 
0339     Meta::TrackList* newlist = new Meta::TrackList( top->mid( 0, topV ) );
0340     newlist->append( bot->mid( botV ) );
0341 
0342     delete top;
0343     return newlist;
0344 }
0345 
0346 bool
0347 APG::ConstraintSolver::pop_comp( double a, double b )
0348 {
0349     return ( a < b );
0350 }
0351 
0352 Meta::TrackPtr
0353 APG::ConstraintSolver::random_track_from_domain() const
0354 {
0355     return m_domain.at( QRandomGenerator::global()->generate() % m_domain.size() );
0356 }
0357 
0358 Meta::TrackList
0359 APG::ConstraintSolver::sample( Meta::TrackList domain, const int sampleSize ) const
0360 {
0361     std::random_shuffle( domain.begin(), domain.end() );
0362     return domain.mid( 0, sampleSize );
0363 }
0364 
0365 quint32
0366 APG::ConstraintSolver::playlist_size() const
0367 {
0368     return rng_poisson( (double)m_suggestedPlaylistSize );
0369 }
0370 
0371 bool
0372 APG::ConstraintSolver::select( const double satisfaction ) const
0373 {
0374     double x = (double)QRandomGenerator::global()->generate()/(double)RAND_MAX;
0375     const double scale = -30.0; // TODO: make adjustable
0376     return ( x < 1.0 / ( 1.0 + exp( scale * (satisfaction-0.8) ) ) );
0377 }
0378 
0379 void
0380 APG::ConstraintSolver::dump_population( const Population& population ) const
0381 {
0382     DEBUG_BLOCK
0383     for ( Population::ConstIterator it = population.constBegin(); it != population.constEnd(); ++it ) {
0384         Meta::TrackList* tl = it.key();
0385         debug() << "at" << (void*)(tl) << "satisfaction:" << it.value();
0386         foreach ( Meta::TrackPtr t, (*tl) ) {
0387             debug() << "\ttrack:" << t->prettyName();
0388         }
0389     }
0390 }
0391 
0392 double
0393 APG::ConstraintSolver::rng_gaussian( const double mu, const double sigma ) const
0394 {
0395     /* adapted from randist/gauss.c in GNU Scientific Library 1.14 */
0396     double u, v, x, y, Q;
0397     const double  s =  0.449871;
0398     const double  t = -0.386595;
0399     const double  a =  0.19600;
0400     const double  b =  0.25472;
0401     const double r1 =  0.27597;
0402     const double r2 =  0.27846;
0403 
0404     do {
0405         u = 1 - rng_uniform();
0406         v = ( rng_uniform() - 0.5 ) * 1.7156;
0407         x = u - s;
0408         y = fabs (v) - t;
0409         Q = x * x + y * (a * y - b * x);
0410     } while (Q >= r1 && (Q > r2 || v * v > -4 * u * u * log (u)));
0411 
0412     return mu + ( sigma * (v / u) );
0413 }
0414 
0415 quint32
0416 APG::ConstraintSolver::rng_poisson( const double mu ) const
0417 {
0418     if ( mu >= 25.0 ) {
0419         double v = rng_gaussian( mu, sqrt( mu ) );
0420         return ( v < 0.0 ) ? 0 : (quint32)v;
0421     }
0422 
0423     const double emu = exp( -mu );
0424     double prod = 1.0;
0425     quint32 k = 0;
0426 
0427     do {
0428         prod *= rng_uniform();
0429         k++;
0430     }
0431     while ( prod > emu );
0432 
0433     return k - 1;
0434 }
0435 
0436 double
0437 APG::ConstraintSolver::rng_uniform() const
0438 {
0439     return ( (double)QRandomGenerator::global()->generate() / (double)(RAND_MAX) );
0440 }