From c4c1016a7c1b0f75f7b154c2548d3fa78a6ab895 Mon Sep 17 00:00:00 2001 From: Marcin Lenkowski Date: Sun, 8 May 2022 20:40:22 +0200 Subject: [PATCH 1/4] test rewrite to phpunit --- .gitignore | 2 + Simplex/Helpers.php | 111 ++++++++++---------- composer.json | 56 +++++++---- example.php | 17 ++-- phpunit.xml | 17 ++++ tests/FractionTest.php | 89 ++++++++++++++++ tests/FractionTest.phpt | 67 ------------- tests/HelpersTest.php | 48 +++++++++ tests/HelpersTest.phpt | 38 ------- tests/TableTest.php | 42 ++++++++ tests/TableTest.phpt | 35 ------- tests/TaskTest.phpt | 21 ---- tests/TasksTest.php | 217 ++++++++++++++++++++++++++++++++++++++++ tests/bootstrap.php | 6 -- tests/tasks/Task1.phpt | 50 --------- tests/tasks/Task2.phpt | 60 ----------- tests/tasks/Task3.phpt | 91 ----------------- 17 files changed, 513 insertions(+), 454 deletions(-) create mode 100644 phpunit.xml create mode 100644 tests/FractionTest.php delete mode 100644 tests/FractionTest.phpt create mode 100644 tests/HelpersTest.php delete mode 100644 tests/HelpersTest.phpt create mode 100644 tests/TableTest.php delete mode 100644 tests/TableTest.phpt delete mode 100644 tests/TaskTest.phpt create mode 100644 tests/TasksTest.php delete mode 100644 tests/bootstrap.php delete mode 100644 tests/tasks/Task1.phpt delete mode 100644 tests/tasks/Task2.phpt delete mode 100644 tests/tasks/Task3.phpt diff --git a/.gitignore b/.gitignore index 8701a21..8ec3301 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /nbproject /tests/output /composer.lock +/.idea +/.phpunit.result.cache diff --git a/Simplex/Helpers.php b/Simplex/Helpers.php index f4cb30e..b077c3a 100644 --- a/Simplex/Helpers.php +++ b/Simplex/Helpers.php @@ -15,62 +15,59 @@ class Helpers { - /** - * @param int $a - * @param int $b - * @return int - */ - static function gcd($a, $b) - { - if (!self::isInt($a) || !self::isInt($b)) { - throw new \InvalidArgumentException('Integers expected for gcd.'); - } - - $a = (int) abs($a); - $b = (int) abs($b); - - if ($a === 0 && $b === 0) { - throw new \InvalidArgumentException('At least one number must not be a zero.'); - } - - if ($a === 0) return abs($b); - if ($b === 0) return abs($a); - - return abs(self::gcdRecursive($a, $b)); - } - - - - /** - * @param int $a - * @param int $b - * @return int - */ - private static function gcdRecursive($a, $b) - { - return ($a % $b) ? self::gcdRecursive($b,$a % $b) : $b; - } - - - - /** - * @param numeric $n - * @return int -1, 0, 1 - */ - static function sgn($n) - { - return $n < 0 ? -1 : ($n > 0 ? 1 : 0); - } - - - - /** - * @param numeric $n - * @return bool - */ - static function isInt($n) - { - return is_numeric($n) && round($n) === (float) $n; - } + /** + * @param int $a + * @param int $b + * @return int + */ + static function gcd(int $a, int $b): int + { + if (!self::isInt($a) || !self::isInt($b)) { + throw new \InvalidArgumentException('Integers expected for gcd.'); + } + + $a = (int)abs($a); + $b = (int)abs($b); + + if ($a === 0 && $b === 0) { + throw new \InvalidArgumentException('At least one number must not be a zero.'); + } + + if ($a === 0) return abs($b); + if ($b === 0) return abs($a); + + return abs(self::gcdRecursive($a, $b)); + } + + + /** + * @param int $a + * @param int $b + * @return int + */ + private static function gcdRecursive($a, $b) + { + return ($a % $b) ? self::gcdRecursive($b, $a % $b) : $b; + } + + + /** + * @param numeric $n + * @return int -1, 0, 1 + */ + static function sgn($n) + { + return $n < 0 ? -1 : ($n > 0 ? 1 : 0); + } + + + /** + * @param numeric $n + * @return bool + */ + static function isInt($n): bool + { + return is_numeric($n) && round($n) === (float)$n; + } } diff --git a/composer.json b/composer.json index 8403cdb..eaaba26 100644 --- a/composer.json +++ b/composer.json @@ -1,23 +1,37 @@ - { - "name": "uestla/simplex-calculator", - "type": "library", - "description": "Simple tool for linear programming problems solving", - "keywords": ["simplex", "algorithm", "linear", "programming"], - "homepage": "http://simplex.tode.cz", - "license": "MIT", - "support": { - "issues": "https://github.com/uestla/Simplex-Calculator/issues" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "nette/tester": "@dev" - }, - "autoload": { - "psr-0": { - "Simplex": "/" - } - } + "name": "uestla/simplex-calculator", + "type": "library", + "description": "Simple tool for linear programming problems solving", + "keywords": [ + "simplex", + "algorithm", + "linear", + "programming" + ], + "homepage": "http://simplex.tode.cz", + "license": "MIT", + "support": { + "issues": "https://github.com/uestla/Simplex-Calculator/issues" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "nette/tester": "@dev", + "phpunit/phpunit": "^9.3.3", + "nunomaduro/phpinsights": "^2.3" + }, + "autoload": { + "psr-0": { + "Simplex": "/" + }, + "psr-4": { + "Simplex\\": "Simplex" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + } } diff --git a/example.php b/example.php index eafe440..0830dee 100644 --- a/example.php +++ b/example.php @@ -1,28 +1,29 @@ 1, - 'x2' => 2, + 'x1' => 1, + 'x2' => 2, )); $task = new Simplex\Task($z); $task->addRestriction(new Simplex\Restriction(array( - 'x1' => 3, - 'x2' => 2, + 'x1' => 3, + 'x2' => 2, ), Simplex\Restriction::TYPE_LOE, 24)); $task->addRestriction(new Simplex\Restriction(array( - 'x1' => -2, - 'x2' => -4, + 'x1' => -2, + 'x2' => -4, ), Simplex\Restriction::TYPE_GOE, -32)); $solver = new Simplex\Solver($task); -var_dump($solver); die(); +var_dump($solver); +die(); diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..79ccff9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests + + + + + ./tests + + + diff --git a/tests/FractionTest.php b/tests/FractionTest.php new file mode 100644 index 0000000..9f4c49b --- /dev/null +++ b/tests/FractionTest.php @@ -0,0 +1,89 @@ +a = new Fraction(1, 4); + $this->b = new Fraction(2, 3); + } + + public function test_divaded_by_zero(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Division by zero.'); + + new Fraction(3, 0); + } + + public function fractionEqualizationDataProvider(): array + { + return [ + [0.25, '1/4'], + [-0.25, '-1/4'], + [-0.25, '1/4', -1], + [8, '32', 1 / 4], + [0.14, '7/50'] + ]; + } + + /** + * @dataProvider fractionEqualizationDataProvider + */ + public function test_fraction_equalization(float $nominator, string $expectation, float $denominator = 1): void + { + $f = new Fraction($nominator, $denominator); + $this->assertEquals($expectation, (string)$f); + } + + public function test_fraction_adding(): void + { + $this->assertEquals('11/12', (string)$this->a->add($this->b)); + $this->assertEquals('11/12', (string)$this->b->add($this->a)); + } + + public function test_fraction_multiplying(): void + { + $this->assertEquals('1/6', (string)$this->a->multiply($this->b)); + $this->assertEquals('1/6', (string)$this->b->multiply($this->a)); + } + + public function test_fraction_dividing(): void + { + $this->assertEquals('3/8', (string)$this->a->divide($this->b)); + $this->assertEquals('8/3', (string)$this->b->divide($this->a)); + } + + public function test_fraction_constants_multiply(): void + { + $a = new Fraction(1 / 4); + $this->assertEquals('-1/4', (string)$a->multiply(-1)); + } + + public function test_fraction_sng_abs_value(): void + { + $a = new Fraction(-1 / 4); + $this->assertEquals(-1, $a->sgn()); + $this->assertEquals('1/4', (string)$a->absVal()); + } + + public function test_fraction_comparison(): void + { + $a = new Fraction(2, 3); + $b = new Fraction(3, 2); + + $this->assertTrue($a->isLowerThan($b)); + $this->assertTrue($b->isGreaterThan($a)); + $this->assertFalse($a->isLowerThan($a)); + $this->assertFalse($a->isGreaterThan($a)); + } +} \ No newline at end of file diff --git a/tests/FractionTest.phpt b/tests/FractionTest.phpt deleted file mode 100644 index 7563357..0000000 --- a/tests/FractionTest.phpt +++ /dev/null @@ -1,67 +0,0 @@ -add($b)); -Assert::equal('11/12', (string) $b->add($a)); - - -$a = new Fraction(1, 4); -$b = new Fraction(2, 3); -Assert::equal('1/6', (string) $a->multiply($b)); -Assert::equal('1/6', (string) $b->multiply($a)); - - -$a = new Fraction(1, 4); -$b = new Fraction(2, 3); -Assert::equal('3/8', (string) $a->divide($b)); - - -$a = new Fraction(1/4); -Assert::equal('-1/4', (string) $a->multiply(-1)); - - -$a = new Fraction(-1/4); -Assert::equal(-1, $a->sgn()); -Assert::equal('1/4', (string) $a->absVal()); - - -$a = new Fraction(2, 3); -$b = new Fraction(3, 2); -Assert::true($a->isLowerThan($b)); -Assert::true($b->isGreaterThan($a)); -Assert::false($a->isLowerThan($a)); -Assert::false($a->isGreaterThan($a)); diff --git a/tests/HelpersTest.php b/tests/HelpersTest.php new file mode 100644 index 0000000..fc92f41 --- /dev/null +++ b/tests/HelpersTest.php @@ -0,0 +1,48 @@ +assertTrue(Helpers::isInt('0785')); + $this->assertTrue(Helpers::isInt('-788')); + $this->assertTrue(Helpers::isInt('-788e8')); + $this->assertFalse(Helpers::isInt(NULL)); + $this->assertFalse(Helpers::isInt(TRUE)); + $this->assertFalse(Helpers::isInt(array())); + $this->assertFalse(Helpers::isInt('a')); + } + + public function test_sgn_helper(): void + { + $this->assertEquals(1, Helpers::sgn(7)); + $this->assertEquals(-1, Helpers::sgn(-7)); + $this->assertEquals(0, Helpers::sgn(0)); + } + + public function test_gcd_helper(): void + { + $this->assertEquals(3, Helpers::gcd(6, 27)); + $this->assertEquals(3, Helpers::gcd(-6, 27)); + $this->assertEquals(3, Helpers::gcd(6, -27)); + $this->assertEquals(3, Helpers::gcd(-6, -27)); + + $this->assertEquals(1, Helpers::gcd(1, 24)); + $this->assertEquals(21, Helpers::gcd(0, 21)); + $this->assertEquals(256, Helpers::gcd(1400000000000000256, 100000000000000000)); + } + + public function test_gcd_at_least_one_number_must_not_be_a_zero(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('At least one number must not be a zero.'); + + Helpers::gcd(0, 0); + } +} diff --git a/tests/HelpersTest.phpt b/tests/HelpersTest.phpt deleted file mode 100644 index 869df6d..0000000 --- a/tests/HelpersTest.phpt +++ /dev/null @@ -1,38 +0,0 @@ -expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Variables of both objective functions don't match."); + + $z = new ValueFunc(array( + 'x1' => 5, + ), 13); + + $z2 = new ValueFunc(array( + 'x2' => 7, + ), 43); + + new Table($z, $z2); + } + + + public function test_row_variables_dont_match_objective_function_variables(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Row variables don't match the objective function variables."); + + $t = new Table(new ValueFunc(array( + 'x1' => 4, + ), 42)); + + $t->addRow(new TableRow('x1', array( + 'x2' => 5, + ), 14)); + } +} diff --git a/tests/TableTest.phpt b/tests/TableTest.phpt deleted file mode 100644 index b2c7097..0000000 --- a/tests/TableTest.phpt +++ /dev/null @@ -1,35 +0,0 @@ - 5, - ), 13); - - $z2 = new ValueFunc(array( - 'x2' => 7, - ), 43); - - new Table($z, $z2); - -}, 'InvalidArgumentException', "Variables of both objective functions don't match."); - - -Assert::exception(function () { - $t = new Table(new ValueFunc(array( - 'x1' => 4, - ), 42)); - - $t->addRow(new TableRow('x1', array( - 'x2' => 5, - ), 14)); - -}, 'InvalidArgumentException', "Row variables don't match the objective function variables."); diff --git a/tests/TaskTest.phpt b/tests/TaskTest.phpt deleted file mode 100644 index 4f68e3b..0000000 --- a/tests/TaskTest.phpt +++ /dev/null @@ -1,21 +0,0 @@ - 4, - ))); - - $task->addRestriction(new Restriction(array( - 'x2' => 5, - ), Restriction::TYPE_EQ, 4)); - -}, 'InvalidArgumentException', "Restriction variables don't match the objective function variables."); diff --git a/tests/TasksTest.php b/tests/TasksTest.php new file mode 100644 index 0000000..09c1e58 --- /dev/null +++ b/tests/TasksTest.php @@ -0,0 +1,217 @@ +expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Restriction variables don't match the objective function variables."); + + $task = new Task(new Func(array( + 'x1' => 4, + ))); + + $task->addRestriction(new Restriction(array( + 'x2' => 5, + ), Restriction::TYPE_EQ, 4)); + } + + public function test_first_task_function(): void + { + $z = new Func(array( + 'x1' => 2, + 'x2' => 1, + )); + + $task = new Task($z); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => 1, + + ), Restriction::TYPE_GOE, 2)); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => -1, + + ), Restriction::TYPE_EQ, 0)); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => -2, + + ), Restriction::TYPE_GOE, -4)); + + $solver = new Solver($task); + $steps = $solver->getSteps(); + + $this->assertEquals(3 + 4, count($steps)); + + $exp = array( + 'x1' => 4, + 'x2' => 4, + 'x3' => 6, + 'x4' => 0, + ); + + foreach (end($steps)->getSolution() as $var => $coeff) { + $this->assertTrue($coeff->isEqualTo($exp[$var])); + } + + $this->assertFalse(current($steps)->hasAlternativeSolution()); + $this->assertTrue(current($steps)->getZ()->getB()->isEqualTo(12)); + } + + public function test_second_task_function(): void + { + $z = new Func(array( + 'x1' => 1, + 'x2' => 2, + )); + + $task = new Task($z); + + $task->addRestriction(new Restriction(array( + 'x1' => 3, + 'x2' => 2, + + ), Restriction::TYPE_LOE, 24)); + + $task->addRestriction(new Restriction(array( + 'x1' => -2, + 'x2' => -4, + + ), Restriction::TYPE_GOE, -32)); + + + $solver = new Solver($task); + $steps = $solver->getSteps(); + + $this->assertEquals(3 + 3, count($steps)); + + $exp1 = array( + 'x1' => 4, + 'x2' => 6, + 'x3' => 0, + 'x4' => 0, + ); + + foreach (end($steps)->getSolution() as $var => $coeff) { + $this->assertTrue($coeff->isEqualTo($exp1[$var])); + } + + $this->assertTrue(current($steps)->getZ()->getB()->isEqualTo(16)); + + $exp2 = array( + 'x1' => 0, + 'x2' => 8, + 'x3' => 8, + 'x4' => 0, + ); + + foreach (prev($steps)->getSolution() as $var => $coeff) { + $this->assertTrue($coeff->isEqualTo($exp2[$var])); + } + + $this->assertTrue(current($steps)->hasAlternativeSolution()); + $this->assertTrue(current($steps)->getZ()->getB()->isEqualTo(16)); + } + + public function test_third_function(): void + { + $z = new Func(array( + 'x1' => 150, + 'x2' => 200, + 'x3' => 200, + )); + + $task = new Task($z); + + $task->addRestriction(new Restriction(array( + 'x1' => 2, + 'x2' => 3, + 'x3' => 1, + + ), Restriction::TYPE_LOE, 80)); + + $task->addRestriction(new Restriction(array( + 'x1' => 3, + 'x2' => 6, + 'x3' => 4, + + ), Restriction::TYPE_LOE, 120)); + + $task->addRestriction(new Restriction(array( + 'x1' => 0, + 'x2' => -1, + 'x3' => 4, + + ), Restriction::TYPE_LOE, 0)); + + $solver = new \Simplex\Solver($task); + $steps = $solver->getSteps(); + + $this->assertEquals(3 + 4, count($steps)); + + $exp = array( + 'x1' => 40, + 'x2' => 0, + 'x3' => 0, + 'x4' => 0, + 'x5' => 0, + 'x6' => 0, + ); + + foreach (end($steps)->getSolution() as $var => $coeff) { + $this->assertTrue($coeff->isEqualTo($exp[$var])); + } + + $this->assertFalse(current($steps)->hasAlternativeSolution()); + $this->assertTrue(current($steps)->getZ()->getB()->isEqualTo(6000)); + } + + public function test_fourth_function(): void + { + $z = new Func(array( + 'x1' => -2, + 'x2' => 1, + )); + + $task = new Task($z); + + $task->addRestriction(new Restriction(array( + 'x1' => -1, + 'x2' => 1, + + ), Restriction::TYPE_LOE, -1)); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => 0, + + ), Restriction::TYPE_LOE, 4)); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => 2, + + ), Restriction::TYPE_GOE, 14)); + + $solver = new \Simplex\Solver($task); + $steps = $solver->getSteps(); + + $this->assertEquals(3 + 4, count($steps)); + $this->assertFalse(end($steps)->hasSolution()); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index beb5002..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,6 +0,0 @@ - 2, - 'x2' => 1, -)); - -$task = new Simplex\Task($z); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 1, - 'x2' => 1, - -), Simplex\Restriction::TYPE_GOE, 2)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 1, - 'x2' => -1, - -), Simplex\Restriction::TYPE_EQ, 0)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 1, - 'x2' => -2, - -), Simplex\Restriction::TYPE_GOE, -4)); - -$solver = new Simplex\Solver($task); -$steps = $solver->getSteps(); - -Assert::equal(3 + 4, count($steps)); - -$exp = array( - 'x1' => 4, - 'x2' => 4, - 'x3' => 6, - 'x4' => 0, -); - -foreach (end($steps)->getSolution() as $var => $coeff) { - Assert::true($coeff->isEqualTo($exp[$var])); -} - -Assert::false(current($steps)->hasAlternativeSolution()); -Assert::true(current($steps)->getZ()->getB()->isEqualTo(12)); diff --git a/tests/tasks/Task2.phpt b/tests/tasks/Task2.phpt deleted file mode 100644 index 02ab286..0000000 --- a/tests/tasks/Task2.phpt +++ /dev/null @@ -1,60 +0,0 @@ - 1, - 'x2' => 2, -)); - -$task = new Simplex\Task($z); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 3, - 'x2' => 2, - -), Simplex\Restriction::TYPE_LOE, 24)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => -2, - 'x2' => -4, - -), Simplex\Restriction::TYPE_GOE, -32)); - - -$solver = new Simplex\Solver($task); -$steps = $solver->getSteps(); - -Assert::equal(3 + 3, count($steps)); - -$exp1 = array( - 'x1' => 4, - 'x2' => 6, - 'x3' => 0, - 'x4' => 0, -); - -foreach (end($steps)->getSolution() as $var => $coeff) { - Assert::true($coeff->isEqualTo($exp1[$var])); -} - -Assert::true(current($steps)->getZ()->getB()->isEqualTo(16)); - -$exp2 = array( - 'x1' => 0, - 'x2' => 8, - 'x3' => 8, - 'x4' => 0, -); - -foreach (prev($steps)->getSolution() as $var => $coeff) { - Assert::true($coeff->isEqualTo($exp2[$var])); -} - -Assert::true(current($steps)->hasAlternativeSolution()); -Assert::true(current($steps)->getZ()->getB()->isEqualTo(16)); diff --git a/tests/tasks/Task3.phpt b/tests/tasks/Task3.phpt deleted file mode 100644 index 304d52c..0000000 --- a/tests/tasks/Task3.phpt +++ /dev/null @@ -1,91 +0,0 @@ - 150, - 'x2' => 200, - 'x3' => 200, -)); - -$task = new Simplex\Task($z); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 2, - 'x2' => 3, - 'x3' => 1, - -), Simplex\Restriction::TYPE_LOE, 80)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 3, - 'x2' => 6, - 'x3' => 4, - -), Simplex\Restriction::TYPE_LOE, 120)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 0, - 'x2' => -1, - 'x3' => 4, - -), Simplex\Restriction::TYPE_LOE, 0)); - -$solver = new Simplex\Solver($task); -$steps = $solver->getSteps(); - -Assert::equal(3 + 4, count($steps)); - -$exp = array( - 'x1' => 40, - 'x2' => 0, - 'x3' => 0, - 'x4' => 0, - 'x5' => 0, - 'x6' => 0, -); - -foreach (end($steps)->getSolution() as $var => $coeff) { - Assert::true($coeff->isEqualTo($exp[$var])); -} - -Assert::false(current($steps)->hasAlternativeSolution()); -Assert::true(current($steps)->getZ()->getB()->isEqualTo(6000)); - - - -// === TASK 4 ========================================= - -$z = new Simplex\Func(array( - 'x1' => -2, - 'x2' => 1, -)); - -$task = new Simplex\Task($z); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => -1, - 'x2' => 1, - -), Simplex\Restriction::TYPE_LOE, -1)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 1, - 'x2' => 0, - -), Simplex\Restriction::TYPE_LOE, 4)); - -$task->addRestriction(new Simplex\Restriction(array( - 'x1' => 1, - 'x2' => 2, - -), Simplex\Restriction::TYPE_GOE, 14)); - -$solver = new Simplex\Solver($task); -$steps = $solver->getSteps(); - -Assert::equal(3 + 4, count($steps)); -Assert::false(end($steps)->hasSolution()); From e636657aa4e0fe3a2b9c075ef1f0f5020020cbf1 Mon Sep 17 00:00:00 2001 From: Marcin Lenkowski Date: Sun, 8 May 2022 21:10:34 +0200 Subject: [PATCH 2/4] upgrade QA from php insights --- Simplex/Fraction.php | 427 +++++++++++-------------- Simplex/Func.php | 7 +- Simplex/Helpers.php | 46 +-- Simplex/Restriction.php | 152 ++++----- Simplex/Solver.php | 100 +++--- Simplex/Table.php | 693 ++++++++++++++++++---------------------- Simplex/TableRow.php | 84 ++--- Simplex/Task.php | 318 +++++++++--------- Simplex/ValueFunc.php | 67 ++-- Simplex/VariableSet.php | 142 ++++---- Simplex/simplex.php | 2 + example.php | 18 +- 12 files changed, 905 insertions(+), 1151 deletions(-) diff --git a/Simplex/Fraction.php b/Simplex/Fraction.php index fe7fb14..50582bf 100644 --- a/Simplex/Fraction.php +++ b/Simplex/Fraction.php @@ -1,265 +1,194 @@ n = (int) $n; - - } else { - $nf = self::fromDecimal($n); - $this->n = $nf->getNumerator(); - $this->d = $nf->getDenominator(); - } - - if (Helpers::isInt($d)) { - $this->d = (int) ($d * ($this->d === NULL ? 1 : $this->d)); - - } else { - $df = self::fromDecimal($d); - $this->n = $this->n * $df->getDenominator(); - $this->d = (int) ($df->getNumerator() * ($this->d === NULL ? 1 : $this->d)); - } - - $this->canonicalize(); - } - - - - /** - * @param Fraction|numeric $a - * @return Fraction - */ - static function create($a) - { - if ($a instanceof self) { - return $a; - } - - return new self($a); - } - - - - /** @return int */ - function getNumerator() - { - return $this->n; - } - - - - /** @return int */ - function getDenominator() - { - return $this->d; - } - - - - /** @return Fraction */ - function canonicalize() - { - if ($this->d === 0) { - throw new \Exception('Division by zero.'); - } - - if ($this->d < 0) { - $this->n = -$this->n; - $this->d = -$this->d; - } - - $gcd = Helpers::gcd($this->n, $this->d); - $this->n /= $gcd; - $this->d /= $gcd; - return $this; - } - - - - /** - * a/b + c/d = (ad + bc)/bd - * - * @param Fraction|numeric $a - * @return Fraction - */ - function add($a) - { - $a = self::create($a); - return new self($this->n * $a->getDenominator() + $this->d * $a->getNumerator(), $this->d * $a->getDenominator()); - } - - - - /** - * a/b - c/d = (ad - bc)/bd - * - * @param Fraction|numeric $a - * @return Fraction - */ - function subtract($a) - { - return $this->add(self::create($a)->multiply(-1)); - } - - - - /** - * (a/b)(c/d) = (ac/bd) - * - * @param Fraction|numeric $a - * @return Fraction - */ - function multiply($a) - { - $a = self::create($a); - return new self($this->n * $a->getNumerator(), $this->d * $a->getDenominator()); - } - - - - /** - * (a/b)/(c/d) = ad/bc - * - * @param Fraction|numeric $a - * @return Fraction - */ - function divide($a) - { - $a = self::create($a); - return $this->multiply(new self($a->getDenominator(), $a->getNumerator())); - } - - - - /** @return int -1, 0, 1 */ - function sgn() - { - return Helpers::sgn($this->n); - } - - - - /** @return Fraction */ - function absVal() - { - return new self($this->sgn() * $this->n, $this->d); - } - - - - /** - * 2/3 vs 3/2 => 4/6 vs 9/6 => 4 < 9 - * - * @param Fraction|numeric $a - * @return int -1, 0, 1 - */ - function compare($a) - { - $a = self::create($a); - return Helpers::sgn($this->n * $a->getDenominator() - $a->getNumerator() * $this->d); - } - - - - /** - * @param Fraction|numeric $a - * @return bool - */ - function isEqualTo($a) - { - return $this->compare($a) === 0; - } - - - - /** - * @param Fraction|numeric $a - * @return bool - */ - function isLowerThan($a) - { - return $this->compare($a) === -1; - } - - - - /** - * @param Fraction|numeric $a - * @return bool - */ - function isGreaterThan($a) - { - return $this->compare($a) === 1; - } - - - - /** @return string */ - function toString() - { - return $this->n . ($this->d !== 1 ? '/' . $this->d : ''); - } - - - - /** @return float */ - function toFloat() - { - return $this->n / $this->d; - } - - - - /** @return string */ - function __toString() - { - return $this->toString(); - } - - - - /** - * 0.25 => 25/100 - * - * @param float $n - * @return Fraction - */ - private static function fromDecimal($n) - { - if (Helpers::isInt($n)) { - return new self($n); - } - - $decpart = (float) ($n - (int) $n); - $mlp = pow(10, strlen($decpart) - 2 - ($n < 0 ? 1 : 0)); - return new self((int) ($n * $mlp), $mlp); - } - + private ?int $n = null; + + private ?int $d = null; + + public function __construct(float $n, float $d = 1) + { + if (Helpers::isInt($n)) { + $this->n = (int)$n; + } else { + $nf = self::fromDecimal($n); + $this->n = $nf->getNumerator(); + $this->d = $nf->getDenominator(); + } + + if (Helpers::isInt($d)) { + $this->d = (int)($d * ($this->d === null ? 1 : $this->d)); + } else { + $df = self::fromDecimal($d); + $this->n *= $df->getDenominator(); + $this->d = (int)($df->getNumerator() * ($this->d === null ? 1 : $this->d)); + } + + $this->canonicalize(); + } + + public function __toString(): string + { + return $this->toString(); + } + + /** + * @param Fraction|int|float $a + * @return Fraction + */ + public static function create($a): Fraction + { + if ($a instanceof self) { + return $a; + } + + return new self($a); + } + + public function getNumerator(): int + { + return $this->n; + } + + public function getDenominator(): int + { + return $this->d; + } + + public function canonicalize(): Fraction + { + if ($this->d === 0) { + throw new \Exception('Division by zero.'); + } + + if ($this->d < 0) { + $this->n = -$this->n; + $this->d = -$this->d; + } + + $gcd = Helpers::gcd($this->n, $this->d); + $this->n /= $gcd; + $this->d /= $gcd; + return $this; + } + + /** + * a/b + c/d = (ad + bc)/bd + * + * @param Fraction|int|float $a + * @return Fraction + */ + public function add($a): Fraction + { + $a = self::create($a); + return new self($this->n * $a->getDenominator() + $this->d * $a->getNumerator(), $this->d * $a->getDenominator()); + } + + /** + * a/b - c/d = (ad - bc)/bd + * @param Fraction|int|float $a + * @return Fraction + */ + public function subtract($a): Fraction + { + return $this->add(self::create($a)->multiply(-1)); + } + + /** + * (a/b)(c/d) = (ac/bd) + * @param Fraction|int|float $a + * @return Fraction + */ + public function multiply($a): Fraction + { + $a = self::create($a); + return new self($this->n * $a->getNumerator(), $this->d * $a->getDenominator()); + } + + /** + * (a/b)/(c/d) = ad/bc + * @param Fraction|int|float $a + * @return Fraction + */ + public function divide($a): Fraction + { + $a = self::create($a); + return $this->multiply(new self($a->getDenominator(), $a->getNumerator())); + } + + /** @return int -1, 0, 1 */ + public function sgn(): int + { + return Helpers::sgn($this->n); + } + + public function absVal(): Fraction + { + return new self($this->sgn() * $this->n, $this->d); + } + + /** + * 2/3 vs 3/2 => 4/6 vs 9/6 => 4 < 9 + * + * @param Fraction|int|float $a + * @return int -1, 0, 1 + */ + public function compare($a): int + { + $a = self::create($a); + return Helpers::sgn($this->n * $a->getDenominator() - $a->getNumerator() * $this->d); + } + + public function isEqualTo($a): bool + { + return $this->compare($a) === 0; + } + + public function isLowerThan($a): bool + { + return $this->compare($a) === -1; + } + + public function isGreaterThan($a): bool + { + return $this->compare($a) === 1; + } + + public function toString(): string + { + return $this->n . ($this->d !== 1 ? '/' . $this->d : ''); + } + + public function toFloat(): float + { + return $this->n / $this->d; + } + + /** + * 0.25 => 25/100 + */ + private static function fromDecimal(float $n): Fraction + { + if (Helpers::isInt($n)) { + return new self($n); + } + + $decpart = (float)($n - (int)$n); + $mlp = pow(10, strlen((string)$decpart) - 2 - ($n < 0 ? 1 : 0)); + return new self((int)($n * $mlp), $mlp); + } } diff --git a/Simplex/Func.php b/Simplex/Func.php index 2368394..c256125 100644 --- a/Simplex/Func.php +++ b/Simplex/Func.php @@ -1,16 +1,19 @@ 0 ? 1 : 0); } - - /** - * @param numeric $n - * @return bool - */ - static function isInt($n): bool + public static function isInt($n): bool { return is_numeric($n) && round($n) === (float)$n; } + private static function gcdRecursive(int $a, int $b): int + { + return $a % $b ? self::gcdRecursive($b, $a % $b) : $b; + } } diff --git a/Simplex/Restriction.php b/Simplex/Restriction.php index 38b85ce..0d5d6c9 100644 --- a/Simplex/Restriction.php +++ b/Simplex/Restriction.php @@ -1,105 +1,79 @@ type = (int) $type; - $this->limit = Fraction::create($limit); - } - - - - /** @return int */ - function getType() - { - return $this->type; - } - - - - /** @return Fraction */ - function getLimit() - { - return $this->limit; - } - - - - /** @return Restriction */ - function fixRightSide() - { - if ($this->limit->isLowerThan(0)) { - $set = array(); - foreach ($this->set as $var => $coeff) { - $set[$var] = $coeff->multiply(-1); - } - - $type = $this->type === self::TYPE_EQ ? $this->type - : ($this->type === self::TYPE_GOE ? self::TYPE_LOE : self::TYPE_GOE); - - $this->limit = $this->limit->multiply(-1); - - } else { - $set = $this->set; - $type = $this->type; - } - - $this->set = $set; - $this->type = $type; - - return $this; - } - - - - /** @return string */ - function getTypeSign() - { - return $this->type === self::TYPE_EQ ? '=' - : ($this->type === self::TYPE_LOE ? "\xe2\x89\xa4" : "\xe2\x89\xa5"); - } - - - - /** Deep copy */ - function __clone() - { - $this->limit = clone $this->limit; - } - -} + public const TYPE_EQ = 1; + public const TYPE_LOE = 2; + public const TYPE_GOE = 4; + private int $type; + + private Fraction $limit; + + public function __construct(array $set, int $type, $limit) + { + parent::__construct($set); + + $this->type = (int)$type; + $this->limit = Fraction::create($limit); + } + + /** Deep copy */ + public function __clone() + { + $this->limit = clone $this->limit; + } + + public function getType(): int + { + return $this->type; + } + + public function getLimit(): Fraction + { + return $this->limit; + } + + public function fixRightSide(): Restriction + { + if ($this->limit->isLowerThan(0)) { + $set = []; + foreach ($this->set as $var => $coeff) { + $set[$var] = $coeff->multiply(-1); + } + + $type = $this->type === self::TYPE_EQ ? $this->type + : ($this->type === self::TYPE_GOE ? self::TYPE_LOE : self::TYPE_GOE); + + $this->limit = $this->limit->multiply(-1); + } else { + $set = $this->set; + $type = $this->type; + } + + $this->set = $set; + $this->type = $type; + + return $this; + } + + public function getTypeSign(): string + { + return $this->type === self::TYPE_EQ ? '=' + : ($this->type === self::TYPE_LOE ? "\xe2\x89\xa4" : "\xe2\x89\xa5"); + } +} \ No newline at end of file diff --git a/Simplex/Solver.php b/Simplex/Solver.php index 511e029..79db422 100644 --- a/Simplex/Solver.php +++ b/Simplex/Solver.php @@ -1,72 +1,60 @@ maxSteps = (int) $maxSteps; - $this->steps[] = $task; - - $this->solve(); - } - - - - /** @return array */ - function getSteps() - { - return $this->steps; - } - - - - /** @return void */ - private function solve() - { - $t = clone reset($this->steps); - $this->steps[] = $t->fixRightSides(); - - $t = clone $t; - $this->steps[] = $t->fixNonEquations(); - - $this->steps[] = $tbl = $t->toTable(); - while (!$tbl->isSolved()) { - $tbl = clone $tbl; - $this->steps[] = $tbl->nextStep(); - - if (count($this->steps) > $this->maxSteps) { - break ; - } - } - - if ($tbl->hasAlternativeSolution()) { - $this->steps[] = $tbl->getAlternativeSolution(); - } - } - + /** @var array */ + private array $steps = []; + + private int $maxSteps; + + public function __construct(Task $task, int $maxSteps = 16) + { + $this->maxSteps = (int) $maxSteps; + $this->steps[] = $task; + + $this->solve(); + } + + /** @return array */ + public function getSteps(): array + { + return $this->steps; + } + + private function solve(): void + { + $t = clone reset($this->steps); + $this->steps[] = $t->fixRightSides(); + + $t = clone $t; + $this->steps[] = $t->fixNonEquations(); + + $this->steps[] = $tbl = $t->toTable(); + while (! $tbl->isSolved()) { + $tbl = clone $tbl; + $this->steps[] = $tbl->nextStep(); + + if (count($this->steps) > $this->maxSteps) { + break; + } + } + + if ($tbl->hasAlternativeSolution()) { + $this->steps[] = $tbl->getAlternativeSolution(); + } + } } diff --git a/Simplex/Table.php b/Simplex/Table.php index 8fd3ccf..c990a3b 100644 --- a/Simplex/Table.php +++ b/Simplex/Table.php @@ -1,390 +1,335 @@ getVariableList() !== $z->getVariableList()) { - throw new \InvalidArgumentException("Variables of both objective functions don't match."); - } - - $this->z = new TableRow('z', $z->getSet(), 0); - $this->z2 = $z2 ? new TableRow('z\'', $z2->getSet(), $z2->getValue()) : NULL; - } - - - - /** @return string[] */ - function getVariableList() - { - return $this->z->getVariableList(); - } - - - - /** @return TableRow */ - function getZ() - { - return $this->z; - } - - - - /** @return TableRow|NULL */ - function getZ2() - { - return $this->z2; - } - - - - /** @return bool */ - function hasZ2() - { - return $this->z2 !== NULL; - } - - - - /** - * @param TableRow $row - * @return Table - */ - function addRow(TableRow $row) - { - if ($row->getVariableList() !== $this->z->getVariableList() - || ($this->z2 !== NULL && $row->getVariableList() !== $this->z2->getVariableList())) { - throw new \InvalidArgumentException("Row variables don't match the objective function variables."); - } - - $this->rows[] = $row; - $this->basis[] = $row->getVar(); - return $this; - } - - - - /** @return TableRow[] */ - function getRows() - { - return $this->rows; - } - - - - /** @return bool */ - function hasHelperInBasis() - { - foreach ($this->rows as $row) { - if (strncmp($row->getVar(), 'y', 1) === 0) { - return TRUE; - } - } - - return FALSE; - } - - - - /** @return bool */ - function isSolved() - { - if ($this->z2 !== NULL) { - foreach ($this->z2->getSet() as $coeff) { - if ($coeff->isLowerThan(0)) { - return FALSE; - } - } - - if (!$this->z2->getB()->isEqualTo(0) || $this->hasHelperInBasis()) { - $this->solution = FALSE; - return TRUE; - } - } - - $keyval = $this->z->getMin(); - $keycol = array_search($keyval, $this->z->getSet(), TRUE); - - if ($keyval->isLowerThan(0)) { - foreach ($this->rows as $row) { - $set = $row->getSet(); - if ($set[$keycol]->isGreaterThan(0)) { - return FALSE; - } - } - - $this->solution = FALSE; - } - - return TRUE; - } - - - - /** @return array|bool|NULL */ - function getSolution() - { - return $this->solution; - } - - - - /** @return bool */ - function hasSolution() - { - return is_array($this->solution); - } - - - - /** @return bool */ - function hasAlternativeSolution() - { - return $this->getAlternativeSolution() !== FALSE; - } - - - - /** @return Table|bool */ - function getAlternativeSolution() - { - if ($this->alternative === NULL) { - if (!$this->hasSolution()) { - $this->alternative = FALSE; - - } else { - foreach ($this->solution as $var => $value) { - if ($value->isEqualTo(0) && !in_array($var, $this->basis, TRUE)) { - $clone = clone $this; - $solution = $clone->nextStep()->getSolution(); - - foreach ($clone->nextStep()->getSolution() as $v => $val) { - if (!$val->isEqualTo($this->solution[$v])) { - $this->alternative = $clone; - break 2; - } - } - - $this->alternative = FALSE; - break; - } - } - - $this->alternative === NULL && ($this->alternative = FALSE); - } - } - - return $this->alternative; - } - - - - /** @return string|NULL */ - function getKeyColumn() - { - $zrow = $this->hasHelperInBasis() ? 'z2' : 'z'; - - $keycol = $keyval = NULL; - foreach ($this->$zrow->getSet() as $var => $coeff) { - if ($keyval === NULL || $coeff->isLowerThan($keyval)) { - $keyval = $coeff; - $keycol = $var; - } - } - - return $keycol; - } - - - - /** @return TableRow|NULL */ - function getKeyRow() - { - $mint = $keyrow = NULL; - $keycol = $this->getKeyColumn(); - - if ($keycol === NULL) { - return NULL; - } - - foreach ($this->rows as $row) { - $set = $row->getSet(); - if ($set[$keycol]->isGreaterThan(0) - && ($mint === NULL || $row->getB()->divide($set[$keycol])->isLowerThan($mint))) { - $mint = $row->getB()->divide($set[$keycol]); - $keyrow = $row; - } - } - - return $keyrow; - } - - - - /** @return Table */ - function nextStep() - { - if ($this->z2 !== NULL && !$this->hasHelperInBasis()) { - $this->removeHelpers(); - } - - $newrows = array(); - $keycol = $this->getKeyColumn(); - $keyrow = $this->getKeyRow(); - - if ($keycol === NULL || $keyrow === NULL) { - return $this; - } - - foreach ($this->rows as $row) { - $rowset = array(); - - if ($row->getVar() === $keyrow->getVar()) { - $var = $keycol; - $b = $row->getB()->divide($keyrow->get($keycol)); - - foreach ($row->getSet() as $v => $c) { - $rowset[$v] = $c->divide($keyrow->get($keycol)); - } - - } else { - $var = $row->getVar(); - $set = $row->getSet(); - $dvd = $set[$keycol]->multiply(-1)->divide($keyrow->get($keycol)); - $b = $dvd->multiply($keyrow->getB())->add($row->getB()); - - foreach ($row->getSet() as $v => $c) { - $rowset[$v] = $dvd->multiply($keyrow->get($v))->add($c); - } - } - - $newrows[$var] = array($rowset, $b); - } - - $this->rows = array(); - foreach ($newrows as $var => $meta) { - $this->addRow(new TableRow($var, $meta[0], $meta[1])); - } - - foreach (array('z', 'z2') as $zvar) { - if ($this->$zvar === NULL) continue; - - $zcoeffs = array(); - $zdvd = $this->$zvar->get($keycol)->multiply(-1)->divide($keyrow->get($keycol)); - foreach ($this->$zvar->getSet() as $v => $c) { - $zcoeffs[$v] = $zdvd->multiply($keyrow->get($v))->add($c); - } - - $this->$zvar = new TableRow($zvar, $zcoeffs, $zdvd->multiply($keyrow->getB())->add($this->$zvar->getB())); - } - - // find solution (if any) - if ($this->isSolved()) { - if ($this->solution !== FALSE) { - $this->solution = array(); - - foreach ($this->z->getVariableList() as $var) { - if (strncmp($var, 'y', 1) === 0) continue; - - foreach ($this->rows as $row) { - if ($row->getVar() === $var) { - $this->solution[$var] = $row->getB(); - continue 2; - } - } - - $this->solution[$var] = Fraction::create(0); - } - - ksort($this->solution); - } - } - - return $this; - } - - - - /** @return void */ - private function removeHelpers() - { - $this->z2 = NULL; - - $newrows = array(); - foreach ($this->rows as $row) { - $newrow = array(); - foreach ($row->getSet() as $v => $c) { - if ($v[0] !== 'y') { - $newrow[$v] = $c; - } - } - - $newrows[] = new TableRow($row->getVar(), $newrow, $row->getB()); - } - - $this->rows = $newrows; - - $newz = array(); - foreach ($this->z->getSet() as $var => $coeff) { - if ($var[0] !== 'y') { - $newz[$var] = $coeff; - } - } - - $this->z = new TableRow($this->z->getVar(), $newz, $this->z->getB()); - } - - - - /** Deep copy */ - function __clone() - { - foreach ($this->rows as $key => $row) { - $this->rows[$key] = clone $row; - } - - $this->z = clone $this->z; - - if (is_object($this->z2)) { - $this->z2 = clone $this->z2; - } - } - + /** @var array */ + private array $rows; + + private TableRow $z; + + /** @var TableRow|NULL */ + private ?TableRow $z2 = null; + + /** @var array */ + private array $basis = []; + + // TODO: make it nullable, not boolean + private $solution = null; + + private $alternative = null; + + public function __construct(ValueFunc $z, ?ValueFunc $z2 = null) + { + if ($z2 !== null && $z2->getVariableList() !== $z->getVariableList()) { + throw new \InvalidArgumentException("Variables of both objective functions don't match."); + } + + $this->z = new TableRow('z', $z->getSet(), 0); + $this->z2 = $z2 ? new TableRow('z\'', $z2->getSet(), $z2->getValue()) : null; + } + + /** Deep copy */ + public function __clone() + { + foreach ($this->rows as $key => $row) { + $this->rows[$key] = clone $row; + } + + $this->z = clone $this->z; + + if (is_object($this->z2)) { + $this->z2 = clone $this->z2; + } + } + + /** @return array */ + public function getVariableList(): array + { + return $this->z->getVariableList(); + } + + public function getZ(): TableRow + { + return $this->z; + } + + /** @return TableRow|NULL */ + public function getZ2(): ?TableRow + { + return $this->z2; + } + + public function hasZ2(): bool + { + return $this->z2 !== null; + } + + public function addRow(TableRow $row): Table + { + if ($row->getVariableList() !== $this->z->getVariableList() + || ($this->z2 !== null && $row->getVariableList() !== $this->z2->getVariableList())) { + throw new \InvalidArgumentException("Row variables don't match the objective function variables."); + } + + $this->rows[] = $row; + $this->basis[] = $row->getVar(); + return $this; + } + + /** @return array */ + public function getRows(): array + { + return $this->rows; + } + + public function hasHelperInBasis(): bool + { + foreach ($this->rows as $row) { + if (strncmp($row->getVar(), 'y', 1) === 0) { + return true; + } + } + + return false; + } + + public function isSolved(): bool + { + if ($this->z2 !== null) { + foreach ($this->z2->getSet() as $coeff) { + if ($coeff->isLowerThan(0)) { + return false; + } + } + + if (!$this->z2->getB()->isEqualTo(0) || $this->hasHelperInBasis()) { + $this->solution = false; + return true; + } + } + + $keyval = $this->z->getMin(); + $keycol = array_search($keyval, $this->z->getSet(), true); + + if ($keyval->isLowerThan(0)) { + foreach ($this->rows as $row) { + $set = $row->getSet(); + if ($set[$keycol]->isGreaterThan(0)) { + return false; + } + } + + $this->solution = false; + } + + return true; + } + + /** @return array|bool|NULL */ + public function getSolution() + { + return $this->solution; + } + + public function hasSolution(): bool + { + return is_array($this->solution); + } + + public function hasAlternativeSolution(): bool + { + return $this->getAlternativeSolution() !== false; + } + + public function getAlternativeSolution() + { + if ($this->alternative === null) { + if (!$this->hasSolution()) { + $this->alternative = false; + } else { + foreach ($this->solution as $var => $value) { + if ($value->isEqualTo(0) && !in_array($var, $this->basis, true)) { + $clone = clone $this; + $solution = $clone->nextStep()->getSolution(); + + foreach ($clone->nextStep()->getSolution() as $v => $val) { + if (!$val->isEqualTo($this->solution[$v])) { + $this->alternative = $clone; + break 2; + } + } + + $this->alternative = false; + break; + } + } + + $this->alternative === null && ($this->alternative = false); + } + } + + return $this->alternative; + } + + /** @return string|NULL */ + public function getKeyColumn(): ?string + { + $zrow = $this->hasHelperInBasis() ? 'z2' : 'z'; + + $keycol = $keyval = null; + foreach ($this->$zrow->getSet() as $var => $coeff) { + if ($keyval === null || $coeff->isLowerThan($keyval)) { + $keyval = $coeff; + $keycol = $var; + } + } + + return $keycol; + } + + /** @return TableRow|NULL */ + public function getKeyRow(): ?TableRow + { + $mint = $keyrow = null; + $keycol = $this->getKeyColumn(); + + if ($keycol === null) { + return null; + } + + foreach ($this->rows as $row) { + $set = $row->getSet(); + if ($set[$keycol]->isGreaterThan(0) + && ($mint === null || $row->getB()->divide($set[$keycol])->isLowerThan($mint))) { + $mint = $row->getB()->divide($set[$keycol]); + $keyrow = $row; + } + } + + return $keyrow; + } + + public function nextStep(): Table + { + if ($this->z2 !== null && !$this->hasHelperInBasis()) { + $this->removeHelpers(); + } + + $newrows = []; + $keycol = $this->getKeyColumn(); + $keyrow = $this->getKeyRow(); + + if ($keycol === null || $keyrow === null) { + return $this; + } + + foreach ($this->rows as $row) { + $rowset = []; + + if ($row->getVar() === $keyrow->getVar()) { + $var = $keycol; + $b = $row->getB()->divide($keyrow->get($keycol)); + + foreach ($row->getSet() as $v => $c) { + $rowset[$v] = $c->divide($keyrow->get($keycol)); + } + } else { + $var = $row->getVar(); + $set = $row->getSet(); + $dvd = $set[$keycol]->multiply(-1)->divide($keyrow->get($keycol)); + $b = $dvd->multiply($keyrow->getB())->add($row->getB()); + + foreach ($row->getSet() as $v => $c) { + $rowset[$v] = $dvd->multiply($keyrow->get($v))->add($c); + } + } + + $newrows[$var] = [$rowset, $b]; + } + + $this->rows = []; + foreach ($newrows as $var => $meta) { + $this->addRow(new TableRow($var, $meta[0], $meta[1])); + } + + foreach (['z', 'z2'] as $zvar) { + if ($this->$zvar === null) { + continue; + } + + $zcoeffs = []; + $zdvd = $this->$zvar->get($keycol)->multiply(-1)->divide($keyrow->get($keycol)); + foreach ($this->$zvar->getSet() as $v => $c) { + $zcoeffs[$v] = $zdvd->multiply($keyrow->get($v))->add($c); + } + + $this->$zvar = new TableRow($zvar, $zcoeffs, $zdvd->multiply($keyrow->getB())->add($this->$zvar->getB())); + } + + // find solution (if any) + if ($this->isSolved() && $this->solution !== false) { + $this->solution = []; + + foreach ($this->z->getVariableList() as $var) { + if (strncmp($var, 'y', 1) === 0) { + continue; + } + + foreach ($this->rows as $row) { + if ($row->getVar() === $var) { + $this->solution[$var] = $row->getB(); + continue 2; + } + } + + $this->solution[$var] = Fraction::create(0); + } + + ksort($this->solution); + } + + return $this; + } + + private function removeHelpers(): void + { + $this->z2 = null; + + $newrows = []; + foreach ($this->rows as $row) { + $newrow = []; + foreach ($row->getSet() as $v => $c) { + if ($v[0] !== 'y') { + $newrow[$v] = $c; + } + } + + $newrows[] = new TableRow($row->getVar(), $newrow, $row->getB()); + } + + $this->rows = $newrows; + + $newz = []; + foreach ($this->z->getSet() as $var => $coeff) { + if ($var[0] !== 'y') { + $newz[$var] = $coeff; + } + } + + $this->z = new TableRow($this->z->getVar(), $newz, $this->z->getB()); + } } diff --git a/Simplex/TableRow.php b/Simplex/TableRow.php index 95523bc..2eee4a1 100644 --- a/Simplex/TableRow.php +++ b/Simplex/TableRow.php @@ -1,65 +1,51 @@ var = (string) $var; - $this->b = Fraction::create($b); - } - - - - /** @return string */ - function getVar() - { - return $this->var; - } - - - - /** @return Fraction */ - function getB() - { - return $this->b; - } - - - - /** Deep copy */ - function __clone() - { - parent::__clone(); - - $this->b = clone $this->b; - } - + private string $var; + + private Fraction $b; + + /** + * @param array $set + */ + public function __construct(string $var, array $set, $b) + { + parent::__construct($set); + + $this->var = (string)$var; + $this->b = Fraction::create($b); + } + + /** Deep copy */ + public function __clone() + { + parent::__clone(); + + $this->b = clone $this->b; + } + + public function getVar(): string + { + return $this->var; + } + + public function getB(): Fraction + { + return $this->b; + } } diff --git a/Simplex/Task.php b/Simplex/Task.php index d0e2017..d9394f1 100644 --- a/Simplex/Task.php +++ b/Simplex/Task.php @@ -1,189 +1,161 @@ function = $function; - } - - - - /** @return Func */ - function getFunction() - { - return $this->function; - } - - - - /** - * @param Restriction $r - * @return Task - */ - function addRestriction(Restriction $r) - { - if ($r->getVariableList() !== $this->function->getVariableList()) { - throw new \InvalidArgumentException("Restriction variables don't match the objective function variables."); - } - - $this->restrictions[] = $r; - return $this; - } - - - - /** @return Restriction[] */ - function getRestrictions() - { - return $this->restrictions; - } - - - - /** @return Task */ - function fixRightSides() - { - $restrictions = $this->restrictions; - $this->restrictions = array(); - - foreach ($restrictions as $r) { - $this->addRestriction($r->fixRightSide()); - } - - return $this; - } - - - - /** @return Task */ - function fixNonEquations() - { - $newfunc = $this->function->getSet(); - - $newrestr = array(); - foreach ($this->restrictions as $restriction) { - $newrestr[] = array($restriction->getSet(), $restriction->getLimit()); - } - - $add = count($newfunc); - $hlp = 0; - - foreach ($this->restrictions as $idx => $restriction) { - $added = array(); - - if ($restriction->getType() === Restriction::TYPE_EQ) { - $hlpname = 'y' . ++$hlp; - $added[$hlpname] = 1; - $this->basismap[$hlpname] = $idx; - - } elseif ($restriction->getType() === Restriction::TYPE_LOE) { - $addname = 'x' . ++$add; - $added[$addname] = 1; - $this->basismap[$addname] = $idx; - - } else { - $hlpname = 'y' . ++$hlp; - $added['x' . ++$add] = -1; - $added[$hlpname] = 1; - $this->basismap[$hlpname] = $idx; - } - - foreach ($added as $newvar => $coeff) { - $newfunc[$newvar] = 0; - - foreach ($this->restrictions as $i => $r) { - $newrestr[$i][0][$newvar] = $idx === $i ? $coeff : 0; - } - } - } - - ksort($this->basismap); - $this->function = new Func($newfunc); - - $this->restrictions = array(); - foreach ($newrestr as $r) { - $this->addRestriction(new Restriction($r[0], Restriction::TYPE_EQ, $r[1])); - } - - return $this; - } - - - - /** @return Table */ - function toTable() - { - $zcoeffs = array(); - foreach ($this->function->getSet() as $var => $coeff) { - $zcoeffs[$var] = $coeff->multiply(-1); - } - - $z = new ValueFunc($zcoeffs, 0); - - $z2b = Fraction::create(0); - $z2coeffs = array(); - - foreach ($this->restrictions as $idx => $r) { - foreach ($r->getSet() as $var => $coeff) { - if (strncmp($var, 'y', 1) === 0 && $coeff->isEqualTo(1)) { - foreach ($r->getSet() as $v => $c) { - !isset($z2coeffs[$v]) && $z2coeffs[$v] = Fraction::create(0); - strncmp($v, 'y', 1) !== 0 && ($z2coeffs[$v] = $z2coeffs[$v]->subtract($c)); - } - - $z2b = $z2b->subtract($r->getLimit()); - } - } - } - - $z2 = count($z2coeffs) ? new ValueFunc($z2coeffs, $z2b) : NULL; - - $table = new Table($z, $z2); - foreach ($this->basismap as $var => $idx) { - $table->addRow( - new TableRow($var, $this->restrictions[$idx]->getSet(), $this->restrictions[$idx]->getLimit()) - ); - } - - return $table; - } - - - - /** Deep copy */ - function __clone() - { - $this->function = clone $this->function; - - foreach ($this->restrictions as $key => $restriction) { - $this->restrictions[$key] = clone $restriction; - } - } - + private Func $function; + + /** @var array */ + private array $restrictions; + + /** @var array */ + private array $basismap = []; + + public function __construct(Func $function) + { + $this->function = $function; + } + + /** Deep copy */ + public function __clone() + { + $this->function = clone $this->function; + + foreach ($this->restrictions as $key => $restriction) { + $this->restrictions[$key] = clone $restriction; + } + } + + public function getFunction(): Func + { + return $this->function; + } + + public function addRestriction(Restriction $r): Task + { + if ($r->getVariableList() !== $this->function->getVariableList()) { + throw new \InvalidArgumentException("Restriction variables don't match the objective function variables."); + } + + $this->restrictions[] = $r; + return $this; + } + + /** @return array */ + public function getRestrictions(): array + { + return $this->restrictions; + } + + public function fixRightSides(): Task + { + $restrictions = $this->restrictions; + $this->restrictions = []; + + foreach ($restrictions as $r) { + $this->addRestriction($r->fixRightSide()); + } + + return $this; + } + + public function fixNonEquations(): Task + { + $newfunc = $this->function->getSet(); + + $newrestr = []; + foreach ($this->restrictions as $restriction) { + $newrestr[] = [$restriction->getSet(), $restriction->getLimit()]; + } + + $add = count($newfunc); + $hlp = 0; + + foreach ($this->restrictions as $idx => $restriction) { + $added = []; + + if ($restriction->getType() === Restriction::TYPE_EQ) { + $hlpname = 'y' . ++$hlp; + $added[$hlpname] = 1; + $this->basismap[$hlpname] = $idx; + } elseif ($restriction->getType() === Restriction::TYPE_LOE) { + $addname = 'x' . ++$add; + $added[$addname] = 1; + $this->basismap[$addname] = $idx; + } else { + $hlpname = 'y' . ++$hlp; + $added['x' . ++$add] = -1; + $added[$hlpname] = 1; + $this->basismap[$hlpname] = $idx; + } + + foreach ($added as $newvar => $coeff) { + $newfunc[$newvar] = 0; + + foreach ($this->restrictions as $i => $r) { + $newrestr[$i][0][$newvar] = $idx === $i ? $coeff : 0; + } + } + } + + ksort($this->basismap); + $this->function = new Func($newfunc); + + $this->restrictions = []; + foreach ($newrestr as $r) { + $this->addRestriction(new Restriction($r[0], Restriction::TYPE_EQ, $r[1])); + } + + return $this; + } + + public function toTable(): Table + { + $zcoeffs = []; + foreach ($this->function->getSet() as $var => $coeff) { + $zcoeffs[$var] = $coeff->multiply(-1); + } + + $z = new ValueFunc($zcoeffs, 0); + + $z2b = Fraction::create(0); + $z2coeffs = []; + + foreach ($this->restrictions as $idx => $r) { + foreach ($r->getSet() as $var => $coeff) { + if (strncmp($var, 'y', 1) === 0 && $coeff->isEqualTo(1)) { + foreach ($r->getSet() as $v => $c) { + ! isset($z2coeffs[$v]) && $z2coeffs[$v] = Fraction::create(0); + strncmp($v, 'y', 1) !== 0 && ($z2coeffs[$v] = $z2coeffs[$v]->subtract($c)); + } + + $z2b = $z2b->subtract($r->getLimit()); + } + } + } + + $z2 = count($z2coeffs) ? new ValueFunc($z2coeffs, $z2b) : null; + + $table = new Table($z, $z2); + foreach ($this->basismap as $var => $idx) { + $table->addRow( + new TableRow($var, $this->restrictions[$idx]->getSet(), $this->restrictions[$idx]->getLimit()) + ); + } + + return $table; + } } diff --git a/Simplex/ValueFunc.php b/Simplex/ValueFunc.php index afce9ec..5eadd7e 100644 --- a/Simplex/ValueFunc.php +++ b/Simplex/ValueFunc.php @@ -1,54 +1,45 @@ value = Fraction::create($value); - } - - - - /** @return Fraction */ - function getValue() - { - return $this->value; - } - - - - /** Deep copy */ - function __clone() - { - parent::__clone(); - - if (is_object($this->value)) { - $this->value = clone $this->value; - } - } - + private Fraction $value; + + /** + * @param array $set + */ + public function __construct(array $set, $value) + { + parent::__construct($set); + + $this->value = Fraction::create($value); + } + + /** Deep copy */ + public function __clone() + { + parent::__clone(); + + if (is_object($this->value)) { + $this->value = clone $this->value; + } + } + + public function getValue(): Fraction + { + return $this->value; + } } diff --git a/Simplex/VariableSet.php b/Simplex/VariableSet.php index 43f788e..99c3058 100644 --- a/Simplex/VariableSet.php +++ b/Simplex/VariableSet.php @@ -1,98 +1,76 @@ fraction ] */ - function __construct(array $set) - { - foreach ($set as $var => $coeff) { - $set[$var] = Fraction::create($coeff); - } - - ksort($set); - $this->set = $set; - } - - - - /** @return array */ - function getSet() - { - return $this->set; - } - - - - /** @return Fraction|NULL */ - function getMin() - { - $min = NULL; - - foreach ($this->set as $var => $coeff) { - if ($min === NULL || $coeff->isLowerThan($min)) { - $min = $coeff; - } - } - - return $min; - } - - - - /** @return string[] */ - function getVariableList() - { - return array_keys($this->set); - } - - - - /** - * @param string $var - * @return bool - */ - function has($var) - { - return isset($this->set[$var]); - } - - - - /** - * @param string $var - * @return Fraction - */ - function get($var) - { - return $this->set[$var]; - } - - - - /** Deep copy */ - function __clone() - { - foreach ($this->set as $var => $coeff) { - $this->set[$var] = clone $coeff; - } - } - + /** @var array */ + protected array $set; + + /** @param array $set [ varname => fraction ] */ + public function __construct(array $set) + { + foreach ($set as $var => $coeff) { + $set[$var] = Fraction::create($coeff); + } + + ksort($set); + $this->set = $set; + } + + /** Deep copy */ + public function __clone() + { + foreach ($this->set as $var => $coeff) { + $this->set[$var] = clone $coeff; + } + } + + /** @return array */ + public function getSet(): array + { + return $this->set; + } + + /** @return Fraction|NULL */ + public function getMin(): ?Fraction + { + $min = null; + + foreach ($this->set as $var => $coeff) { + if ($min === null || $coeff->isLowerThan($min)) { + $min = $coeff; + } + } + + return $min; + } + + /** @return array */ + public function getVariableList(): array + { + return array_keys($this->set); + } + + public function has(string $var): bool + { + return isset($this->set[$var]); + } + + public function get(string $var): Fraction + { + return $this->set[$var]; + } } diff --git a/Simplex/simplex.php b/Simplex/simplex.php index 08ef57c..ba6d93b 100644 --- a/Simplex/simplex.php +++ b/Simplex/simplex.php @@ -1,5 +1,7 @@ 1, 'x2' => 2, -)); +]); $task = new Simplex\Task($z); -$task->addRestriction(new Simplex\Restriction(array( +$task->addRestriction(new Simplex\Restriction([ 'x1' => 3, 'x2' => 2, -), Simplex\Restriction::TYPE_LOE, 24)); +], Simplex\Restriction::TYPE_LOE, 24)); -$task->addRestriction(new Simplex\Restriction(array( +$task->addRestriction(new Simplex\Restriction([ 'x1' => -2, 'x2' => -4, -), Simplex\Restriction::TYPE_GOE, -32)); - +], Simplex\Restriction::TYPE_GOE, -32)); $solver = new Simplex\Solver($task); var_dump($solver); -die(); +die; From b2efdc36ae5af26325b033674c375d57aa7a9b7e Mon Sep 17 00:00:00 2001 From: Marcin Lenkowski Date: Mon, 9 May 2022 08:27:52 +0200 Subject: [PATCH 3/4] added LinearProgram class for nicer problem writing --- README.md | 54 +++++++++++++++- Simplex/Formulation/Constraint.php | 28 ++++++++ Simplex/Formulation/Equal.php | 7 ++ Simplex/Formulation/Formula.php | 65 +++++++++++++++++++ Simplex/Formulation/GreaterOrEqual.php | 7 ++ Simplex/Formulation/LinearProgram.php | 88 ++++++++++++++++++++++++++ Simplex/Formulation/LowerOrEqual.php | 7 ++ Simplex/Helpers.php | 2 +- Simplex/Solution.php | 62 ++++++++++++++++++ composer.json | 3 +- tests/TaskCreator.php | 43 +++++++++++++ tests/TasksInstantiatorTest.php | 75 ++++++++++++++++++++++ tests/TasksTest.php | 77 +++++++++++++--------- 13 files changed, 484 insertions(+), 34 deletions(-) create mode 100644 Simplex/Formulation/Constraint.php create mode 100644 Simplex/Formulation/Equal.php create mode 100644 Simplex/Formulation/Formula.php create mode 100644 Simplex/Formulation/GreaterOrEqual.php create mode 100644 Simplex/Formulation/LinearProgram.php create mode 100644 Simplex/Formulation/LowerOrEqual.php create mode 100644 Simplex/Solution.php create mode 100644 tests/TaskCreator.php create mode 100644 tests/TasksInstantiatorTest.php diff --git a/README.md b/README.md index 8a61469..13bb6e8 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,56 @@ Enjoy. Conventions ----------- -Please name your variables as **x<n>** where *<n>* is a natural number (see [example.php](https://github.com/uestla/Simplex-Calculator/blob/master/example.php) for details). +Please name your variables as **x<n>** where *<n>* is a natural number ( +see [example.php](https://github.com/uestla/Simplex-Calculator/blob/master/example.php) for details). + + +Using LinearProgram class +------------------------- + +Using `\Simplex\Formulation\LinearProgram` you can formulate linear program solution. + +For example let's assume, that we have given linear problem: + +> z = 2x1 + x2 + +with constraints: +> x1 + x2 >= 2 +> +> x1 - x2 = 0 +> +> x1 - x2 >= -4 + +that we can describe in following way: + +```php +use \Simplex\Formulation\LinearProgram; +use \Simplex\Formulation\Formula; +use \Simplex\Formulation\GreaterOrEqual; +use \Simplex\Formulation\Equal; + +$program = new LinearProgram( + new Formula(2, 1), + [ + new GreaterOrEqual(new Formula(1, 1), 2), + new Equal(new Formula(1, -1), 0), + new GreaterOrEqual(new Formula(1, -2), -4), + ] +); +``` + +Now we can take maximum solution with only: + +```php +$program->getMax(); // return 12 +``` + +And optimized parameters: + +```php +$solution = $program->getSolutionFormula(); +// returns new Formula(4, 4, 6, 0); + +$solution->getParam('x1'); // 4 +$solution->getParam(1); // the same as upper 4 +``` \ No newline at end of file diff --git a/Simplex/Formulation/Constraint.php b/Simplex/Formulation/Constraint.php new file mode 100644 index 0000000..df80129 --- /dev/null +++ b/Simplex/Formulation/Constraint.php @@ -0,0 +1,28 @@ +formula = $formula; + $this->constraint = $constraint; + } + + public function getFormula(): Formula + { + return $this->formula; + } + + public function getConstraint(): int + { + return $this->constraint; + } +} \ No newline at end of file diff --git a/Simplex/Formulation/Equal.php b/Simplex/Formulation/Equal.php new file mode 100644 index 0000000..8472aa1 --- /dev/null +++ b/Simplex/Formulation/Equal.php @@ -0,0 +1,7 @@ +coefficients['x' . ++$i] = $coefficient; + } + } + + public function getCoefficients(): array + { + return $this->coefficients; + } + + public function getSize(): int + { + return count($this->coefficients); + } + + public function getParam(string $variable): float + { + if (is_numeric($variable)) { + $variable = 'x' . $variable; + } + + if (!array_key_exists($variable, $this->coefficients)) { + throw new \InvalidArgumentException('Variable ' . $variable . 'is not exists'); + } + + return $this->coefficients[$variable]; + } +} \ No newline at end of file diff --git a/Simplex/Formulation/GreaterOrEqual.php b/Simplex/Formulation/GreaterOrEqual.php new file mode 100644 index 0000000..c9469b3 --- /dev/null +++ b/Simplex/Formulation/GreaterOrEqual.php @@ -0,0 +1,7 @@ +validate($goal, $constraints); + + $this->goal = $goal; + $this->constraints = $constraints; + + $this->makeTask(); + $this->solution = Solution::build($this->task); + } + + private function validate(Formula $goal, array $constraints): void + { + foreach ($constraints as $constraint) { + if (!$constraint instanceof Constraint) { + throw new \InvalidArgumentException("All \$constraints have to be instance of \Simplex\Constraint"); + } + + if ($constraint->getFormula()->getSize() !== $goal->getSize()) { + throw new \OutOfBoundsException("Number of parameters are not the same for each formula"); + } + } + } + + private function makeTask(): void + { + $z = new Func($this->goal->getCoefficients()); + $this->task = new Task($z); + + /** @var Constraint $constraint */ + foreach ($this->constraints as $constraint) { + $restriction = [ + GreaterOrEqual::class => Restriction::TYPE_GOE, + LowerOrEqual::class => Restriction::TYPE_LOE, + Equal::class => Restriction::TYPE_EQ, + ][get_class($constraint)]; + + $this->task->addRestriction( + new Restriction( + $constraint->getFormula()->getCoefficients(), + $restriction, + $constraint->getConstraint() + ) + ); + } + } + + public function getSolution(): Solution + { + return $this->solution; + } + + public function getOptimizedParams(): array + { + return $this->solution->getSolutionParams(); + } + + public function getMax(): float + { + return $this->solution->getMax(); + } + + public function getSolutionFormula(): Formula + { + return new Formula($this->solution->getSolutionParams()); + } + +} \ No newline at end of file diff --git a/Simplex/Formulation/LowerOrEqual.php b/Simplex/Formulation/LowerOrEqual.php new file mode 100644 index 0000000..5b7145b --- /dev/null +++ b/Simplex/Formulation/LowerOrEqual.php @@ -0,0 +1,7 @@ +steps = $steps; + $this->solution = end($steps)->getSolution() ?: null; + $this->z = current($steps)->getZ(); + } + + public static function instantiateFromSolver(Solver $solver): self + { + return new self($solver->getSteps()); + } + + public static function build(Task $task): self + { + return static::instantiateFromSolver(new Solver($task)); + } + + public function getSolution(): ?array + { + return $this->solution; + } + + public function getSolutionParams(): array + { + $solutionParameters = []; + foreach ($this->getSolution() as $variable => $value) { + $solutionParameters[$variable] = $value->toFloat(); + } + + return $solutionParameters; + } + + public function getMaxFraction(): Fraction + { + return $this->z->getB(); + } + + public function getMax(): float + { + return $this->getMaxFraction()->toFloat(); + } + + public function getSteps(): array + { + return $this->steps; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index eaaba26..697f973 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "require-dev": { "nette/tester": "@dev", "phpunit/phpunit": "^9.3.3", - "nunomaduro/phpinsights": "^2.3" + "nunomaduro/phpinsights": "^2.3", + "symfony/var-dumper": "^4.4" }, "autoload": { "psr-0": { diff --git a/tests/TaskCreator.php b/tests/TaskCreator.php new file mode 100644 index 0000000..28729d5 --- /dev/null +++ b/tests/TaskCreator.php @@ -0,0 +1,43 @@ + 2, + 'x2' => 1, + )); + + $task = new Task($z); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => 1, + + ), Restriction::TYPE_GOE, 2)); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => -1, + + ), Restriction::TYPE_EQ, 0)); + + $task->addRestriction(new Restriction(array( + 'x1' => 1, + 'x2' => -2, + + ), Restriction::TYPE_GOE, -4)); + + return $task; + } +} \ No newline at end of file diff --git a/tests/TasksInstantiatorTest.php b/tests/TasksInstantiatorTest.php new file mode 100644 index 0000000..9cf9162 --- /dev/null +++ b/tests/TasksInstantiatorTest.php @@ -0,0 +1,75 @@ +expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("All \$constraints have to be instance of \Simplex\Constraint"); + + new LinearProgram( + new Formula(2, 1), + [ + new GreaterOrEqual(new Formula(1, 1), 2), + new Equal(new Formula(1, -1), 0), + null + ] + ); + } + + public function test_should_all_formula_has_same_length(): void + { + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage("Number of parameters are not the same for each formula"); + + new LinearProgram( + new Formula(2, 1), + [ + new GreaterOrEqual(new Formula(1), 2), + new Equal(new Formula(1, -1, 2), 0), + ] + ); + } + + public function test_task_instantiate(): void + { + /** + * We have given linear problem + * z = 2x1 + x2 + * with constraints: + * x1 + x2 >= 2 + * x1 - x2 = 0 + * x1 - x2 >= -4 + * + * that we can describe using \Simplex\Formulation\LinearProgram + */ + $program = new LinearProgram( + new Formula(2, 1), + [ + new GreaterOrEqual(new Formula(1, 1), 2), + new Equal(new Formula(1, -1), 0), + new GreaterOrEqual(new Formula(1, -2), -4), + ] + ); + + $params = array('x1' => 4, 'x2' => 4, 'x3' => 6, 'x4' => 0); + $formula = new Formula($params); + + $this->assertCount(3 + 4, $program->getSolution()->getSteps()); + $this->assertEquals($params, $program->getOptimizedParams()); + $this->assertEquals($formula, $program->getSolutionFormula()); + $this->assertEquals($params['x1'], $program->getSolutionFormula()->getParam('x1')); + $this->assertEquals($params['x1'], $program->getSolutionFormula()->getParam(1)); + $this->assertEquals(12, $program->getMax()); + } +} \ No newline at end of file diff --git a/tests/TasksTest.php b/tests/TasksTest.php index 09c1e58..a7d5bc0 100644 --- a/tests/TasksTest.php +++ b/tests/TasksTest.php @@ -5,13 +5,14 @@ use PHPUnit\Framework\TestCase; use Simplex\Func; use Simplex\Restriction; +use Simplex\Solution; use Simplex\Solver; use Simplex\Task; -use Simplex\VariableSet; -use Tester\Assert; class TasksTest extends TestCase { + use TaskCreator; + public function test_task_restriction_variables(): void { $this->expectException(\InvalidArgumentException::class); @@ -28,35 +29,12 @@ public function test_task_restriction_variables(): void public function test_first_task_function(): void { - $z = new Func(array( - 'x1' => 2, - 'x2' => 1, - )); - - $task = new Task($z); - - $task->addRestriction(new Restriction(array( - 'x1' => 1, - 'x2' => 1, - - ), Restriction::TYPE_GOE, 2)); - - $task->addRestriction(new Restriction(array( - 'x1' => 1, - 'x2' => -1, - - ), Restriction::TYPE_EQ, 0)); - - $task->addRestriction(new Restriction(array( - 'x1' => 1, - 'x2' => -2, - - ), Restriction::TYPE_GOE, -4)); + $task = $this->getFirstTask(); $solver = new Solver($task); $steps = $solver->getSteps(); - $this->assertEquals(3 + 4, count($steps)); + $this->assertCount(3 + 4, $steps); $exp = array( 'x1' => 4, @@ -70,7 +48,44 @@ public function test_first_task_function(): void } $this->assertFalse(current($steps)->hasAlternativeSolution()); - $this->assertTrue(current($steps)->getZ()->getB()->isEqualTo(12)); + $this->assertEquals(12, current($steps)->getZ()->getB()->toFloat()); + } + + public function test_first_task_with_solution_instance(): void + { + $task = $this->getFirstTask(); + + $solver = new Solver($task); + $solution = Solution::instantiateFromSolver($solver); + + $exp = array( + 'x1' => 4, + 'x2' => 4, + 'x3' => 6, + 'x4' => 0, + ); + + $this->assertCount(3 + 4, $solution->getSteps()); + $this->assertEquals($exp, $solution->getSolutionParams()); + $this->assertEquals(12, $solution->getMax()); + } + + public function test_first_task_as_builder_solution(): void + { + $task = $this->getFirstTask(); + + $solution = Solution::build($task); + + $exp = array( + 'x1' => 4, + 'x2' => 4, + 'x3' => 6, + 'x4' => 0, + ); + + $this->assertCount(3 + 4, $solution->getSteps()); + $this->assertEquals($exp, $solution->getSolutionParams()); + $this->assertEquals(12, $solution->getMax()); } public function test_second_task_function(): void @@ -98,7 +113,7 @@ public function test_second_task_function(): void $solver = new Solver($task); $steps = $solver->getSteps(); - $this->assertEquals(3 + 3, count($steps)); + $this->assertCount(3 + 3, $steps); $exp1 = array( 'x1' => 4, @@ -162,7 +177,7 @@ public function test_third_function(): void $solver = new \Simplex\Solver($task); $steps = $solver->getSteps(); - $this->assertEquals(3 + 4, count($steps)); + $this->assertCount(3 + 4, $steps); $exp = array( 'x1' => 40, @@ -211,7 +226,7 @@ public function test_fourth_function(): void $solver = new \Simplex\Solver($task); $steps = $solver->getSteps(); - $this->assertEquals(3 + 4, count($steps)); + $this->assertCount(3 + 4, $steps); $this->assertFalse(end($steps)->hasSolution()); } } \ No newline at end of file From 188ba5df32782c6915a50dd1520006593013e952 Mon Sep 17 00:00:00 2001 From: Marcin Lenkowski Date: Mon, 9 May 2022 08:44:17 +0200 Subject: [PATCH 4/4] update with example --- README.md | 5 +- Simplex/Formulation/LinearProgram.php | 1 + example_simplified.php | 25 + test.txt | 1019 +++++++++++++++++++++++++ 4 files changed, 1049 insertions(+), 1 deletion(-) create mode 100644 example_simplified.php create mode 100644 test.txt diff --git a/README.md b/README.md index 13bb6e8..e35cd55 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,7 @@ $solution = $program->getSolutionFormula(); $solution->getParam('x1'); // 4 $solution->getParam(1); // the same as upper 4 -``` \ No newline at end of file +``` + +see [example_simplified.php](https://github.com/uestla/Simplex-Calculator/blob/master/example_simplified.php) for +details. diff --git a/Simplex/Formulation/LinearProgram.php b/Simplex/Formulation/LinearProgram.php index 9a67436..75cced3 100644 --- a/Simplex/Formulation/LinearProgram.php +++ b/Simplex/Formulation/LinearProgram.php @@ -5,6 +5,7 @@ use Simplex\Func; use Simplex\Restriction; use Simplex\Solution; +use Simplex\Solver; use Simplex\Task; use Simplex\VariableSet; diff --git a/example_simplified.php b/example_simplified.php new file mode 100644 index 0000000..05b4915 --- /dev/null +++ b/example_simplified.php @@ -0,0 +1,25 @@ +getMax(), // 16 + $program->getOptimizedParams(), // array(4) { ["x1"]=>float(4) ["x2"]=>float(6) ["x3"]=>float(0) ["x4"]=>float(0) } + $program->getSolutionFormula(), // upper value but in Simplex\Formulation\Formula +); \ No newline at end of file diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..123e520 --- /dev/null +++ b/test.txt @@ -0,0 +1,1019 @@ +object(Simplex\Solver)#14 (2) { + ["steps":"Simplex\Solver":private]=> + array(6) { + [0]=> + object(Simplex\Task)#5 (3) { + ["function":"Simplex\Task":private]=> + object(Simplex\Func)#3 (1) { + ["set":protected]=> + array(2) { + ["x1"]=> + object(Simplex\Fraction)#2 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#4 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + ["restrictions":"Simplex\Task":private]=> + array(2) { + [0]=> + object(Simplex\Restriction)#6 (3) { + ["type":"Simplex\Restriction":private]=> + int(2) + ["limit":"Simplex\Restriction":private]=> + object(Simplex\Fraction)#9 (2) { + ["n":"Simplex\Fraction":private]=> + int(24) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(2) { + ["x1"]=> + object(Simplex\Fraction)#7 (2) { + ["n":"Simplex\Fraction":private]=> + int(3) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#8 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + [1]=> + object(Simplex\Restriction)#10 (3) { + ["type":"Simplex\Restriction":private]=> + int(4) + ["limit":"Simplex\Restriction":private]=> + object(Simplex\Fraction)#13 (2) { + ["n":"Simplex\Fraction":private]=> + int(-32) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(2) { + ["x1"]=> + object(Simplex\Fraction)#11 (2) { + ["n":"Simplex\Fraction":private]=> + int(-2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#12 (2) { + ["n":"Simplex\Fraction":private]=> + int(-4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + } + ["basismap":"Simplex\Task":private]=> + array(0) { + } + } + [1]=> + object(Simplex\Task)#15 (3) { + ["function":"Simplex\Task":private]=> + object(Simplex\Func)#16 (1) { + ["set":protected]=> + array(2) { + ["x1"]=> + object(Simplex\Fraction)#17 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#18 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + ["restrictions":"Simplex\Task":private]=> + array(2) { + [0]=> + object(Simplex\Restriction)#19 (3) { + ["type":"Simplex\Restriction":private]=> + int(2) + ["limit":"Simplex\Restriction":private]=> + object(Simplex\Fraction)#20 (2) { + ["n":"Simplex\Fraction":private]=> + int(24) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(2) { + ["x1"]=> + object(Simplex\Fraction)#7 (2) { + ["n":"Simplex\Fraction":private]=> + int(3) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#8 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + [1]=> + object(Simplex\Restriction)#21 (3) { + ["type":"Simplex\Restriction":private]=> + int(2) + ["limit":"Simplex\Restriction":private]=> + object(Simplex\Fraction)#26 (2) { + ["n":"Simplex\Fraction":private]=> + int(32) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(2) { + ["x1"]=> + object(Simplex\Fraction)#24 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#25 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + } + ["basismap":"Simplex\Task":private]=> + array(0) { + } + } + [2]=> + object(Simplex\Task)#22 (3) { + ["function":"Simplex\Task":private]=> + object(Simplex\Func)#33 (1) { + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#27 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#28 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#34 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#35 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + ["restrictions":"Simplex\Task":private]=> + array(2) { + [0]=> + object(Simplex\Restriction)#29 (3) { + ["type":"Simplex\Restriction":private]=> + int(1) + ["limit":"Simplex\Restriction":private]=> + object(Simplex\Fraction)#30 (2) { + ["n":"Simplex\Fraction":private]=> + int(24) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#7 (2) { + ["n":"Simplex\Fraction":private]=> + int(3) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#8 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#23 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#36 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + [1]=> + object(Simplex\Restriction)#37 (3) { + ["type":"Simplex\Restriction":private]=> + int(1) + ["limit":"Simplex\Restriction":private]=> + object(Simplex\Fraction)#32 (2) { + ["n":"Simplex\Fraction":private]=> + int(32) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#24 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#25 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#38 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#39 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + } + ["basismap":"Simplex\Task":private]=> + array(2) { + ["x3"]=> + int(0) + ["x4"]=> + int(1) + } + } + [3]=> + object(Simplex\Table)#46 (6) { + ["rows":"Simplex\Table":private]=> + array(2) { + [0]=> + object(Simplex\TableRow)#49 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x3" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#30 (2) { + ["n":"Simplex\Fraction":private]=> + int(24) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#7 (2) { + ["n":"Simplex\Fraction":private]=> + int(3) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#8 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#23 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#36 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + [1]=> + object(Simplex\TableRow)#50 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x4" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#32 (2) { + ["n":"Simplex\Fraction":private]=> + int(32) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#24 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#25 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#38 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#39 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + } + ["z":"Simplex\Table":private]=> + object(Simplex\TableRow)#47 (3) { + ["var":"Simplex\TableRow":private]=> + string(1) "z" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#48 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#40 (2) { + ["n":"Simplex\Fraction":private]=> + int(-1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#41 (2) { + ["n":"Simplex\Fraction":private]=> + int(-2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#42 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#43 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + } + ["z2":"Simplex\Table":private]=> + NULL + ["basis":"Simplex\Table":private]=> + array(2) { + [0]=> + string(2) "x3" + [1]=> + string(2) "x4" + } + ["solution":"Simplex\Table":private]=> + NULL + ["alternative":"Simplex\Table":private]=> + NULL + } + [4]=> + object(Simplex\Table)#45 (6) { + ["rows":"Simplex\Table":private]=> + array(2) { + [0]=> + object(Simplex\TableRow)#31 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x3" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#67 (2) { + ["n":"Simplex\Fraction":private]=> + int(8) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#70 (2) { + ["n":"Simplex\Fraction":private]=> + int(2) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#71 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#72 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#73 (2) { + ["n":"Simplex\Fraction":private]=> + int(-1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + } + } + [1]=> + object(Simplex\TableRow)#54 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x2" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#74 (2) { + ["n":"Simplex\Fraction":private]=> + int(8) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#75 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + ["x2"]=> + object(Simplex\Fraction)#76 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#77 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#78 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(4) + } + } + } + } + ["z":"Simplex\Table":private]=> + object(Simplex\TableRow)#79 (3) { + ["var":"Simplex\TableRow":private]=> + string(1) "z" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#85 (2) { + ["n":"Simplex\Fraction":private]=> + int(16) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#68 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#81 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#82 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#83 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + } + } + ["z2":"Simplex\Table":private]=> + NULL + ["basis":"Simplex\Table":private]=> + array(4) { + [0]=> + string(2) "x3" + [1]=> + string(2) "x4" + [2]=> + string(2) "x3" + [3]=> + string(2) "x2" + } + ["solution":"Simplex\Table":private]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#61 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#74 (2) { + ["n":"Simplex\Fraction":private]=> + int(8) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#67 (2) { + ["n":"Simplex\Fraction":private]=> + int(8) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#66 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + ["alternative":"Simplex\Table":private]=> + object(Simplex\Table)#80 (6) { + ["rows":"Simplex\Table":private]=> + array(2) { + [0]=> + object(Simplex\TableRow)#86 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x1" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#103 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#57 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#58 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#59 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + ["x4"]=> + object(Simplex\Fraction)#60 (2) { + ["n":"Simplex\Fraction":private]=> + int(-1) + ["d":"Simplex\Fraction":private]=> + int(4) + } + } + } + [1]=> + object(Simplex\TableRow)#53 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x2" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#95 (2) { + ["n":"Simplex\Fraction":private]=> + int(6) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#65 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#44 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#51 (2) { + ["n":"Simplex\Fraction":private]=> + int(-1) + ["d":"Simplex\Fraction":private]=> + int(4) + } + ["x4"]=> + object(Simplex\Fraction)#52 (2) { + ["n":"Simplex\Fraction":private]=> + int(3) + ["d":"Simplex\Fraction":private]=> + int(8) + } + } + } + } + ["z":"Simplex\Table":private]=> + object(Simplex\TableRow)#87 (3) { + ["var":"Simplex\TableRow":private]=> + string(1) "z" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#110 (2) { + ["n":"Simplex\Fraction":private]=> + int(16) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#64 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#62 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#63 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#107 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + } + } + ["z2":"Simplex\Table":private]=> + NULL + ["basis":"Simplex\Table":private]=> + array(8) { + [0]=> + string(2) "x3" + [1]=> + string(2) "x4" + [2]=> + string(2) "x3" + [3]=> + string(2) "x2" + [4]=> + string(2) "x1" + [5]=> + string(2) "x2" + [6]=> + string(2) "x1" + [7]=> + string(2) "x2" + } + ["solution":"Simplex\Table":private]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#103 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#95 (2) { + ["n":"Simplex\Fraction":private]=> + int(6) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#100 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#102 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + ["alternative":"Simplex\Table":private]=> + NULL + } + } + [5]=> + object(Simplex\Table)#80 (6) { + ["rows":"Simplex\Table":private]=> + array(2) { + [0]=> + object(Simplex\TableRow)#86 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x1" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#103 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#57 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#58 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#59 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + ["x4"]=> + object(Simplex\Fraction)#60 (2) { + ["n":"Simplex\Fraction":private]=> + int(-1) + ["d":"Simplex\Fraction":private]=> + int(4) + } + } + } + [1]=> + object(Simplex\TableRow)#53 (3) { + ["var":"Simplex\TableRow":private]=> + string(2) "x2" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#95 (2) { + ["n":"Simplex\Fraction":private]=> + int(6) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#65 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#44 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#51 (2) { + ["n":"Simplex\Fraction":private]=> + int(-1) + ["d":"Simplex\Fraction":private]=> + int(4) + } + ["x4"]=> + object(Simplex\Fraction)#52 (2) { + ["n":"Simplex\Fraction":private]=> + int(3) + ["d":"Simplex\Fraction":private]=> + int(8) + } + } + } + } + ["z":"Simplex\Table":private]=> + object(Simplex\TableRow)#87 (3) { + ["var":"Simplex\TableRow":private]=> + string(1) "z" + ["b":"Simplex\TableRow":private]=> + object(Simplex\Fraction)#110 (2) { + ["n":"Simplex\Fraction":private]=> + int(16) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["set":protected]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#64 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#62 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#63 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#107 (2) { + ["n":"Simplex\Fraction":private]=> + int(1) + ["d":"Simplex\Fraction":private]=> + int(2) + } + } + } + ["z2":"Simplex\Table":private]=> + NULL + ["basis":"Simplex\Table":private]=> + array(8) { + [0]=> + string(2) "x3" + [1]=> + string(2) "x4" + [2]=> + string(2) "x3" + [3]=> + string(2) "x2" + [4]=> + string(2) "x1" + [5]=> + string(2) "x2" + [6]=> + string(2) "x1" + [7]=> + string(2) "x2" + } + ["solution":"Simplex\Table":private]=> + array(4) { + ["x1"]=> + object(Simplex\Fraction)#103 (2) { + ["n":"Simplex\Fraction":private]=> + int(4) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x2"]=> + object(Simplex\Fraction)#95 (2) { + ["n":"Simplex\Fraction":private]=> + int(6) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x3"]=> + object(Simplex\Fraction)#100 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + ["x4"]=> + object(Simplex\Fraction)#102 (2) { + ["n":"Simplex\Fraction":private]=> + int(0) + ["d":"Simplex\Fraction":private]=> + int(1) + } + } + ["alternative":"Simplex\Table":private]=> + NULL + } + } + ["maxSteps":"Simplex\Solver":private]=> + int(16) +}