Solver::setMeetings()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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 Path
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 rmemeber to clone also path or create a new one
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 15
    public function __construct(Schedule $schedule, Application $laravel)
95
    {
96 15
        self::checkGlpsol();
97 15
        $this->path = Path::createPath();
98 15
        $this->schedule = $schedule;
99 15
        $this->laravel = $laravel;
100 15
    }
101
102
    /**
103
     * @throws OptimiseException
104
     */
105 15
    static private function checkGlpsol()
106
    {
107 15
        if (!(`which glpsol`))
108 10
            throw new OptimiseException('glpsol is not installed');
109 15
    }
110
111
    /**
112
     * @throws OptimiseException
113
     */
114 8
    public function __destruct()
115
    {
116 8
        $this->path = null; //call the path destruct
117 8
    }
118
119
    /**
120
     * @return Path
121
     */
122
    public function getPath()
123
    {
124
        return clone $this->path;
125
    }
126
127
    /**
128
     * @return Schedule
129
     */
130
    public function getSchedule()
131
    {
132
        return $this->schedule;
133
    }
134
135
    /**
136
     * @param Schedule $schedule
137
     * @return Solver
138
     */
139
    public function setSchedule($schedule)
140
    {
141
        $this->schedule = $schedule;
142
        return $this;
143
    }
144
145
    /**
146
     * @return \string[]
147
     */
148 6
    public function getUsers()
149
    {
150 6
        return $this->users;
151
    }
152
153
    /**
154
     * @param string[] $users
155
     * @return Solver
156
     */
157 12
    public function setUsers($users)
158
    {
159 12
        $this->users = $users;
160 12
        return $this;
161
    }
162
163
    /**
164
     * @return string[]
165
     */
166 6
    public function getMeetings()
167
    {
168 6
        return $this->meetings;
169
    }
170
171
    /**
172
     * @param string[] $meetings
173
     * @return Solver
174
     */
175 9
    public function setMeetings($meetings)
176
    {
177 9
        $this->meetings = $meetings;
178 9
        return $this;
179
    }
180
181
    /**
182
     * @return int
183
     */
184
    public function getTimeSlots()
185
    {
186
        return $this->timeSlots;
187
    }
188
189
    /**
190
     * @param int $timeSlots
191
     * @return Solver
192
     * @throws OptimiseException
193
     */
194 15
    public function setTimeSlots($timeSlots)
195
    {
196 15
        if (!is_int($timeSlots) || $timeSlots <= 0)
197 10
            throw new OptimiseException($timeSlots . ' is not integer or it is not >0');
198
199 15
        $this->timeSlots = $timeSlots;
200 15
        return $this;
201
    }
202
203
    /**
204
     * @return int
205
     */
206
    public function getMaxTimeSlots()
207
    {
208
        return $this->maxTimeSlots;
209
    }
210
211
    /**
212
     * @param int $maxTimeSlots
213
     * @return Solver
214
     * @throws OptimiseException
215
     */
216 15
    public function setMaxTimeSlots($maxTimeSlots)
217
    {
218 15
        if (!is_int($maxTimeSlots) || $maxTimeSlots <= 0)
219 10
            throw new OptimiseException($maxTimeSlots . ' is not integer or it is not >0');
220
221 15
        $this->maxTimeSlots = $maxTimeSlots;
222 15
        return $this;
223
    }
224
225
    /**
226
     * @return string[]
227
     */
228
    public function getMeetingsAvailability()
229
    {
230
        return $this->meetingsAvailability;
231
    }
232
233
    /**
234
     * @param string[] $meetingsAvailability
235
     * @return Solver
236
     * @throws OptimiseException
237
     */
238 9
    public function setMeetingsAvailability($meetingsAvailability)
