1 /***************************************************************************
2                           timetablegeneratemultipleform.cpp  -  description
3                              -------------------
4     begin                : Aug 20, 2007
5     copyright            : (C) 2007 by Lalescu Liviu
6     email                : Please see https://lalescu.ro/liviu/ for details about contacting Liviu Lalescu (in particular, you can find here the e-mail address)
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software: you can redistribute it and/or modify  *
12  *   it under the terms of the GNU Affero General Public License as        *
13  *   published by the Free Software Foundation, either version 3 of the    *
14  *   License, or (at your option) any later version.                       *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "longtextmessagebox.h"
19 
20 #include "generate.h"
21 
22 #include "timetablegeneratemultipleform.h"
23 #include "timetable_defs.h"
24 #include "timetable.h"
25 #include "fet.h"
26 #include "timetableexport.h"
27 
28 #include "rules.h"
29 
30 #include <ctime>
31 
32 #include <QMessageBox>
33 
34 //#include <QMutex>
35 
36 #include <QScrollBar>
37 
38 #include <QVBoxLayout>
39 
40 #include <QDir>
41 
42 #include <QApplication>
43 #include <QtGlobal>
44 
45 #include <QProcess>
46 #include <QTimer>
47 
48 #include <QDate>
49 #include <QTime>
50 #include <QLocale>
51 #include <QString>
52 
53 #include <QSemaphore>
54 
55 #include <QSettings>
56 
57 #include <QThread> //only for QThread::idealThreadCount()
58 
59 #include <mutex>
60 //#include <condition_variable>
61 
62 //extern QMutex myMutex;
63 
64 //Old comment below:
65 //The 'static' qualifiers were commented out below (in 8 places) to address possible crashes on some platforms.
66 //static QMutex fitnessMutexInThreads;
67 static std::mutex fitnessMutexInThreads;
68 
69 extern const QString COMPANY;
70 extern const QString PROGRAM;
71 
72 extern Generate gen;
73 
74 //static Matrix1D<Worker> generateMultipleWorker;
75 //static Matrix1D<QThread> generateMultipleThread;
76 static int allNThreads;
77 static Matrix1D<QSemaphore> semaphoreTimetableStarted;
78 static Matrix1D<QSemaphore> semaphoreTimetableFinished;
79 /*static Matrix1D<std::binary_semaphore> semaphoreTimetableStarted;
80 static Matrix1D<std::binary_semaphore> semaphoreTimetableFinished;*/
81 /*static Matrix1D<std::condition_variable> cvTimetableStarted;
82 static Matrix1D<std::condition_variable> cvTimetableFinished;*/
83 
84 //static QSemaphore semaphoreTimetableFinished;
85 
86 //static QSemaphore semaphoreTimetableStarted;
87 
88 //Represents the current status of the simulation - running or stopped.
89 extern bool simulation_running_multi;
90 
91 //extern QSemaphore semaphorePlacedActivity;
92 
93 static Matrix1D<Generate> genMultiMatrix;
94 //Generate genMulti;
95 
96 static int nTimetables;
97 static int timeLimit;
98 
99 extern Solution best_solution;
100 
101 extern QString conflictsStringTitle;
102 extern QString conflictsString;
103 
104 static Matrix1D<time_t> process_start_time;
105 
106 static Matrix1D<TimetablingThread> timetablingThreads;
107 
startGenerating()108 void TimetablingThread::startGenerating()
109 {
110 	//time_t start_time;
111 
112 	genMultiMatrix[_nThread].abortOptimization=false;
113 
114 	//time(&start_time);
115 	time(&process_start_time[_nThread]);
116 
117 	bool impossible;
118 	bool timeExceeded;
119 
120 	//emit(timetableStarted(_nThread/*, nOverallTimetable+1*/));
121 	//semaphoreTimetableStarted[_nThread].acquire();
122 
123 	genMultiMatrix[_nThread].generate(timeLimit, impossible, timeExceeded, true); //true means threaded
124 	QString s;
125 
126 	bool ok;
127 
128 	//genMultiMatrix[_nThread].myMutex.lock();
129 	if(genMultiMatrix[_nThread].abortOptimization){
130 		//genMultiMatrix[_nThread].myMutex.unlock();
131 		//cout<<"returning, because abortOptimization of _nThread="<<_nThread<<endl;
132 		return;
133 	}
134 
135 	if(allNThreads>=2)
136 		s=tr("(Thread %1)").arg(_nThread+1)+QString(" ");
137 	else
138 		s=QString("");
139 
140 	if(impossible){
141 		s+=tr("Timetable impossible to generate");
142 		s+=QString(".");
143 		ok=false;
144 	}
145 	else if(timeExceeded){
146 		s+=tr("Time exceeded for current timetable");
147 
148 		////////2011-05-26
149 		int mact=genMultiMatrix[_nThread].maxActivitiesPlaced;
150 		int mseconds=genMultiMatrix[_nThread].timeToHighestStage;
151 
152 		bool zero=false;
153 		if(mseconds==0)
154 			zero=true;
155 		int hh=mseconds/3600;
156 		mseconds%=3600;
157 		int mm=mseconds/60;
158 		mseconds%=60;
159 		int ss=mseconds;
160 		QString tim;
161 		if(hh>0){
162 			tim+=" ";
163 			tim+=tr("%1 h", "hours").arg(hh);
164 		}
165 		if(mm>0){
166 			tim+=" ";
167 			tim+=tr("%1 m", "minutes").arg(mm);
168 		}
169 		if(ss>0 || zero){
170 			tim+=" ";
171 			tim+=tr("%1 s", "seconds").arg(ss);
172 		}
173 		tim.remove(0, 1);
174 		s+=QString(". ");
175 		s+=tr("Max placed activities: %1 (at %2)", "%1 represents the maximum number of activities placed, %2 is a time interval").arg(mact).arg(tim);
176 		///////
177 
178 		s+=QString(".");
179 
180 		ok=false;
181 	}
182 	else{
183 		ok=true;
184 
185 		time_t finish_time;
186 		time(&finish_time);
187 		int seconds=int(difftime(finish_time, process_start_time[_nThread]));
188 		int hours=seconds/3600;
189 		seconds%=3600;
190 		int minutes=seconds/60;
191 		seconds%=60;
192 
193 		FakeString tmp;
194 		fitnessMutexInThreads.lock();
195 		genMultiMatrix[_nThread].c.fitness(gt.rules, &tmp);
196 		fitnessMutexInThreads.unlock();
197 
198 		s+=tr("Timetable breaks %1 soft constraints, has %2 soft conflicts total, and was generated in %3 hours, %4 minutes and %5 seconds.")
199 		 .arg(genMultiMatrix[_nThread].c.conflictsWeightList.count())
200 		 .arg(CustomFETString::numberPlusTwoDigitsPrecision(genMultiMatrix[_nThread].c.conflictsTotal))
201 		 .arg(hours)
202 		 .arg(minutes)
203 		 .arg(seconds);
204 	}
205 	//genMultiMatrix[_nThread].myMutex.unlock();
206 	//emit(resultReady(_nThread, s, ok));
207 
208 	emit(timetableGenerated(_nThread, nOverallTimetable+1, s, ok));
209 	semaphoreTimetableFinished[_nThread].acquire();
210 	/*std::mutex mtx;
211 	std::unique_lock<std::mutex> lck(mtx);
212 	cvTimetableFinished[_nThread].wait(lck);*/
213 }
214 
215 /*Controller::Controller()
216 {
217 	Worker* worker=new Worker;
218 	worker->moveToThread(&workerThread);
219 	connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
220 	connect(this, SIGNAL(operate(int)), worker, SLOT(doWork(int)));
221 	connect(worker, SIGNAL(resultReady(int, const QString&, bool)), this, SLOT(handleResults(int, const QString&, bool)));
222 	workerThread.start();
223 }
224 
225 Controller::~Controller()
226 {
227 	workerThread.quit();
228 	workerThread.wait();
229 }
230 
231 void Controller::handleResults(int nThread, const QString& s, bool ok)
232 {
233 	emit(timetableGenerated(nThread, nOverallTimetable+1, s, ok));
234 	semaphoreTimetableFinished[_nThread].acquire();
235 }
236 
237 void Controller::startOperate(int nThread)
238 {
239 	emit(operate(nThread));
240 }*/
241 
TimetableGenerateMultipleForm(QWidget * parent)242 TimetableGenerateMultipleForm::TimetableGenerateMultipleForm(QWidget* parent): QDialog(parent)
243 {
244 	setupUi(this);
245 
246 	timetablesTabWidget->setUsesScrollButtons(true);
247 
248 	currentResultsTextEdit->setReadOnly(true);
249 
250 	startPushButton->setDefault(true);
251 
252 	connect(startPushButton, SIGNAL(clicked()), this, SLOT(start()));
253 	connect(stopPushButton, SIGNAL(clicked()), this, SLOT(stop()));
254 	connect(closePushButton, SIGNAL(clicked()), this, SLOT(closePressed()));
255 	connect(helpPushButton, SIGNAL(clicked()), this, SLOT(help()));
256 
257 	centerWidgetOnScreen(this);
258 	restoreFETDialogGeometry(this);
259 
260 	simulation_running_multi=false;
261 
262 	startPushButton->setEnabled(true);
263 	stopPushButton->setDisabled(true);
264 	closePushButton->setEnabled(true);
265 	minutesGroupBox->setEnabled(true);
266 	timetablesGroupBox->setEnabled(true);
267 	threadsGroupBox->setEnabled(true);
268 
269 	labels.append(textLabel);
270 
271 	nThreadsSpinBox->setMinimum(1);
272 	nThreadsSpinBox->setMaximum(QThread::idealThreadCount());
273 	nThreadsSpinBox->setValue(1); //this is necessary, before the connection to nThreadsChanged(int)
274 
275 	connect(nThreadsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(nThreadsChanged(int)));
276 	QSettings settings(COMPANY, PROGRAM);
277 	nThreadsSpinBox->setValue(settings.value(this->metaObject()->className()+QString("/number-of-threads"), "1").toInt()); //this is necessary, after the connection to nThreadsChanged(int)
278 	timetablesTabWidget->setCurrentIndex(0);
279 
280 	minutesSpinBox->setValue(settings.value(this->metaObject()->className()+QString("/time-limit"), "600000").toInt());
281 	timetablesSpinBox->setValue(settings.value(this->metaObject()->className()+QString("/number-of-timetables"), "10").toInt());
282 }
283 
~TimetableGenerateMultipleForm()284 TimetableGenerateMultipleForm::~TimetableGenerateMultipleForm()
285 {
286 	QSettings settings(COMPANY, PROGRAM);
287 	saveFETDialogGeometry(this);
288 	settings.setValue(this->metaObject()->className()+QString("/number-of-threads"), nThreadsSpinBox->value());
289 
290 	settings.setValue(this->metaObject()->className()+QString("/time-limit"), minutesSpinBox->value());
291 	settings.setValue(this->metaObject()->className()+QString("/number-of-timetables"), timetablesSpinBox->value());
292 
293 	if(simulation_running_multi)
294 		this->stop();
295 
296 	for(Solution* sol : qAsConst(highestStageSolutions))
297 		delete sol;
298 	highestStageSolutions.clear();
299 	nTimetableForHighestStageSolutions.clear();
300 	nThreadForHighest.clear();
301 	simulationTimedOutForHighest.clear();
302 
303 	//assert(controllersList.count()==0);
304 }
305 
nThreadsChanged(int nt)306 void TimetableGenerateMultipleForm::nThreadsChanged(int nt)
307 {
308 	int oldIndex=timetablesTabWidget->currentIndex();
309 
310 	int oldN=timetablesTabWidget->count();
311 	if(oldN>nt){
312 		for(int i=oldN-1; i>=nt; i--){
313 			QWidget* wd=timetablesTabWidget->widget(i);
314 			timetablesTabWidget->removeTab(i);
315 			assert(labels.count()>0);
316 			labels.removeLast();
317 			delete wd;
318 		}
319 
320 		if(oldIndex<timetablesTabWidget->count())
321 			timetablesTabWidget->setCurrentIndex(oldIndex);
322 		else
323 			timetablesTabWidget->setCurrentIndex(timetablesTabWidget->count()-1);
324 	}
325 	else if(oldN<nt){
326 		for(int i=oldN; i<nt; i++){
327 			QWidget* wd=new QWidget(timetablesTabWidget);
328 
329 			QLabel* lb=new QLabel(tr("Current timetable: 0 out of 0 activities placed, 0h 0m 0s\nMax placed activities: 0 (at 0 s)"), wd);
330 			QVBoxLayout* bl=new QVBoxLayout(wd);
331 			bl->addWidget(lb);
332 
333 			timetablesTabWidget->addTab(wd, QString::number(i+1));
334 
335 			labels.append(lb);
336 		}
337 
338 		timetablesTabWidget->setCurrentIndex(timetablesTabWidget->count()-1);
339 	}
340 }
341 
help()342 void TimetableGenerateMultipleForm::help()
343 {
344 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);
345 
346 	if(s2.right(4)==".fet")
347 		s2=s2.left(s2.length()-4);
348 
349 	QString destDir=OUTPUT_DIR+FILE_SEP+"timetables"+FILE_SEP+s2+"-multi";
350 
351 	QString s=tr("You can see the generated timetables on the hard disk, in HTML and XML formats and the soft conflicts in text format,"
352 	 " or the latest timetable (or the latest highest-stage timetable, if no timetable was completed) in the Timetable/View menu."
353 	 " The directory %1 must be emptied and deleted before proceeding.").arg(QDir::toNativeSeparators(destDir))
354 	 +"\n\n"
355 	 +tr("Note that, for large data, each timetable might occupy more megabytes of hard disk space, so make sure you have enough space"
356 	 " (you can check the dimension of a single timetable as a precaution). Each attempted timetable will correspond to a folder in %1"
357 	 " that contains information about the random seed that was used, but only completed timetables will contain the full set of timetable files.")
358 	 .arg(QDir::toNativeSeparators(destDir))
359 	 +"\n\n"
360 	 +tr("For finished timetables, there are also saved the timetables in .fet format (data + constraints to lock the timetable), so that you"
361 	 " can open each of them later.")
362 	 +"\n\n"
363 	 +tr("If you get an impossible timetable, please enter menu Generate (single) and see the initial order of evaluation of activities; this might help.")
364 	 +"\n\n"
365 	 +tr("You can limit the search time, by specifying the maximum number of minutes allowed to spend for each timetable (option %1).").arg("'"+tr("Limit each")+"'")
366 	 +" "+tr("The maximum and also the predefined value is %1 minutes, which means %2 hours, so virtually unlimited.").arg(600000).arg(10000)
367 	 +"\n\n"
368 	 +tr("Note that if you start the multiple generation with the same global seed, the timetables will be identical (if you let the generations finish).")
369 	 +" "+tr("The seed of the first thread will be the global seed multiplied in each component with the number of threads (modulo m1=%1 and respectively m2=%2),"
370 	 " for the second thread the seed will be the same as for the first thread, but +1 in each component (modulo m1 and respectively m2), for the third thread the"
371 	 " seed will be the same as for the first thread, but +2 in each component, and so on.")
372 	 .arg(gen.rng.m1).arg(gen.rng.m2)
373 	 +" "+tr("(If the component of a such computed seed will be all zeroes, we will add to the components of that seed the total number of threads instead"
374 	 " of the thread number starting from zero.)")
375 	 +" "+tr("This method was suggested by %1.").arg("Chichi Lalescu")
376 	 +" "+tr("After generating multiple, the global seed will become equal to the seed of the first thread.")
377 	 +"\n\n"
378 	 +tr("The number of threads is limited by your computer processor(s). If you have for example an 8 core/16 thread processor, the maximum allowed"
379 	 " number of threads is 16. In this case you can make a comparison of generation time with 8 threads or with 16 threads. If you generate on a single thread,"
380 	 " the speed of generation of a timetable will be in general a bit higher than that of obtaining a single timetable by generating on multiple threads,"
381 	 " because the processor slows down if you are using more threads, but you will obtain more timetables in a comparable time.")
382 	 +"\n\n"
383 	 +tr("WARNING: As you use more threads, the processor will be used to a greater extent and it might overheat. Also, the system might become"
384 	 " slow or nonreponsive.")
385 	 +"\n\n"
386 	 +tr("Note: If your file is very big, containing a very large number of teachers or total subgroups, generating on more threads might consume the"
387 	 " processor cache and the generation might become very slow compared to generating on a single thread.")
388 	 ;
389 
390 	LongTextMessageBox::largeInformation(this, tr("FET information"), s);
391 }
392 
start()393 void TimetableGenerateMultipleForm::start(){
394 	int nThreads=nThreadsSpinBox->value();
395 	assert(nThreads>=1);
396 	genMultiMatrix.resize(nThreads);
397 	timetablingThreads.resize(nThreads);
398 	process_start_time.resize(nThreads);
399 	//generateMultipleThread.resize(nThreads);
400 	//generateMultipleWorker.resize(nThreads);
401 	semaphoreTimetableStarted.resize(nThreads);
402 	semaphoreTimetableFinished.resize(nThreads);
403 	//cvTimetableStarted.resize(nThreads);
404 	//cvTimetableFinished.resize(nThreads);
405 	allNThreads=nThreads;
406 
407 	for(Solution* sol : qAsConst(highestStageSolutions))
408 		delete sol;
409 	highestStageSolutions.clear();
410 	nTimetableForHighestStageSolutions.clear();
411 	nThreadForHighest.clear();
412 	simulationTimedOutForHighest.clear();
413 
414 	for(int t=0; t<nThreads; t++){
415 		/*genMultiMatrix[t].rng.s10=(gen.rng.s10*nThreads+t)%gen.rng.m1;
416 		genMultiMatrix[t].rng.s11=(gen.rng.s11*nThreads+t)%gen.rng.m1;
417 		genMultiMatrix[t].rng.s12=(gen.rng.s12*nThreads+t)%gen.rng.m1;
418 		if(genMultiMatrix[t].rng.s10==0 && genMultiMatrix[t].rng.s11==0 && genMultiMatrix[t].rng.s12==0){
419 			assert(t!=0);
420 			genMultiMatrix[t].rng.s10=(gen.rng.s10*nThreads+nThreads)%gen.rng.m1;
421 			genMultiMatrix[t].rng.s11=(gen.rng.s11*nThreads+nThreads)%gen.rng.m1;
422 			genMultiMatrix[t].rng.s12=(gen.rng.s12*nThreads+nThreads)%gen.rng.m1;
423 		}
424 		assert(genMultiMatrix[t].rng.s10!=0 || genMultiMatrix[t].rng.s11!=0 || genMultiMatrix[t].rng.s12!=0);
425 
426 		genMultiMatrix[t].rng.s20=(gen.rng.s20*nThreads+t)%gen.rng.m2;
427 		genMultiMatrix[t].rng.s21=(gen.rng.s21*nThreads+t)%gen.rng.m2;
428 		genMultiMatrix[t].rng.s22=(gen.rng.s22*nThreads+t)%gen.rng.m2;
429 		if(genMultiMatrix[t].rng.s20==0 && genMultiMatrix[t].rng.s21==0 && genMultiMatrix[t].rng.s22==0){
430 			assert(t!=0);
431 			genMultiMatrix[t].rng.s20=(gen.rng.s20*nThreads+nThreads)%gen.rng.m2;
432 			genMultiMatrix[t].rng.s21=(gen.rng.s21*nThreads+nThreads)%gen.rng.m2;
433 			genMultiMatrix[t].rng.s22=(gen.rng.s22*nThreads+nThreads)%gen.rng.m2;
434 		}
435 		assert(genMultiMatrix[t].rng.s20!=0 || genMultiMatrix[t].rng.s21!=0 || genMultiMatrix[t].rng.s22!=0);*/
436 
437 		//init method suggested by Chichi Lalescu
438 		qint64 s10=(gen.rng.s10*nThreads+t)%gen.rng.m1;
439 		qint64 s11=(gen.rng.s11*nThreads+t)%gen.rng.m1;
440 		qint64 s12=(gen.rng.s12*nThreads+t)%gen.rng.m1;
441 		if(s10==0 && s11==0 && s12==0){
442 			assert(t!=0);
443 			s10=(gen.rng.s10*nThreads+nThreads)%gen.rng.m1;
444 			s11=(gen.rng.s11*nThreads+nThreads)%gen.rng.m1;
445 			s12=(gen.rng.s12*nThreads+nThreads)%gen.rng.m1;
446 		}
447 
448 		qint64 s20=(gen.rng.s20*nThreads+t)%gen.rng.m2;
449 		qint64 s21=(gen.rng.s21*nThreads+t)%gen.rng.m2;
450 		qint64 s22=(gen.rng.s22*nThreads+t)%gen.rng.m2;
451 		if(s20==0 && s21==0 && s22==0){
452 			assert(t!=0);
453 			s20=(gen.rng.s20*nThreads+nThreads)%gen.rng.m2;
454 			s21=(gen.rng.s21*nThreads+nThreads)%gen.rng.m2;
455 			s22=(gen.rng.s22*nThreads+nThreads)%gen.rng.m2;
456 		}
457 
458 		genMultiMatrix[t].rng.initializeMRG32k3a(s10, s11, s12, s20, s21, s22);
459 	}
460 
461 	nTimetables=timetablesSpinBox->value();
462 	assert(nTimetables>0);
463 	timeLimit=60*minutesSpinBox->value(); //seconds
464 	assert(timeLimit>0);
465 
466 	QDir dir;
467 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);
468 
469 	if(s2.right(4)==".fet")
470 		s2=s2.left(s2.length()-4);
471 
472 	QString destDir=OUTPUT_DIR+FILE_SEP+"timetables"+FILE_SEP+s2+"-multi";
473 	if(dir.exists(destDir)){
474 		QMessageBox::warning(this, tr("FET information"), tr("The directory %1 exists and might not be empty"
475 		 " (it might contain old files). You need to manually remove all contents of this directory AND the directory itself (or rename it)"
476 		 " and then you can generate multiple timetables.")
477 		 .arg(QDir::toNativeSeparators(destDir)));
478 
479 		return;
480 	}
481 
482 	if(!gt.rules.internalStructureComputed){
483 		if(!gt.rules.computeInternalStructure(this)){
484 			QMessageBox::warning(this, TimetableGenerateMultipleForm::tr("FET warning"), TimetableGenerateMultipleForm::tr("Data is wrong. Please correct and try again"));
485 			return;
486 		}
487 	}
488 
489 	if(!gt.rules.initialized || gt.rules.activitiesList.isEmpty()){
490 		QMessageBox::critical(this, TimetableGenerateMultipleForm::tr("FET information"),
491 			TimetableGenerateMultipleForm::tr("You have entered simulation with uninitialized rules or 0 activities...aborting"));
492 		assert(0);
493 		exit(1);
494 		return;
495 	}
496 
497 	currentResultsTextEdit->setPlainText("");
498 
499 	bool ok=genMultiMatrix[0].precompute(this);
500 	if(!ok){
501 		currentResultsTextEdit->setPlainText(TimetableGenerateMultipleForm::tr("Cannot optimize - please modify your data"));
502 		currentResultsTextEdit->update();
503 
504 		QMessageBox::information(this, TimetableGenerateMultipleForm::tr("FET information"),
505 		 TimetableGenerateMultipleForm::tr("Your data cannot be processed - please modify it as instructed."));
506 
507 		return;
508 	}
509 
510 	//assert(controllersList.count()==0);
511 	for(int t=0; t<nThreads; t++){
512 		//generateMultipleWorker[t].disconnect(); //disconnect all connections for this QThread
513 		genMultiMatrix[t].disconnect(); //disconnect all connections for this Generate
514 
515 		//Controller* controller=new Controller();
516 		timetablingThreads[t].disconnect();
517 
518 		//connect(controller, SIGNAL(timetableStarted(int, int)), this, SLOT(timetableStarted(int, int)));
519 		//connect(controller, SIGNAL(timetableGenerated(int, int, const QString&, bool)), this, SLOT(timetableGenerated(int, int, const QString&, bool)));
520 
521 		//controllersList.append(controller);
522 
523 		timetablingThreads[t]._nThread=t;
524 
525 		genMultiMatrix[t].nThread=t;
526 		genMultiMatrix[t].isRunning=false;
527 
528 		connect(&genMultiMatrix[t], SIGNAL(activityPlaced(int, int)), this, SLOT(activityPlaced(int, int)));
529 		connect(&timetablingThreads[t], SIGNAL(timetableGenerated(int, int, const QString&, bool)), this, SLOT(timetableGenerated(int, int, const QString&, bool)));
530 	}
531 
532 	startPushButton->setDisabled(true);
533 	stopPushButton->setEnabled(true);
534 	minutesGroupBox->setDisabled(true);
535 	timetablesGroupBox->setDisabled(true);
536 	closePushButton->setDisabled(true);
537 	threadsGroupBox->setDisabled(true);
538 
539 	nGeneratedTimetables=0;
540 	nSuccessfullyGeneratedTimetables=0;
541 	highestPlacedActivities=0;
542 
543 	simulation_running_multi=true;
544 
545 	time(&all_processes_start_time);
546 
547 	//assert(controllersList.count()==nThreads);
548 
549 	//for(int t=0; t<nThreads; t++)
550 	//	controllersList.at(t)->_nThread=t;
551 
552 	for(int t=0; t<nThreads; t++){
553 		genMultiMatrix[t].c.makeUnallocated(gt.rules);
554 
555 		timetablingThreads[t].nOverallTimetable=t;
556 
557 		//Controller* tc=controllersList.at(t);
558 		//tc->nOverallTimetable=t;
559 
560 		timetableStarted(t, timetablingThreads[t].nOverallTimetable+1);
561 
562 		semaphoreTimetableStarted[t].acquire();
563 		/*std::mutex mtx;
564 		std::unique_lock<std::mutex> lck(mtx);
565 		cvTimetableStarted[t].wait(lck);*/
566 
567 		//timetablingThreads[t].startGenerating();
568 		timetablingThreads[t]._internalGeneratingThread=std::thread([=]{timetablingThreads[t].startGenerating();});
569 		timetablingThreads[t]._internalGeneratingThread.detach();
570 	}
571 }
572 
timetableStarted(int nThread,int timetable)573 void TimetableGenerateMultipleForm::timetableStarted(int nThread, int timetable)
574 {
575 	TimetableExport::writeRandomSeed(this, genMultiMatrix[nThread].rng, timetable, true); //true represents 'before' state
576 
577 	semaphoreTimetableStarted[nThread].release();
578 	//cvTimetableStarted[nThread].notify_one();
579 }
580 
timetableGenerated(int nThread,int timetable,const QString & description,bool ok)581 void TimetableGenerateMultipleForm::timetableGenerated(int nThread, int timetable, const QString& description, bool ok)
582 {
583 	nGeneratedTimetables++;
584 	numberOfGeneratedTimetablesLabel->setText(tr("Generated: %1").arg(nGeneratedTimetables));
585 	if(ok){
586 		nSuccessfullyGeneratedTimetables++;
587 		numberOfSuccessfullyGeneratedTimetablesLabel->setText(tr("Successfully: %1").arg(nSuccessfullyGeneratedTimetables));
588 		highestPlacedActivities=genMultiMatrix[nThread].maxActivitiesPlaced;
589 		assert(highestPlacedActivities==gt.rules.nInternalActivities);
590 	}
591 	else{
592 		if(highestPlacedActivities<genMultiMatrix[nThread].maxActivitiesPlaced){
593 			highestPlacedActivities=genMultiMatrix[nThread].maxActivitiesPlaced;
594 			for(Solution* sol : qAsConst(highestStageSolutions))
595 				delete sol;
596 			highestStageSolutions.clear();
597 			nTimetableForHighestStageSolutions.clear();
598 			nThreadForHighest.clear();
599 			simulationTimedOutForHighest.clear();
600 			Solution* sol=new Solution();
601 			sol->copyForHighestStage(gt.rules, genMultiMatrix[nThread].highestStageSolution);
602 			highestStageSolutions.append(sol);
603 			nTimetableForHighestStageSolutions.append(timetable);
604 			nThreadForHighest.append(nThread);
605 			simulationTimedOutForHighest.append(true);
606 		}
607 		else if(highestPlacedActivities==genMultiMatrix[nThread].maxActivitiesPlaced){
608 			Solution* sol=new Solution();
609 			sol->copyForHighestStage(gt.rules, genMultiMatrix[nThread].highestStageSolution);
610 			highestStageSolutions.append(sol);
611 			nTimetableForHighestStageSolutions.append(timetable);
612 			nThreadForHighest.append(nThread);
613 			simulationTimedOutForHighest.append(true);
614 		}
615 	}
616 
617 	if(nGeneratedTimetables<=nTimetables){
618 		if(ok)
619 			TimetableExport::writeRandomSeed(this, genMultiMatrix[nThread].rng, timetable, false); //false represents 'before' state
620 
621 		QString s=QString("");
622 		s+=tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description").arg(timetable).arg(description);
623 		currentResultsTextEdit->appendPlainText(s);
624 
625 		bool begin;
626 		if(nGeneratedTimetables==1)
627 			begin=true;
628 		else
629 			begin=false;
630 		TimetableExport::writeReportForMultiple(this, s, begin);
631 
632 		if(ok){
633 			//needed to get the conflicts string
634 			FakeString tmp;
635 			fitnessMutexInThreads.lock();
636 			genMultiMatrix[nThread].c.fitness(gt.rules, &tmp);
637 			fitnessMutexInThreads.unlock();
638 
639 			/*TimetableExport::getStudentsTimetable(genMultiMatrix[nThread].c);
640 			TimetableExport::getTeachersTimetable(genMultiMatrix[nThread].c);
641 			TimetableExport::getRoomsTimetable(genMultiMatrix[nThread].c);*/
642 			TimetableExport::getStudentsTeachersRoomsTimetable(genMultiMatrix[nThread].c);
643 
644 			TimetableExport::writeSimulationResults(this, timetable);
645 
646 			//update the string representing the conflicts
647 			conflictsStringTitle=tr("Soft conflicts", "Title of dialog");
648 			conflictsString = "";
649 
650 			conflictsString+=tr("Number of broken soft constraints: %1").arg(best_solution.conflictsWeightList.count());
651 
652 			conflictsString+="\n";
653 
654 			conflictsString+=tr("Total soft conflicts: %1").arg(CustomFETString::numberPlusTwoDigitsPrecision(best_solution.conflictsTotal));
655 
656 			conflictsString+="\n";
657 			conflictsString+=tr("Soft conflicts listing (in decreasing order):")+"\n";
658 
659 			for(const QString& t : qAsConst(best_solution.conflictsDescriptionList))
660 				conflictsString+=t+"\n";
661 
662 			updateAllTimetableViewDialogs();
663 		}
664 	}
665 
666 	//semaphoreTimetableFinished.release();
667 
668 	semaphoreTimetableFinished[nThread].release();
669 	//cvTimetableFinished[nThread].notify_one();
670 	//generateMultipleThread[nThread].quit();
671 	//generateMultipleThread[nThread].wait();
672 	//while(generateMultipleThread[nThread].isRunning()){
673 		//wait for the thread to finish.
674 	//}
675 	//assert(generateMultipleThread[nThread].isFinished());
676 	if(nGeneratedTimetables<nTimetables && simulation_running_multi){
677 		if(timetablingThreads[nThread]._internalGeneratingThread.joinable())
678 			timetablingThreads[nThread]._internalGeneratingThread.join();
679 
680 		//Controller* oldController=controllersList.at(nThread);
681 		//controllersList.removeAt(nThread);
682 		//int toldn=oldController->nOverallTimetable;
683 		int toldn=timetablingThreads[nThread].nOverallTimetable;
684 		//delete oldController;
685 		//oldController->deleteLater();
686 
687 		genMultiMatrix[nThread].c.makeUnallocated(gt.rules);
688 
689 		//Controller* controller=new Controller();
690 		//controllersList[nThread]=controller;
691 
692 		//controller->_nThread=nThread;
693 		//controller->nOverallTimetable=toldn+allNThreads;
694 		timetablingThreads[nThread].nOverallTimetable=toldn+allNThreads;
695 
696 		//connect(controller, SIGNAL(timetableStarted(int, int)), this, SLOT(timetableStarted(int, int)));
697 
698 		//connect(controller, SIGNAL(timetableGenerated(int, int, const QString&, bool)), this, SLOT(timetableGenerated(int, int, const QString&, bool)));
699 
700 		//timetableStarted(nThread, controller->nOverallTimetable+1/*+allNThreads*/);
701 		timetableStarted(nThread, timetablingThreads[nThread].nOverallTimetable+1);
702 		semaphoreTimetableStarted[nThread].acquire();
703 		/*std::mutex mtx;
704 		std::unique_lock<std::mutex> lck(mtx);
705 		cvTimetableStarted[nThread].wait(lck);*/
706 
707 		//timetablingThreads[nThread]._internalGeneratingThread=std::thread(timetablingThreads[nThread].startGenerating);
708 		timetablingThreads[nThread]._internalGeneratingThread=std::thread([=]{timetablingThreads[nThread].startGenerating();});
709 		timetablingThreads[nThread]._internalGeneratingThread.detach();
710 		//controller->startOperate(nThread);
711 	}
712 	else if(simulation_running_multi){
713 		//assert(controllersList.count()==allNThreads);
714 		for(int t=0; t<allNThreads; t++){
715 			//This is always done (1 || stuff) because we need to disconnnect the Generate.
716 			//if(1 || generateMultipleThread[t].isRunning()){
717 			genMultiMatrix[t].myMutex.lock();
718 			genMultiMatrix[t].abortOptimization=true;
719 			//genMultiMatrix[t].disconnect();
720 			genMultiMatrix[t].myMutex.unlock();
721 
722 			//delete controllersList[t];
723 			//}
724 
725 			if(timetablingThreads[t]._internalGeneratingThread.joinable())
726 				timetablingThreads[t]._internalGeneratingThread.join();
727 
728 			if(highestPlacedActivities<genMultiMatrix[t].maxActivitiesPlaced){
729 				highestPlacedActivities=genMultiMatrix[t].maxActivitiesPlaced;
730 				for(Solution* sol : qAsConst(highestStageSolutions))
731 					delete sol;
732 				highestStageSolutions.clear();
733 				nTimetableForHighestStageSolutions.clear();
734 				nThreadForHighest.clear();
735 				simulationTimedOutForHighest.clear();
736 				Solution* sol=new Solution();
737 				sol->copyForHighestStage(gt.rules, genMultiMatrix[t].highestStageSolution);
738 				highestStageSolutions.append(sol);
739 				nTimetableForHighestStageSolutions.append(timetablingThreads[t].nOverallTimetable+1);
740 				nThreadForHighest.append(t);
741 				simulationTimedOutForHighest.append(true);
742 			}
743 			else if(highestPlacedActivities==genMultiMatrix[t].maxActivitiesPlaced){
744 				Solution* sol=new Solution();
745 				sol->copyForHighestStage(gt.rules, genMultiMatrix[t].highestStageSolution);
746 				highestStageSolutions.append(sol);
747 				nTimetableForHighestStageSolutions.append(timetablingThreads[t].nOverallTimetable+1);
748 				nThreadForHighest.append(t);
749 				simulationTimedOutForHighest.append(true);
750 			}
751 		}
752 		simulationFinished();
753 	}
754 }
755 
stop()756 void TimetableGenerateMultipleForm::stop()
757 {
758 	if(!simulation_running_multi){
759 		return;
760 	}
761 
762 	simulation_running_multi=false;
763 
764 	//assert(controllersList.count()==nThreadsSpinBox->value());
765 
766 	for(int t=0; t<nThreadsSpinBox->value(); t++){
767 		//cout<<"1. t="<<t<<endl;
768 		genMultiMatrix[t].myMutex.lock();
769 		genMultiMatrix[t].abortOptimization=true;
770 		genMultiMatrix[t].myMutex.unlock();
771 		//cout<<"2. t="<<t<<endl;
772 		if(timetablingThreads[t]._internalGeneratingThread.joinable())
773 			timetablingThreads[t]._internalGeneratingThread.join();
774 		//cout<<"3. t="<<t<<endl;
775 		//if(timetablingThreads[t].joinable())
776 		//	timetablingThreads[t].join();
777 
778 		//if(controllersList.at(t)->workerThread.isRunning()){
779 		//	genMultiMatrix[t].myMutex.lock();
780 
781 		//	genMultiMatrix[t].abortOptimization=true;
782 		if(nSuccessfullyGeneratedTimetables==0){
783 			if(highestPlacedActivities<genMultiMatrix[t].maxActivitiesPlaced){
784 				highestPlacedActivities=genMultiMatrix[t].maxActivitiesPlaced;
785 				for(Solution* sol : qAsConst(highestStageSolutions))
786 					delete sol;
787 				highestStageSolutions.clear();
788 				nTimetableForHighestStageSolutions.clear();
789 				nThreadForHighest.clear();
790 				simulationTimedOutForHighest.clear();
791 				Solution* sol=new Solution();
792 				sol->copyForHighestStage(gt.rules, genMultiMatrix[t].highestStageSolution);
793 				highestStageSolutions.append(sol);
794 				//nTimetableForHighestStageSolutions.append(controllersList.at(t)->nOverallTimetable+1);
795 				nTimetableForHighestStageSolutions.append(timetablingThreads[t].nOverallTimetable+1);
796 				nThreadForHighest.append(t);
797 				simulationTimedOutForHighest.append(false);
798 			}
799 			else if(highestPlacedActivities==genMultiMatrix[t].maxActivitiesPlaced){
800 				Solution* sol=new Solution();
801 				sol->copyForHighestStage(gt.rules, genMultiMatrix[t].highestStageSolution);
802 				highestStageSolutions.append(sol);
803 				//nTimetableForHighestStageSolutions.append(controllersList.at(t)->nOverallTimetable+1);
804 				nTimetableForHighestStageSolutions.append(timetablingThreads[t].nOverallTimetable+1);
805 				nThreadForHighest.append(t);
806 				simulationTimedOutForHighest.append(false);
807 			}
808 		}
809 
810 		//genMultiMatrix[t].myMutex.unlock();
811 	}
812 
813 	QString s=TimetableGenerateMultipleForm::tr("Simulation interrupted!");
814 
815 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);
816 
817 	if(s2.right(4)==".fet")
818 		s2=s2.left(s2.length()-4);
819 
820 	QString destDir=OUTPUT_DIR+FILE_SEP+"timetables"+FILE_SEP+s2+"-multi";
821 
822 	time_t final_time;
823 	time(&final_time);
824 	int sec=int(difftime(final_time, all_processes_start_time));
825 	int h=sec/3600;
826 	sec%=3600;
827 	int m=sec/60;
828 	sec%=60;
829 
830 	QDate dat=QDate::currentDate();
831 	QTime tim=QTime::currentTime();
832 	QLocale loc(FET_LANGUAGE);
833 	QString sTime=loc.toString(dat, QLocale::ShortFormat)+" "+loc.toString(tim, QLocale::ShortFormat);
834 	QString ms2=QString("");
835 
836 	QString stringForDisk=s;
837 
838 	QString ms3=QString("");
839 	if(nSuccessfullyGeneratedTimetables>=1){
840 		s+=QString("\n\n");
841 		s+=TimetableGenerateMultipleForm::tr("From the interface you can access the last successfully generated timetable.");
842 	}
843 	else{
844 		s+=QString("\n\n");
845 		s+=TimetableGenerateMultipleForm::tr("From the interface you can access the last highest-stage timetable and on the disk"
846 		 " there were saved all the highest-stage timetables (the highest number of activities reached is %1, reached in %2 timetables).")
847 		 .arg(highestPlacedActivities).arg(highestStageSolutions.count());
848 
849 		stringForDisk+=QString("\n\n");
850 		stringForDisk+=TimetableGenerateMultipleForm::tr("On the disk there were saved all the highest-stage timetables (the highest number"
851 		 " of activities reached is %1, reached in %2 timetables).").arg(highestPlacedActivities).arg(highestStageSolutions.count());
852 
853 		for(int i=0; i<highestStageSolutions.count(); i++){
854 			Solution* sol=highestStageSolutions.at(i);
855 			int nTimetable=nTimetableForHighestStageSolutions.at(i);
856 			int nThread=nThreadForHighest.at(i);
857 			bool timedOut=simulationTimedOutForHighest.at(i);
858 			QString description=tr("(Thread %1)").arg(nThread+1);
859 			description+=QString(" ");
860 			if(timedOut)
861 				description+=tr("Time exceeded.");
862 			else
863 				description+=tr("Simulation stopped.");
864 			description+=QString(" ");
865 			description+=tr("Maximum placed activities: %1.").arg(highestPlacedActivities);
866 			if(i>=1 || currentResultsTextEdit->toPlainText().isEmpty())
867 				currentResultsTextEdit->appendPlainText(tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description")
868 				 .arg(nTimetable).arg(description));
869 			else
870 				currentResultsTextEdit->appendPlainText(QString("\n")+tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description")
871 				 .arg(nTimetable).arg(description));
872 			ms3+=tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description")
873 			 .arg(nTimetable).arg(description)+QString("\n");
874 
875 			//needed to get the conflicts string
876 			FakeString tmp;
877 			fitnessMutexInThreads.lock();
878 			sol->fitness(gt.rules, &tmp);
879 			fitnessMutexInThreads.unlock();
880 
881 			/*TimetableExport::getStudentsTimetable(*sol);
882 			TimetableExport::getTeachersTimetable(*sol);
883 			TimetableExport::getRoomsTimetable(*sol);*/
884 			TimetableExport::getStudentsTeachersRoomsTimetable(*sol);
885 
886 			TimetableExport::writeSimulationResults(this, nTimetable, true);
887 
888 			//update the string representing the conflicts
889 			conflictsStringTitle=tr("Conflicts", "Title of dialog");
890 			conflictsString = "";
891 
892 			conflictsString+=tr("Number of broken constraints: %1").arg(sol->conflictsWeightList.count());
893 
894 			conflictsString+="\n";
895 
896 			conflictsString+=tr("Total conflicts: %1").arg(CustomFETString::numberPlusTwoDigitsPrecision(sol->conflictsTotal));
897 
898 			conflictsString+="\n";
899 			conflictsString+=tr("Conflicts listing (in decreasing order):")+"\n";
900 
901 			for(const QString& t : qAsConst(sol->conflictsDescriptionList))
902 				conflictsString+=t+"\n";
903 		}
904 		updateAllTimetableViewDialogs();
905 	}
906 
907 	if(!ms3.isEmpty())
908 		ms3+=QString("\n");
909 
910 	s+="\n\n";
911 	s+=TimetableGenerateMultipleForm::tr("The results were saved in the directory %1.").arg(QDir::toNativeSeparators(destDir));
912 	s+="\n\n";
913 	s+=tr("Total searching time was: %1h %2m %3s.").arg(h).arg(m).arg(sec);
914 
915 	stringForDisk+="\n\n";
916 	stringForDisk+=TimetableGenerateMultipleForm::tr("The results were saved in the directory %1.").arg(QDir::toNativeSeparators(destDir));
917 	stringForDisk+="\n\n";
918 	stringForDisk+=tr("Total searching time was: %1h %2m %3s.").arg(h).arg(m).arg(sec);
919 
920 	ms2+=QString("\n\n");
921 	ms2+=TimetableGenerateMultipleForm::tr("This file was automatically generated by FET %1 on %2.",
922 	 "%1 is the FET version, %2 is the date and time when this file was generated.").arg(FET_VERSION).arg(sTime);
923 
924 	if(nGeneratedTimetables>=1)
925 		TimetableExport::writeReportForMultiple(this, QString("\n")+ms3+stringForDisk+ms2, false);
926 	else
927 		TimetableExport::writeReportForMultiple(this, ms3+stringForDisk+ms2, true);
928 
929 	QMessageBox::information(this, tr("FET information"), s);
930 
931 	startPushButton->setEnabled(true);
932 	stopPushButton->setDisabled(true);
933 	minutesGroupBox->setEnabled(true);
934 	timetablesGroupBox->setEnabled(true);
935 	closePushButton->setEnabled(true);
936 	threadsGroupBox->setEnabled(true);
937 
938 	/*gen.rng.s10=genMultiMatrix[0].rng.s10;
939 	gen.rng.s11=genMultiMatrix[0].rng.s11;
940 	gen.rng.s12=genMultiMatrix[0].rng.s12;
941 	gen.rng.s20=genMultiMatrix[0].rng.s20;
942 	gen.rng.s21=genMultiMatrix[0].rng.s21;
943 	gen.rng.s22=genMultiMatrix[0].rng.s22;
944 	assert(gen.rng.s10!=0 || gen.rng.s11!=0 || gen.rng.s12!=0);
945 	assert(gen.rng.s20!=0 || gen.rng.s21!=0 || gen.rng.s22!=0);*/
946 	gen.rng.initializeMRG32k3a(genMultiMatrix[0].rng.s10, genMultiMatrix[0].rng.s11, genMultiMatrix[0].rng.s12,
947 	 genMultiMatrix[0].rng.s20, genMultiMatrix[0].rng.s21, genMultiMatrix[0].rng.s22);
948 
949 	/*for(Controller* tc : qAsConst(controllersList))
950 		//delete tc;
951 		tc->deleteLater();
952 	controllersList.clear();*/
953 }
954 
simulationFinished()955 void TimetableGenerateMultipleForm::simulationFinished()
956 {
957 	if(!simulation_running_multi){
958 		return;
959 	}
960 
961 	simulation_running_multi=false;
962 
963 	QString s2=INPUT_FILENAME_XML.right(INPUT_FILENAME_XML.length()-INPUT_FILENAME_XML.lastIndexOf(FILE_SEP)-1);
964 
965 	if(s2.right(4)==".fet")
966 		s2=s2.left(s2.length()-4);
967 
968 	QString destDir=OUTPUT_DIR+FILE_SEP+"timetables"+FILE_SEP+s2+"-multi";
969 
970 	time_t final_time;
971 	time(&final_time);
972 	int s=int(difftime(final_time, all_processes_start_time));
973 	int h=s/3600;
974 	s%=3600;
975 	int m=s/60;
976 	s%=60;
977 
978 	QString ms=QString("");
979 	ms+=TimetableGenerateMultipleForm::tr("Simulation finished!");
980 
981 	QString stringForDisk=ms;
982 
983 	QString ms3=QString("");
984 
985 	if(nSuccessfullyGeneratedTimetables>=1){
986 		ms+=QString("\n\n");
987 		ms+=TimetableGenerateMultipleForm::tr("From the interface you can access the last successfully generated timetable.");
988 	}
989 	else{
990 		ms+=QString("\n\n");
991 		ms+=TimetableGenerateMultipleForm::tr("From the interface you can access the last highest-stage timetable and on the disk"
992 		 " there were saved all the highest-stage timetables (the highest number of activities reached is %1, reached in %2 timetables).")
993 		 .arg(highestPlacedActivities).arg(highestStageSolutions.count());
994 
995 		stringForDisk+=QString("\n\n");
996 		stringForDisk+=TimetableGenerateMultipleForm::tr("On the disk there were saved all the highest-stage timetables (the highest number"
997 		 " of activities reached is %1, reached in %2 timetables).").arg(highestPlacedActivities).arg(highestStageSolutions.count());
998 
999 		for(int i=0; i<highestStageSolutions.count(); i++){
1000 			Solution* sol=highestStageSolutions.at(i);
1001 			int nTimetable=nTimetableForHighestStageSolutions.at(i);
1002 			int nThread=nThreadForHighest.at(i);
1003 			bool timedOut=simulationTimedOutForHighest.at(i);
1004 			QString description=tr("(Thread %1)").arg(nThread+1);
1005 			description+=QString(" ");
1006 			if(timedOut)
1007 				description+=tr("Time exceeded.");
1008 			else
1009 				description+=tr("Simulation stopped.");
1010 			description+=QString(" ");
1011 			description+=tr("Maximum placed activities: %1.").arg(highestPlacedActivities);
1012 			if(i>=1 || currentResultsTextEdit->toPlainText().isEmpty())
1013 				currentResultsTextEdit->appendPlainText(tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description")
1014 				 .arg(nTimetable).arg(description));
1015 			else
1016 				currentResultsTextEdit->appendPlainText(QString("\n")+tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description")
1017 				 .arg(nTimetable).arg(description));
1018 			ms3+=tr("Timetable no: %1 => %2", "%1 is the number of this timetable when generating multiple timetables, %2 is its description")
1019 			 .arg(nTimetable).arg(description)+QString("\n");
1020 
1021 			//needed to get the conflicts string
1022 			FakeString tmp;
1023 			fitnessMutexInThreads.lock();
1024 			sol->fitness(gt.rules, &tmp);
1025 			fitnessMutexInThreads.unlock();
1026 
1027 			/*TimetableExport::getStudentsTimetable(*sol);
1028 			TimetableExport::getTeachersTimetable(*sol);
1029 			TimetableExport::getRoomsTimetable(*sol);*/
1030 			TimetableExport::getStudentsTeachersRoomsTimetable(*sol);
1031 
1032 			TimetableExport::writeSimulationResults(this, nTimetable, true);
1033 
1034 			//update the string representing the conflicts
1035 			conflictsStringTitle=tr("Conflicts", "Title of dialog");
1036 			conflictsString = "";
1037 
1038 			conflictsString+=tr("Number of broken constraints: %1").arg(sol->conflictsWeightList.count());
1039 
1040 			conflictsString+="\n";
1041 
1042 			conflictsString+=tr("Total conflicts: %1").arg(CustomFETString::numberPlusTwoDigitsPrecision(sol->conflictsTotal));
1043 
1044 			conflictsString+="\n";
1045 			conflictsString+=tr("Conflicts listing (in decreasing order):")+"\n";
1046 
1047 			for(const QString& t : qAsConst(sol->conflictsDescriptionList))
1048 				conflictsString+=t+"\n";
1049 		}
1050 		updateAllTimetableViewDialogs();
1051 	}
1052 
1053 	if(!ms3.isEmpty())
1054 		ms3+=QString("\n");
1055 
1056 	ms+=QString("\n\n");
1057 	ms+=TimetableGenerateMultipleForm::tr("The results were saved in the directory %1.").arg(QDir::toNativeSeparators(destDir));
1058 	ms+=QString("\n\n");
1059 	ms+=TimetableGenerateMultipleForm::tr("Total searching time was %1h %2m %3s.").arg(h).arg(m).arg(s);
1060 
1061 	stringForDisk+=QString("\n\n");
1062 	stringForDisk+=TimetableGenerateMultipleForm::tr("The results were saved in the directory %1.").arg(QDir::toNativeSeparators(destDir));
1063 	stringForDisk+=QString("\n\n");
1064 	stringForDisk+=TimetableGenerateMultipleForm::tr("Total searching time was %1h %2m %3s.").arg(h).arg(m).arg(s);
1065 
1066 	QDate dat=QDate::currentDate();
1067 	QTime tim=QTime::currentTime();
1068 	QLocale loc(FET_LANGUAGE);
1069 	QString sTime=loc.toString(dat, QLocale::ShortFormat)+" "+loc.toString(tim, QLocale::ShortFormat);
1070 	QString ms2=QString("\n\n");
1071 	ms2+=TimetableGenerateMultipleForm::tr("This file was automatically generated by FET %1 on %2.",
1072 	 "%1 is the FET version, %2 is the date and time when this file was generated.").arg(FET_VERSION).arg(sTime);
1073 
1074 	assert(nGeneratedTimetables>=1);
1075 	TimetableExport::writeReportForMultiple(this, QString("\n")+ms3+stringForDisk+ms2, false);
1076 
1077 //Old comment below (2020-08-14).
1078 //On Windows we do not beep for Qt >= 5.14.1, because the QMessageBox below beeps itself.
1079 //It would be better to test at runtime, not at compile time, but it is easier/safer this way.
1080 //(The alternative would be to develop a parser for the function qVersion(), but I am not sure it will always respect the exact format "vM.vm.vp".)
1081 //We test the macro Q_OS_WIN32 because on the old Qt 4 it is the only available macro from these three below,
1082 //Q_OS_WIN, Q_OS_WIN32, and Q_OS_WIN64 (which are available on Qt 5.14.1). Yes, the test is redundant (because if QT_VERSION < 5.14.1
1083 //the condition is true and if QT_VERSION >= 5.14.1 then all these three macros are available), but this doesn't hurt.
1084 //#if (!defined(Q_OS_WIN) && !defined(Q_OS_WIN32) && !defined(Q_OS_WIN64)) || (QT_VERSION < QT_VERSION_CHECK(5,14,1))
1085 	if(BEEP_AT_END_OF_GENERATION)
1086 		QApplication::beep();
1087 //#endif
1088 
1089 	if(ENABLE_COMMAND_AT_END_OF_GENERATION){
1090 		QString s=commandAtEndOfGeneration.simplified();
1091 		if(!s.isEmpty()){
1092 			QStringList sl=s.split(" ");
1093 			assert(sl.count()>=1);
1094 			QString command=sl.at(0);
1095 			QStringList arguments;
1096 			for(int i=1; i<sl.count(); i++)
1097 				arguments.append(sl.at(i));
1098 
1099 			/*if(DETACHED_NOTIFICATION==false){
1100 				QProcess* myProcess=new QProcess();
1101 				if(terminateCommandAfterSeconds>0)
1102 					QTimer::singleShot(terminateCommandAfterSeconds*1000, myProcess, SLOT(terminate()));
1103 				if(killCommandAfterSeconds>0)
1104 					QTimer::singleShot(killCommandAfterSeconds*1000, myProcess, SLOT(kill()));
1105 
1106 				//https://www.qtcentre.org/threads/43083-Freeing-a-QProcess-after-it-has-finished-using-deleteLater()
1107 				connect(myProcess, SIGNAL(finished(int)), myProcess, SLOT(deleteLater()));
1108 				myProcess->start(command, arguments);
1109 			}*/
1110 			//else{
1111 			QProcess::startDetached(command, arguments);
1112 			//}
1113 		}
1114 	}
1115 
1116 	//Trick so that the message box will be silent (the only sound is thus the beep above).
1117 	QMessageBox msgBox(this);
1118 	msgBox.setWindowTitle(TimetableGenerateMultipleForm::tr("FET information"));
1119 	msgBox.setText(ms);
1120 	msgBox.exec();
1121 	//QMessageBox::information(this, TimetableGenerateMultipleForm::tr("FET information"), ms);
1122 
1123 	startPushButton->setEnabled(true);
1124 	stopPushButton->setDisabled(true);
1125 	minutesGroupBox->setEnabled(true);
1126 	timetablesGroupBox->setEnabled(true);
1127 	closePushButton->setEnabled(true);
1128 	threadsGroupBox->setEnabled(true);
1129 
1130 	/*gen.rng.s10=genMultiMatrix[0].rng.s10;
1131 	gen.rng.s11=genMultiMatrix[0].rng.s11;
1132 	gen.rng.s12=genMultiMatrix[0].rng.s12;
1133 	gen.rng.s20=genMultiMatrix[0].rng.s20;
1134 	gen.rng.s21=genMultiMatrix[0].rng.s21;
1135 	gen.rng.s22=genMultiMatrix[0].rng.s22;
1136 	assert(gen.rng.s10!=0 || gen.rng.s11!=0 || gen.rng.s12!=0);
1137 	assert(gen.rng.s20!=0 || gen.rng.s21!=0 || gen.rng.s22!=0);*/
1138 	gen.rng.initializeMRG32k3a(genMultiMatrix[0].rng.s10, genMultiMatrix[0].rng.s11, genMultiMatrix[0].rng.s12,
1139 	 genMultiMatrix[0].rng.s20, genMultiMatrix[0].rng.s21, genMultiMatrix[0].rng.s22);
1140 
1141 	/*for(Controller* tc : qAsConst(controllersList))
1142 		//delete tc;
1143 		tc->deleteLater();
1144 	controllersList.clear();*/
1145 }
1146 
activityPlaced(int nThread,int na)1147 void TimetableGenerateMultipleForm::activityPlaced(int nThread, int na)
1148 {
1149 	assert(nThread>=0);
1150 	assert(nThread<nThreadsSpinBox->value());
1151 
1152 	genMultiMatrix[nThread].myMutex.lock();
1153 	int seconds=genMultiMatrix[nThread].searchTime; //seconds
1154 	int mact=genMultiMatrix[nThread].maxActivitiesPlaced;
1155 	int mseconds=genMultiMatrix[nThread].timeToHighestStage;
1156 	genMultiMatrix[nThread].myMutex.unlock();
1157 
1158 	////////2011-05-26
1159 	genMultiMatrix[nThread].semaphorePlacedActivity.release();
1160 	//genMultiMatrix[nThread].cvForPlacedActivity.notify_one();
1161 
1162 	//time_t finish_time;
1163 	//time(&finish_time);
1164 	//int seconds=int(difftime(finish_time, start_time));
1165 	//int seconds=int(difftime(finish_time, process_start_time[nThread]));
1166 	int hours=seconds/3600;
1167 	seconds%=3600;
1168 	int minutes=seconds/60;
1169 	seconds%=60;
1170 
1171 	QString s;
1172 
1173 	bool zero=false;
1174 	if(mseconds==0)
1175 		zero=true;
1176 	int hh=mseconds/3600;
1177 	mseconds%=3600;
1178 	int mm=mseconds/60;
1179 	mseconds%=60;
1180 	int ss=mseconds;
1181 
1182 	QString tim;
1183 	if(hh>0){
1184 		tim+=" ";
1185 		tim+=tr("%1 h", "hours").arg(hh);
1186 	}
1187 	if(mm>0){
1188 		tim+=" ";
1189 		tim+=tr("%1 m", "minutes").arg(mm);
1190 	}
1191 	if(ss>0 || zero){
1192 		tim+=" ";
1193 		tim+=tr("%1 s", "seconds").arg(ss);
1194 	}
1195 	tim.remove(0, 1);
1196 	s+=QString("\n");
1197 	s+=tr("Max placed activities: %1 (at %2)", "%1 represents the maximum number of activities placed, %2 is a time interval").arg(mact).arg(tim);
1198 	///////
1199 
1200 	labels[nThread]->setText(tr("Current timetable: %1 out of %2 activities placed, %3h %4m %5s")
1201 	 .arg(na)
1202 	 .arg(gt.rules.nInternalActivities)
1203 	 .arg(hours)
1204 	 .arg(minutes)
1205 	 .arg(seconds)+s);
1206 }
1207 
closePressed()1208 void TimetableGenerateMultipleForm::closePressed()
1209 {
1210 	if(!simulation_running_multi)
1211 		this->close();
1212 }
1213