Completed
Pull Request — master (#28)
by claudio
07:42 queued 01:16
created

Solver::getOutput()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 9.4286
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Claudio Cardinale <[email protected]>
5
 * Date: 07/12/15
6
 * Time: 21.18
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
 */
19
20
namespace plunner\Console\Commands\Optimise;
21
22
use \Illuminate\Console\Scheduling\Schedule;
23
use \Illuminate\Foundation\Application;
24
25
/**
26
 * Class Solver
27
 * @package plunner\Console\Commands\Optimise
28
 * @author Claudio Cardinale <[email protected]>
29
 * @copyright 2015 Claudio Cardinale
30
 * @version 1.0.0
31
 */
32
class Solver
33
{
34
    /**
35
     * @var string
36
     */
37
    private $path;
38
    /**
39
     * @var string[]
40
     */
41
    private $users;
42
    /**
43
     * @var string[]
44
     */
45
    private $meetings;
46
    /**
47
     * @var int
48
     */
49
    private $timeSlots = 0;
50
    /**
51
     * @var int
52
     */
53
    private $maxTimeSlots = 0;
54
    /**
55
     * @var string[]
56
     */
57
    private $meetingsAvailability;
58
    /**
59
     * @var string[]
60
     */
61
    private $meetingsDuration;
62
    /**
63
     * @var string[]
64
     */
65
    private $usersAvailability;
66
    /**
67
     * @var string[]
68
     */
69
    private $usersMeetings;
70
71
    /**
72
    * @var Schedule laravel schedule object needed to perform command in background
73
    */
74
    private $schedule;
75
76
    /**
77
     * @var Application
78
     */
79
    private $laravel;
80
81
    //TODo clone function
82
    //TODO mehtod to check if all variables are correctly set
83
    //TODO check no duplicates
84
    //TODO exception if glpsol return erros
85
    //TODO intercept all erros of system calls like mkdir
86
    //TODO to_string
87
88
    /**
89
     * Solver constructor.
90
     * @param Schedule $schedule
91
     * @param Application $laravel
92
     * @throws OptimiseException on general problems
93
     */
94 4
    public function __construct(Schedule $schedule, Application $laravel)
95
    {
96 4
        self::checkGlpsol();
97 4
        $this->createPath();
98 4
        $this->schedule = $schedule;
99 4
        $this->laravel = $laravel;
100 4
    }
101
102
    /**
103
     * @throws OptimiseException
104
     */
105 2
    function __destruct()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
106
    {
107 2
        if ($this->path && is_dir($this->path) && !self::delTree($this->path))
108 2
            throw new OptimiseException('problems during removing of path directory');
109 2
    }
110
111
    /**
112
     * @throws OptimiseException
113
     */
114 4
    static private function checkGlpsol()
115
    {
116 4
        if(!(`which glpsol`))
117 4
            throw new OptimiseException('glpsol is not installed');
118 4
    }
119
120
    /**
121
     * remove a no empty dir
122
     * @param $dir
123
     * @return bool
124
     */
125 2
    private static function delTree($dir) {
126 2
        $files = array_diff(scandir($dir), array('.','..'));
127 2
        foreach ($files as $file) {
128 2
            (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file");
129 2
        }
130 2
        return rmdir($dir);
131
    }
132
133
134
    /**
135
     * @throws OptimiseException on problems during creation of tmp dir
136
     */
137 4
    private function createPath()
138
    {
139 4
        $this->path = tempnam(sys_get_temp_dir(), 'OPT'); //TODO check the return in case of errors this return false on failure
140 4
        unlink($this->path); //remove file to create a dir
141 4
        if(file_exists($this->path))
142 4
            throw new OptimiseException('problem during creation of tmp dir (the directory already exists)');
143 4
        if(!@mkdir($this->path))
144 4
            throw new OptimiseException('problem during creation of tmp dir (mkdir problem)');;
145 4
        if(! is_dir($this->path))
146 4
            throw new OptimiseException('problem during creation of tmp dir (it is not possible to create directory)');
147 4
    }
148
149
    /**
150
     * @return string
151
     */
152
    public function getPath()
153
    {
154
        return $this->path;
155
    }
156
157
    /**
158
     * @return Schedule
159
     */
160
    public function getSchedule()
161
    {
162
        return $this->schedule;
163
    }
164
165
    /**
166
     * @param Schedule $schedule
167
     * @return Solver
168
     */
169
    public function setSchedule($schedule)
170
    {
171
        $this->schedule = $schedule;
172
        return $this;
173
    }
174
175
    /**
176
     * @return \string[]
177
     */
178 2
    public function getUsers()
179
    {
180 2
        return $this->users;
181
    }
182
183
    /**
184
     * @param \string[] $users
185
     * @return Solver
186
     */
187 4
    public function setUsers($users)
188
    {
189 4
        $this->users = $users;
0 ignored issues
show
Documentation Bug introduced by
It seems like $users of type array<integer,object<string>> is incompatible with the declared type array<integer,string> of property $users.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
190 4
        return $this;
191
    }
192
193
    /**
194
     * @return \string[]
195
     */
196 2
    public function getMeetings()
197
    {
198 2
        return $this->meetings;
199
    }
200
201
    /**
202
     * @param \string[] $meetings
203
     * @return Solver
204
     */
205 4
    public function setMeetings($meetings)
206
    {
207 4
        $this->meetings = $meetings;
0 ignored issues
show
Documentation Bug introduced by
It seems like $meetings of type array<integer,object<string>> is incompatible with the declared type array<integer,string> of property $meetings.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
208 4
        return $this;
209
    }
210
211
    /**
212
     * @return int
213
     */
214
    public function getTimeSlots()
215
    {
216
        return $this->timeSlots;
217
    }
218
219
    /**
220
     * @param int $timeSlots
221
     * @return Solver
222
     * @throws OptimiseException
223
     */
224 4
    public function setTimeSlots($timeSlots)
225
    {
226 4
        if(!is_int($timeSlots) || $timeSlots <=0)
227 4
            throw new OptimiseException('$timeSlots is not integer or it is not >0');
228
229 4
        $this->timeSlots = $timeSlots;
230 4
        return $this;
231
    }
232
233
    /**
234
     * @return int
235
     */
236
    public function getMaxTimeSlots()
237
    {
238
        return $this->maxTimeSlots;
239
    }
240
241
    /**
242
     * @param int $maxTimeSlots
243
     * @return Solver
244
     * @throws OptimiseException
245
     */
246 4
    public function setMaxTimeSlots($maxTimeSlots)
247
    {
248 4
        if(!is_int($maxTimeSlots) || $maxTimeSlots <=0)
249 4
            throw new OptimiseException('$maxTimeSlots is not integer or it is not >0');
250
251 4
        $this->maxTimeSlots = $maxTimeSlots;
252 4
        return $this;
253
    }
254
255
    /**
256
     * @return \string[]
257
     */
258
    public function getMeetingsAvailability()
259
    {
260
        return $this->meetingsAvailability;
261
    }
262
263
    /**
264
     * @param \string[] $meetingsAvailability
265
     * @return Solver
266
     * @throws OptimiseException
267
     */
268 4
    public function setMeetingsAvailability($meetingsAvailability)
269
    {
270 4
        $meetings = array_keys($meetingsAvailability);
271 4
        if(array_diff($meetings, $this->meetings))
272 4
            throw new OptimiseException('meetings different from meetings set');
273 4
        foreach($meetingsAvailability as $key=>$meetingsAvailabilityS) {
274 4
            $timeSlots = array_keys($meetingsAvailabilityS);
275 4
            if(count($timeSlots) != $this->timeSlots)
276 4
                throw new OptimiseException('timeSlots different from timeSlots set');
277 4
            $meetingsAvailability[$key] = self::arrayPad($meetingsAvailabilityS, $this->timeSlots + $this->maxTimeSlots, 0);
278 4
        }
279
280 4
        $this->meetingsAvailability = $meetingsAvailability;
0 ignored issues
show
Documentation Bug introduced by
It seems like $meetingsAvailability of type array<integer,object<string>|array> is incompatible with the declared type array<integer,string> of property $meetingsAvailability.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
281 4
        return $this;
282
    }
283
284
    /**
285
     * @return \string[]
286
     */
287
    public function getMeetingsDuration()
288
    {
289
        return $this->meetingsDuration;
290
    }
291
292
    /**
293
     * @param \string[] $meetingsDuration
294
     * @return Solver
295
     * @throws OptimiseException
296
     */
297 4
    public function setMeetingsDuration($meetingsDuration)
298
    {
299 4
        $meetings = array_keys($meetingsDuration);
300 4
        if(array_diff($meetings, $this->meetings)) {
301
            print "";
302
            throw new OptimiseException('meetings different from meetings set');
303
        }
304 4
        foreach($meetingsDuration as $duration) {
305 4
            $duration = (int) $duration; //TODO fix this (fix for optimise)
306 4
            if(!is_int($duration) || $duration <=0)
307 4
                throw new OptimiseException('duration is not integer or it is not >0');
308 4
        }
309
310 4
        $this->meetingsDuration = $meetingsDuration;
0 ignored issues
show
Documentation Bug introduced by
It seems like $meetingsDuration of type array<integer,object<string>> is incompatible with the declared type array<integer,string> of property $meetingsDuration.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
311 4
        return $this;
312
    }
313
314
    /**
315
     * @return \string[]
316
     */
317
    public function getUsersAvailability()
318
    {
319
        return $this->usersAvailability;
320
    }
321
322
    /**
323
     * @param \string[] $usersAvailability
324
     * @return Solver
325
     * @throws OptimiseException
326
     */
327 4
    public function setUsersAvailability($usersAvailability)
328
    {
329 4
        $users = array_keys($usersAvailability);
330 4
        if(array_diff($users, $this->users))
331 4
            throw new OptimiseException('users different from users set');
332 4
        foreach($usersAvailability as $key=>$usersAvailabilityS) {
333 4
            $timeSlots = array_keys($usersAvailabilityS);
334 4
            if(count($timeSlots) != $this->timeSlots)
335 4
                throw new OptimiseException('timeSlots different from timeSlots set');
336
337 4
            $usersAvailability[$key] = self::arrayPad($usersAvailabilityS, $this->timeSlots + $this->maxTimeSlots, 0);
338 4
        }
339
340 4
        $this->usersAvailability = $usersAvailability;
0 ignored issues
show
Documentation Bug introduced by
It seems like $usersAvailability of type array<integer,object<string>|array> is incompatible with the declared type array<integer,string> of property $usersAvailability.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
341 4
        return $this;
342
    }
343
344
    /**
345
     * @return \string[]
346
     */
347
    public function getUsersMeetings()
348
    {
349
        return $this->usersMeetings;
350
    }
351
352
    /**
353
     * @param \string[] $usersMeetings
354
     * @return Solver
355
     * @throws OptimiseException
356
     */
357 4
    public function setUsersMeetings($usersMeetings)
358
    {
359 4
        $users = array_keys($usersMeetings);
360 4
        if(array_diff($users, $this->users))
361 4
            throw new OptimiseException('users different from users set');
362 4
        foreach($usersMeetings as $usersMeetingsS) {
363 4
            $meetings = array_keys($usersMeetingsS);
364 4
            if(array_diff($meetings, $this->meetings))
365 4
                throw new OptimiseException('meetings different from meetings set');
366 4
        }
367
368 4
        $this->usersMeetings = $usersMeetings;
0 ignored issues
show
Documentation Bug introduced by
It seems like $usersMeetings of type array<integer,object<string>> is incompatible with the declared type array<integer,string> of property $usersMeetings.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
369 4
        return $this;
370
    }
371
372
    /**
373
     * @throws OptimiseException
374
     */
375 4
    private function writeUsers()
376
    {
377 4
        self::writeCSVArrayNoKey($this->getUsersPath(), $this->users);
378 4
    }
379
380
    /**
381
     * @throws OptimiseException
382
     */
383 4
    private function writeMeetings()
384
    {
385 4
        self::writeCSVArrayNoKey($this->getMeetingsPath(), $this->meetings);
386 4
    }
387
388
    /**
389
     * @throws OptimiseException
390
     */
391 4
    private function writeMeetingsDuration()
392
    {
393 4
        self::writeCSVArray($this->getMeetingsDurationPath(), $this->meetingsDuration, 'MeetingsDuration');
394 4
    }
395
396
    /**
397
     * @throws OptimiseException
398
     */
399 4
    private function writeMeetingsAvailability()
400
    {
401 4
        self::writeCSVMatrix($this->getMeetingsAvailabilityPath(), $this->meetingsAvailability, 'MeetingsAvailability');
402 4
    }
403
404
    /**
405
     * @throws OptimiseException
406
     */
407 4
    private function writeUsersAvailability()
408
    {
409 4
        self::writeCSVMatrix($this->getUsersAvailabilityPath(), $this->usersAvailability, 'UsersAvailability');
410 4
    }
411
412
    /**
413
     * @throws OptimiseException
414
     */
415 4
    private function writeUsersMeetings()
416
    {
417 4
        self::writeCSVMatrix($this->getUsersMeetingsPath(), $this->usersMeetings, 'UsersMeetings');
418 4
    }
419
420
    /**
421
     * @param string $file
422
     * @param array $data
423
     * @throws OptimiseException
424
     */
425 4
    static private function writeCSVArrayNoKey($file, $data)
426
    {
427
        $f = function ($fp, $data){
428 4
            foreach ($data as $field) {
429 4
                fputcsv($fp, [$field]);
430 4
            }
431 4
        };
432
433 4
        self::writeCSV($file, $data, ['i'], $f);
434 4
    }
435
436
    /**
437
     * @param string $file
438
     * @param array $data
439
     * @param string $name
440
     * @throws OptimiseException
441
     */
442 4
    static private function writeCSVArray($file, $data, $name)
443
    {
444
        $f = function ($fp, $data){
445 4
            foreach ($data as $key=>$field) {
446 4
                fputcsv($fp, [$key, $field]);
447 4
            }
448 4
        };
449
450 4
        self::writeCSV($file, $data, ['i', $name], $f);
451 4
    }
452
453
    /**
454
     * @param string $file
455
     * @param array $data
456
     * @param string $name
457
     * @throws OptimiseException
458
     */
459 4
    static private function writeCSVMatrix($file, $data, $name)
460
    {
461
        $f = function ($fp, $data){
462 4
            foreach ($data as $key=>$field) {
463 4
                foreach ($field as $key2=>$field2)
464 4
                    fputcsv($fp, [$key, $key2, $field2]);
465 4
            }
466 4
        };
467
468 4
        self::writeCSV($file, $data, ['i', 'j', $name], $f);
469 4
    }
470
471
    /**
472
     * @param string $file
473
     * @param array $data
474
     * @param array $heading
475
     * @param \Closure $writer
476
     * @throws OptimiseException
477
     */
478 4
    static private function writeCSV($file, $data, $heading, \Closure $writer)
479
    {
480 4
        $fp = @fopen($file, 'w');
481 4
        if(!$fp)
482 4
            throw new OptimiseException('problem during creation of a file');
483
484 4
        fputcsv($fp, $heading);
485
486 4
        $writer($fp, $data);
487
488
        //fputcsv($fp, []); //empty line
489
490 4
        fclose($fp);
491 4
    }
492
493
    /**
494
     * @return Solver
495
     * @throws OptimiseException
496
     */
497 4
    public function solve()
498
    {
499 4
        $this->writeData();
500 4
        $this->writeModelFile();
501
        $event = $this->schedule->exec('glpsol --math '.$this->getModelPath())->sendOutputTo($this->getOutputPath())->after(function () { }); //this just to execute in foreground
502 4
        if($event->isDue($this->laravel))
503 4
            $event->run($this->laravel);
504
        //TODO catch glpsol errors
505 4
        return $this;
506
    }
507
508
    /**
509
     * @throws OptimiseException
510
     */
511 4
    private function writeModelFile()
512
    {
513 4
        $strReplaceS = array('{USERS_PATH}', '{MEETINGS_PATH}', '{USER_AVAILABILITY_PATH}', '{MEETINGS_AVAILABILITY_PATH}', '{USER_MEETINGS_PATH}', '{MEETINGS_DURATION_PATH}', '{TIME_SLOTS}', '{MAX_TIME_SLOTS}', '{X_OUT_PATH}', '{Y_OUT_PATH}');
514 4
        $strReplaceR = array($this->getUsersPath(), $this->getMeetingsPath(), $this->getUsersAvailabilityPath(), $this->getMeetingsAvailabilityPath(), $this->getUsersMeetingsPath(), $this->getMeetingsDurationPath(), $this->timeSlots, $this->maxTimeSlots, $this->getXPath(), $this->getYPath());
515 4
        $f = @fopen($this->getModelPath(), "w");
516 4
        if(!$f)
517 4
            throw new OptimiseException('problem during creation of a file');
518 4
        fwrite($f, str_replace($strReplaceS, $strReplaceR, file_get_contents(__DIR__ . "/model.stub")));
519 4
        fclose($f);
520 4
    }
521
522
    /**
523
     * @return array
524
     * @throws OptimiseException
525
     */
526 4
    public function getXResults()
527
    {
528 4
        return self::readCSVFile($this->getXPath());
529
    }
530
531
    /**
532
     * @return array
533
     * @throws OptimiseException
534
     */
535 4
    public function getYResults()
536
    {
537 4
        return self::readCSVFile($this->getYPath());
538
    }
539
540
    /**
541
     * @return string
542
     * @throws OptimiseException
543
     */
544
    public function getOutput()
545
    {
546
        if(!($data = file_get_contents($this->getOutputPath())))
547
            throw new OptimiseException('problems during reading the file');
548
        return $data;
549
    }
550
551
    /**
552
     * @param string $file
553
     * @return array
554
     * @throws OptimiseException
555
     */
556 4
    static private function readCSVFile($file)
557
    {
558 4
        if(!file_exists($file) || !filesize($file))
559 4
            throw new OptimiseException('no results file');
560
561 4
        $handle = @fopen($file,"r");
562 4
        if(!$handle)
563 4
            throw new OptimiseException('problems during reading the file');
564
565 4
        $ret = [];
566 4
        fgetcsv($handle); //skip head
567 4
        while (($data = fgetcsv($handle)) !== FALSE) {
568 4
            if(count($data) != 3) {
569
                fclose($handle);
570
                throw new OptimiseException('problems during parsing the file');
571
            }
572
573 4
            $ret[$data[0]][$data[1]] = $data[2];
574 4
        }
575
576 4
        fclose($handle);
577
578 4
        return $ret;
579
    }
580
581
    /**
582
     * @return string
583
     */
584 4
    private function getModelPath()
585
    {
586 4
        return $this->path.'/model.mod';
587
    }
588
589
    /**
590
     * @return string
591
     */
592 4
    private function getUsersPath()
593
    {
594 4
        return $this->path.'/Users.csv';
595
    }
596
597
    /**
598
     * @return string
599
     */
600 4
    private function getMeetingsPath()
601
    {
602 4
        return $this->path.'/Meeting.csv';
603
    }
604
605
    /**
606
     * @return string
607
     */
608 4
    private function getMeetingsDurationPath()
609
    {
610 4
        return $this->path.'/MeetingsDuration.csv';
611
    }
612
613
    /**
614
     * @return string
615
     */
616 4
    private function getMeetingsAvailabilityPath()
617
    {
618 4
        return $this->path.'/MeetingsAvailability.csv';
619
    }
620
621
    /**
622
     * @return string
623
     */
624 4
    private function getUsersAvailabilityPath()
625
    {
626 4
        return $this->path.'/UsersAvailability.csv';
627
    }
628
629
    /**
630
     * @return string
631
     */
632 4
    private function getUsersMeetingsPath()
633
    {
634 4
        return $this->path.'/UsersMeetings.csv';
635
    }
636
637
    /**
638
     * @return string
639
     */
640 4
    private function getXPath()
641
    {
642 4
        return $this->path.'/x.csv';
643
    }
644
645
    /**
646
     * @return string
647
     */
648 4
    private function getYPath()
649
    {
650 4
        return $this->path.'/y.csv';
651
    }
652
653
    /**
654
     * @return string
655
     */
656 4
    private function getOutputPath()
657
    {
658 4
        return $this->path.'/out.txt';
659
    }
660
661
    /**
662
     * @throws OptimiseException
663
     */
664 4
    private function writeData()
665
    {
666 4
        $this->checkData();
667 4
        $this->writeUsers();
668 4
        $this->writeMeetings();
669 4
        $this->writeMeetingsDuration();
670 4
        $this->writeMeetingsAvailability();
671 4
        $this->writeUsersAvailability();
672 4
        $this->writeUsersMeetings();
673 4
    }
674
675
    /**
676
     * @throws OptimiseException
677
     */
678 4
    private function checkData()
679
    {
680 4
        $this->checkArrayProprieties(['users', 'meetings', 'meetingsAvailability', 'meetingsDuration', 'usersAvailability', 'usersMeetings']);
681 4
        $this->checkIntProprieties(['timeSlots', 'maxTimeSlots']);
682 4
    }
683
684
    /**
685
     * @param $proprieties
686
     * @throws OptimiseException
687
     */
688 4
    private function checkArrayProprieties($proprieties)
689
    {
690 4
        foreach($proprieties as $propriety)
691 4
            if(count($this->$propriety)==0)
692 4
                throw new OptimiseException($propriety.' is not set correctly');
693 4
    }
694
695
    /**
696
     * @param $proprieties
697
     * @throws OptimiseException
698
     */
699 4
    private function checkIntProprieties($proprieties)
700
    {
701 4
        foreach($proprieties as $propriety)
702 4
            if(!is_int($this->$propriety) || $this->$propriety <= 0)
703 4
                throw new OptimiseException($propriety.' is not set correctly');
704 4
    }
705
706
    /**
707
     * implementation of arraypad that doesn't change original keys<br/>
708
     * <strong>CAUTION: Only positive $len</strong>
709
     * @param array $array
710
     * @return array
711
     */
712 4
    static private function arrayPad(array $array, $len, $pad)
713
    {
714 4
        $len = $len - count($array);
715 4
        for($i = 0; $i<$len; $i++)
716 4
            $array[] = $pad;
717 4
        return $array;
718
    }
719
}