Skip to content

Commit 64be3fc

Browse files
committed
Minor refactor to Glpk Solver
1 parent cfb5b5e commit 64be3fc

8 files changed

Lines changed: 58 additions & 29 deletions

File tree

app/Services/Lottery/Solvers/Glpk/Glpk.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ public function identifyWorstUnits(LotteryManifest $manifest, LotterySpec $spec)
6969
*/
7070
protected function glpkDistribution(LotteryManifest $manifest, LotterySpec $spec): array
7171
{
72-
$runner = $this->taskRunnerFactory->make(Task::GLPK_DISTRIBUTION);
73-
$result = $runner->execute($spec, $this->timeout, [
74-
'phase1_timeout' => $this->phase1Timeout,
75-
]);
72+
$runner = $this->taskRunnerFactory
73+
->make(Task::GLPK_DISTRIBUTION)
74+
->withContext([ 'phase1_timeout' => $this->phase1Timeout ]);
75+
76+
$result = $runner->execute($spec, $this->timeout);
7677

7778
$this->auditTask($manifest, $result, 'success');
7879

app/Services/Lottery/Solvers/Glpk/TaskRunners/GlpkDistribution.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ class GlpkDistribution extends TaskRunner
1616
* Phase 1: Find minimum satisfaction using GLPK optimization
1717
* Phase 2: Distribute units using GLPK with S constraint from Phase 1
1818
*/
19-
public function execute(LotterySpec $spec, float $timeout, array $context = []): TaskResult
19+
public function execute(LotterySpec $spec, float $timeout): TaskResult
2020
{
2121
$startTime = microtime(true);
2222

23-
$phase1Result = $this->executePhase1($spec, $context['phase1_timeout'] ?? $timeout);
23+
$phase1Result = $this->executePhase1($spec, $this->context['phase1_timeout'] ?? $timeout);
2424
$phase2Result = $this->executePhase2($spec, $timeout, $phase1Result->get('min_satisfaction'));
2525

2626
return $this->mergeTaskResults($startTime, $phase1Result, $phase2Result);
@@ -41,9 +41,11 @@ protected function executePhase1(LotterySpec $spec, float $timeout): TaskResult
4141
*/
4242
protected function executePhase2(LotterySpec $spec, float $timeout, int $minSatisfaction): TaskResult
4343
{
44-
$distributionRunner = app(TaskRunnerFactory::class)->make(Task::UNIT_DISTRIBUTION);
44+
$runner = app(TaskRunnerFactory::class)
45+
->make(Task::UNIT_DISTRIBUTION)
46+
->withContext([ 'min_satisfaction' => $minSatisfaction ]);
4547

46-
return $distributionRunner->execute($spec, $timeout, [ 'min_satisfaction' => $minSatisfaction ]);
48+
return $runner->execute($spec, $timeout);
4749
}
4850

4951
/**

app/Services/Lottery/Solvers/Glpk/TaskRunners/HybridDistribution.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class HybridDistribution extends TaskRunner
1717
/**
1818
* Execute hybrid distribution task (phase1 binary search + phase2 glpk).
1919
*/
20-
public function execute(LotterySpec $spec, float $timeout, array $context = []): TaskResult
20+
public function execute(LotterySpec $spec, float $timeout): TaskResult
2121
{
2222
// Sum of step timeouts tends to $timeout * 2 (worst case, highly unlikely).
2323
$iterations = (int) ceil(log($spec->familyCount(), 2)) + 1;
@@ -53,16 +53,17 @@ protected function searchBestDistribution(Generator $generator, LotterySpec $spe
5353

5454
while ($generator->valid()) {
5555
try {
56-
$taskResults[] = $distributionRunner->execute($spec, $timeout, [
57-
'min_satisfaction' => $generator->current(),
58-
]);
56+
$taskResults[] = $distributionRunner
57+
->withContext([ 'min_satisfaction' => $generator->current() ])
58+
->execute($spec, $timeout);
59+
5960
$generator->send(FeasibilityResult::FEASIBLE);
6061
} catch (GlpkInfeasibleException) {
6162
$generator->send(FeasibilityResult::INFEASIBLE);
6263
}
6364
}
6465

65-
return $taskResults ?? [];
66+
return $taskResults;
6667
}
6768

6869
/**

app/Services/Lottery/Solvers/Glpk/TaskRunners/MinSatisfaction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class MinSatisfaction extends TaskRunner
1919
* Returns optimal S or throws GlpkTimeoutException.
2020
* On timeout, caller should use binarySearchGenerator() for fallback.
2121
*/
22-
public function execute(LotterySpec $spec, float $timeout, array $context = []): TaskResult
22+
public function execute(LotterySpec $spec, float $timeout): TaskResult
2323
{
2424
$startTime = microtime(true);
2525
$minSatisfaction = $this->findMinSatisfactionWithGlpk($spec, $timeout);

app/Services/Lottery/Solvers/Glpk/TaskRunners/TaskRunner.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
use App\Services\Lottery\Solvers\Glpk\DataGenerator;
99
use App\Services\Lottery\Solvers\Glpk\DataObjects\TaskResult;
1010
use App\Services\Lottery\Solvers\Glpk\Enums\Task;
11-
use App\Services\Lottery\Solvers\Glpk\ModelGenerator;
12-
use App\Services\Lottery\Solvers\Glpk\SolutionParser;
1311
use App\Services\Lottery\Solvers\Glpk\lib\Files;
1412
use App\Services\Lottery\Solvers\Glpk\lib\Process;
13+
use App\Services\Lottery\Solvers\Glpk\ModelGenerator;
14+
use App\Services\Lottery\Solvers\Glpk\SolutionParser;
1515
use Closure;
1616

1717
abstract class TaskRunner
@@ -20,23 +20,37 @@ abstract class TaskRunner
2020

2121
protected string $glpsolPath;
2222
protected Files $files;
23+
protected $context = [];
2324

2425
public function __construct(
2526
protected ModelGenerator $modelGenerator,
2627
protected DataGenerator $dataGenerator,
2728
protected SolutionParser $solutionParser,
2829
) {
2930
$config = config('lottery.solvers.glpk.config');
31+
3032
$this->glpsolPath = $config['glpsol_path'] ?? '/usr/bin/glpsol';
3133
$this->files = new Files($config['temp_dir'] ?? sys_get_temp_dir());
3234
}
3335

36+
/**
37+
* Set additional context for the task runner.
38+
*
39+
* @param array<string, mixed> $context
40+
*/
41+
public function withContext(array $context): self
42+
{
43+
$this->context = $context;
44+
45+
return $this;
46+
}
47+
3448
/**
3549
* Execute the GLPK task.
3650
*
3751
* @param array $context Additional context data required by specific task runners
3852
*/
39-
abstract public function execute(LotterySpec $spec, float $timeout, array $context = []): TaskResult;
53+
abstract public function execute(LotterySpec $spec, float $timeout): TaskResult;
4054

4155
/**
4256
* Run GLPK solver on model and data files.

app/Services/Lottery/Solvers/Glpk/TaskRunners/UnitDistribution.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class UnitDistribution extends TaskRunner
1818
*
1919
* @param array $context Must contain 'min_satisfaction' key
2020
*/
21-
public function execute(LotterySpec $spec, float $timeout, array $context = []): TaskResult
21+
public function execute(LotterySpec $spec, float $timeout): TaskResult
2222
{
23-
$minSatisfaction = $context['min_satisfaction'] ?? null;
23+
$minSatisfaction = $this->context['min_satisfaction'] ?? null;
2424

2525
if ($minSatisfaction === null) {
2626
throw new InvalidArgumentException('UnitDistribution requires min_satisfaction in context');

app/Services/Lottery/Solvers/Glpk/TaskRunners/WorstUnitsPruning.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class WorstUnitsPruning extends TaskRunner
1515
*
1616
* Identifies which units should be discarded to balance the lottery.
1717
*/
18-
public function execute(LotterySpec $spec, float $timeout, array $context = []): TaskResult
18+
public function execute(LotterySpec $spec, float $timeout): TaskResult
1919
{
2020
$startTime = microtime(true);
2121

tests/Unit/Lottery/Glpk/Runners/UnitDistributionTest.php

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use App\Services\Lottery\Solvers\Glpk\Enums\Task;
77
use App\Services\Lottery\Solvers\Glpk\TaskRunners\UnitDistribution;
88

9-
109
uses()->group('Unit.Lottery.Glpk');
1110

1211
beforeEach(function () {
@@ -26,7 +25,9 @@
2625
$timeout = 10;
2726

2827
$runner = app(UnitDistribution::class);
29-
$result = $runner->execute($spec, $timeout, ['min_satisfaction' => 2]);
28+
$result = $runner
29+
->withContext(['min_satisfaction' => 2])
30+
->execute($spec, $timeout);
3031

3132
// Check TaskResult structure
3233
expect($result->task)->toBe(Task::UNIT_DISTRIBUTION);
@@ -55,7 +56,7 @@
5556

5657
$runner = app(UnitDistribution::class);
5758

58-
expect(fn () => $runner->execute($spec, $timeout, []))
59+
expect(fn () => $runner->execute($spec, $timeout))
5960
->toThrow(InvalidArgumentException::class, 'UnitDistribution requires min_satisfaction in context');
6061
});
6162

@@ -68,7 +69,7 @@
6869

6970
$runner = app(UnitDistribution::class);
7071

71-
expect(fn () => $runner->execute($spec, $timeout, ['min_satisfaction' => null]))
72+
expect(fn () => $runner->execute($spec, $timeout))
7273
->toThrow(InvalidArgumentException::class, 'UnitDistribution requires min_satisfaction in context');
7374
});
7475

@@ -85,7 +86,9 @@
8586
$timeout = 10;
8687

8788
$runner = app(UnitDistribution::class);
88-
$result = $runner->execute($spec, $timeout, ['min_satisfaction' => 2]);
89+
$result = $runner
90+
->withContext(['min_satisfaction' => 2])
91+
->execute($spec, $timeout);
8992

9093
$distribution = $result->get('distribution');
9194

@@ -108,7 +111,9 @@
108111
$timeout = 5;
109112

110113
$runner = app(UnitDistribution::class);
111-
$result = $runner->execute($spec, $timeout, ['min_satisfaction' => 1]);
114+
$result = $runner
115+
->withContext(['min_satisfaction' => 1])
116+
->execute($spec, $timeout);
112117

113118
$metadata = $result->metadata;
114119

@@ -140,15 +145,21 @@
140145
$runner = app(UnitDistribution::class);
141146

142147
// Test with S=1 (strict)
143-
$result1 = $runner->execute($spec, $timeout, ['min_satisfaction' => 1]);
148+
$result1 = $runner
149+
->withContext(['min_satisfaction' => 1])
150+
->execute($spec, $timeout);
144151
expect($result1->get('distribution'))->toHaveCount(3);
145152

146153
// Test with S=2 (relaxed)
147-
$result2 = $runner->execute($spec, $timeout, ['min_satisfaction' => 2]);
154+
$result2 = $runner
155+
->withContext(['min_satisfaction' => 2])
156+
->execute($spec, $timeout);
148157
expect($result2->get('distribution'))->toHaveCount(3);
149158

150159
// Test with S=3 (very relaxed)
151-
$result3 = $runner->execute($spec, $timeout, ['min_satisfaction' => 3]);
160+
$result3 = $runner
161+
->withContext(['min_satisfaction' => 3])
162+
->execute($spec, $timeout);
152163
expect($result3->get('distribution'))->toHaveCount(3);
153164
});
154165
});

0 commit comments

Comments
 (0)