239
    {
240 9
        $meetings = array_keys($meetingsAvailability);
241 9
        if (array_diff($meetings, $this->meetings))
242 6
            throw new OptimiseException('meetings different from meetings set');
243 9
        foreach ($meetingsAvailability as $key => $meetingsAvailabilityS) {
244 9
            $timeSlots = array_keys($meetingsAvailabilityS);//TODO this is useless, we can use directly $usersAvailabilityS
245
            //TODO check if keys are sequential and the last is the same with $this->timeSlots
246 9
            if (count($timeSlots) != $this->timeSlots)
247 6
                throw new OptimiseException('timeSlots different from timeSlots set');
248 9
            $meetingsAvailability[$key] = self::arrayPad($meetingsAvailabilityS, $this->timeSlots + $this->maxTimeSlots, 0);
0 ignored issues
show
Bug introduced by
It seems like $meetingsAvailabilityS defined by $meetingsAvailabilityS on line 243 can also be of type string; however, plunner\Console\Commands...mise\Solver::arrayPad() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
249 6
        }
250
251 9
        $this->meetingsAvailability = $meetingsAvailability;
0 ignored issues
show
Documentation Bug introduced by
It seems like $meetingsAvailability of type array<integer,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...
252 9
        return $this;
253
    }
254
255
    /**
256
     * implementation of arraypad that doesn't change original keys<br/>
257
     * <strong>CAUTION: Only positive $len</strong>
258
     * @param array $array
259
     * @return array
260
     */
261 9
    static private function arrayPad(array $array, $len, $pad)
262
    {
263 9
        $len = $len - count($array);
264 9
        for ($i = 0; $i < $len; $i++)
265 9
            $array[] = $pad;
266 9
        return $array;
267
    }
268
269
    /**
270
     * @return string[]
271
     */
272
    public function getMeetingsDuration()
273
    {
274
        return $this->meetingsDuration;
275
    }
276
277
    /**
278
     * @param string[] $meetingsDuration
279
     * @return Solver
280
     * @throws OptimiseException
281
     */
282 9
    public function setMeetingsDuration($meetingsDuration)
283
    {
284 9
        $meetings = array_keys($meetingsDuration);
285 9
        if (array_diff($meetings, $this->meetings)) {
286
            throw new OptimiseException('meetings different from meetings set');
287
        }
288 9
        foreach ($meetingsDuration as $duration) {
289 9
            $duration = (int)$duration; //TODO fix this (fix for optimise)
290 9
            if (!is_int($duration) || $duration <= 0)
291 9
                throw new OptimiseException('duration is not integer or it is not >0');
292 6
        }
293
294 9
        $this->meetingsDuration = $meetingsDuration;
295 9
        return $this;
296
    }
297
298
    /**
299
     * @return string[]
300
     */
301
    public function getUsersAvailability()
302
    {
303
        return $this->usersAvailability;
304
    }
305
306
    /**
307
     * @param string[] $usersAvailability
308
     * @return Solver
309
     * @throws OptimiseException
310
     */
311 9
    public function setUsersAvailability($usersAvailability)
312
    {
313 9
        $users = array_keys($usersAvailability);
314 9
        if (array_diff($users, $this->users))
315 6
            throw new OptimiseException('users different from users set');
316 9
        foreach ($usersAvailability as $key => $usersAvailabilityS) {
317 9
            $timeSlots = array_keys($usersAvailabilityS);//TODO this is useless, we can use directly $usersAvailabilityS
318 9
            if (count($timeSlots) != $this->timeSlots)
319 6
                throw new OptimiseException('timeSlots different from timeSlots set');
320
321 9
            $usersAvailability[$key] = self::arrayPad($usersAvailabilityS, $this->timeSlots + $this->maxTimeSlots, 0);
0 ignored issues
show
Bug introduced by
It seems like $usersAvailabilityS defined by $usersAvailabilityS on line 316 can also be of type string; however, plunner\Console\Commands...mise\Solver::arrayPad() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
322 6
        }
323
324 9
        $this->usersAvailability = $usersAvailability;
0 ignored issues
show
Documentation Bug introduced by
It seems like $usersAvailability of type array<integer,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...
325 9
        return $this;
326
    }
327
328
    /**
329
     * @return string[]
330
     */
331
    public function getUsersMeetings()
332
    {
333
        return $this->usersMeetings;
334
    }
335
336
    /**
337
     * @param string[] $usersMeetings
338
     * @return Solver
339
     * @throws OptimiseException
340
     */
341 9
    public function setUsersMeetings($usersMeetings)
342
    {
343 9
        $users = array_keys($usersMeetings);
344 9
        if (array_diff($users, $this->users))
345 6
            throw new OptimiseException('users different from users set');
346 9
        foreach ($usersMeetings as $usersMeetingsS) {
347 9
            $meetings = array_keys($usersMeetingsS);
348 9
            if (array_diff($meetings, $this->meetings))
349 9
                throw new OptimiseException('meetings different from meetings set');
350 6
        }
351
352 9
        $this->usersMeetings = $usersMeetings;
353 9
        return $this;
354
    }
355
356
    /**
357
     * @return Solver
358
     * @throws OptimiseException
359
     */
360 9
    public function solve()
361
    {
362 9
        $this->writeData();
363 9
        $this->writeModelFile();
364
        $event = $this->schedule->exec('glpsol --math ' . $this->path->getModelPath())->sendOutputTo($this->path->getOutputPath())->after(function () {
365 9
        }); //this just to execute in foreground
366 9
        if ($event->isDue($this->laravel))
367 9
            $event->run($this->laravel);
368
        //TODO catch glpsol errors
369 9
        return $this;
370
    }
371
372
    /**
373
     * @throws OptimiseException
374
     */
375 9
    private function writeData()
376
    {
377 9
        $this->checkData();
378 9
        $this->writeUsers();
379 9
        $this->writeMeetings();
380 9
        $this->writeMeetingsDuration();
381 9
        $this->writeMeetingsAvailability();
382 9
        $this->writeUsersAvailability();
383 9
        $this->writeUsersMeetings();
384 9
    }
385
386
    /**
387
     * @throws OptimiseException
388
     */
389 9
    private function checkData()
390
    {
391 9
        $this->checkArrayProprieties(['users', 'meetings', 'meetingsAvailability', 'meetingsDuration', 'usersAvailability', 'usersMeetings']);
392 9
        $this->checkIntProprieties(['timeSlots', 'maxTimeSlots']);
393 9
    }
394
395
    /**
396
     * @param $proprieties
397
     * @throws OptimiseException
398
     */
399 9
    private function checkArrayProprieties($proprieties)
400
    {
401 9
        foreach ($proprieties as $propriety)
402 9
            if (count($this->$propriety) == 0)
403 9
                throw new OptimiseException($propriety . ' property is not set correctly');
404 9
    }
405
406
    /**
407
     * @param $proprieties
408
     * @throws OptimiseException
409
     */
410 9
    private function checkIntProprieties($proprieties)
411
    {
412 9
        foreach ($proprieties as $propriety)
413 9
            if (!is_int($this->$propriety) || $this->$propriety <= 0)
414 9
                throw new OptimiseException($propriety . ' property is not set correctly');
415 9
    }
416
417
    /**
418
     * @throws OptimiseException
419
     */
420 9
    private function writeUsers()
421
    {
422 9
        self::writeCSVArrayNoKey($this->path->getUsersPath(), $this->users);
423 9
    }
424
425
    /**
426
     * @param string $file
427
     * @param array $data
428
     * @throws OptimiseException
429
     */
430 9
    static private function writeCSVArrayNoKey($file, $data)
431
    {
432
        $f = function ($fp, $data) {
433 9
            foreach ($data as $field) {
434 9
                fputcsv($fp, [$field]);
435 6
            }
436 9
        };
437
438 9
        self::writeCSV($file, $data, ['i'], $f);
439 9
    }
440
441
    /**
442
     * @param string $file
443
     * @param array $data
444
     * @param array $heading
445
     * @param \Closure $writer
446
     * @throws OptimiseException
447
     */
448 9
    static private function writeCSV($file, $data, $heading, \Closure $writer)
449
    {
450 9
        $fp = @fopen($file, 'w');
451 9
        if (!$fp)
452 6
            throw new OptimiseException('problem during creation of a file');
453
454 9
        fputcsv($fp, $heading);
455
456 9
        $writer($fp, $data);
457
458
        //fputcsv($fp, []); //empty line
459
460 9
        fclose($fp);
461 9
    }
462
463
    /**
464
     * @throws OptimiseException
465
     */
466 9
    private function writeMeetings()
467
    {
468 9
        self::writeCSVArrayNoKey($this->path->getMeetingsPath(), $this->meetings);
469 9
    }
470
471
    /**
472
     * @throws OptimiseException
473
     */
474 9
    private function writeMeetingsDuration()
475
    {
476 9
        self::writeCSVArray($this->path->getMeetingsDurationPath(), $this->meetingsDuration, 'MeetingsDuration');
477 9
    }
478
479
    /**
480
     * @param string $file
481
     * @param array $data
482
     * @param string $name
483
     * @throws OptimiseException
484
     */
485 9
    static private function writeCSVArray($file, $data, $name)
486
    {
487
        $f = function ($fp, $data) {
488 9
            foreach ($data as $key => $field) {
489 9
                fputcsv($fp, [$key, $field]);
490 6
            }
491 9
        };
492
493 9
        self::writeCSV($file, $data, ['i', $name], $f);
494 9
    }
495
496
    /**
497
     * @throws OptimiseException
498
     */
499 9
    private function writeMeetingsAvailability()
500
    {
501 9
        self::writeCSVMatrix($this->path->getMeetingsAvailabilityPath(), $this->meetingsAvailability, 'MeetingsAvailability');
502 9
    }
503
504
    /**
505
     * @param string $file
506
     * @param array $data
507
     * @param string $name
508
     * @throws OptimiseException
509
     */
510
    static private function writeCSVMatrix($file, $data, $name)
511
    {
512 9
        $f = function ($fp, $data) {
513 9
            foreach ($data as $key => $field) {
514 9
                foreach ($field as $key2 => $field2)
515 9
                    fputcsv($fp, [$key, $key2, $field2]);
516 6
            }
517 9
        };
518
519 9
        self::writeCSV($file, $data, ['i', 'j', $name], $f);
520 9
    }
521
522
    /**
523
     * @throws OptimiseException
524
     */
525 9
    private function writeUsersAvailability()
526
    {
527 9
        self::writeCSVMatrix($this->path->getUsersAvailabilityPath(), $this->usersAvailability, 'UsersAvailability');
528 9
    }
529
530
    /**
531
     * @throws OptimiseException
532
     */
533 9
    private function writeUsersMeetings()
534
    {
535 9
        self::writeCSVMatrix($this->path->getUsersMeetingsPath(), $this->usersMeetings, 'UsersMeetings');
536 9
    }
537
538
    /**
539
     * @throws OptimiseException
540
     */
541 9
    private function writeModelFile()
542
    {
543 9
        $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}');
544 9
        $strReplaceR = array($this->path->getUsersPath(), $this->path->getMeetingsPath(), $this->path->getUsersAvailabilityPath(), $this->path->getMeetingsAvailabilityPath(), $this->path->getUsersMeetingsPath(), $this->path->getMeetingsDurationPath(), $this->timeSlots, $this->maxTimeSlots, $this->path->getXPath(), $this->path->getYPath());
545 9
        $f = @fopen($this->path->getModelPath(), "w");
546 9
        if (!$f)
547 6
            throw new OptimiseException('problem during creation of a file');
548 9
        fwrite($f, str_replace($strReplaceS, $strReplaceR, file_get_contents(__DIR__ . "/model.stub")));
549 9
        fclose($f);
550 9
    }
551
552
    /**
553
     * @return array
554
     * @throws OptimiseException
555
     */
556 9
    public function getXResults()
557
    {
558 9
        return self::readCSVFile($this->path->getXPath());
559
    }
560
561
    /**
562
     * @param string $file
563
     * @return array
564
     * @throws OptimiseException
565
     */
566 9
    static private function readCSVFile($file)
567
    {
568 9
        if (!file_exists($file) || !filesize($file))
569 6
            throw new OptimiseException('no results file');
570
571 9
        $handle = @fopen($file, "r");
572 9
        if (!$handle)
573 6
            throw new OptimiseException('problems during reading the file');
574
575 9
        $ret = [];
576 9
        fgetcsv($handle); //skip head
577 9
        while (($data = fgetcsv($handle)) !== FALSE) {
578 9
            if (count($data) != 3) {
579
                fclose($handle);
580
                throw new OptimiseException('problems during parsing the file');
581
            }
582
583 9
            $ret[$data[0]][$data[1]] = $data[2];
584 6
        }
585
586 9
        fclose($handle);
587
588 9
        return $ret;
589
    }
590
591
    /**
592
     * @return array
593
     * @throws OptimiseException
594
     */
595 9
    public function getYResults()
596
    {
597 9
        return self::readCSVFile($this->path->getYPath());
598
    }
599
600
    /**
601
     * @return string
602
     * @throws OptimiseException
603
     */
604
    public function getOutput()
605
    {
606
        if (!($data = file_get_contents($this->path->getOutputPath())))
607
            throw new OptimiseException('problems during reading the file');
608
        return $data;
609
    }
610
}