-
Notifications
You must be signed in to change notification settings - Fork 239
Expand file tree
/
Copy pathMonomialExponentGenerator.php
More file actions
115 lines (106 loc) · 3.72 KB
/
MonomialExponentGenerator.php
File metadata and controls
115 lines (106 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php
namespace MathPHP\Polynomials;
use Generator;
use InvalidArgumentException;
use MathPHP\Exception;
use MathPHP\Probability\Combinatorics;
final class MonomialExponentGenerator
{
/**
* @param int $dimension
* @param int $degree
* @return int
* @throws Exception\OutOfBoundsException
*/
public static function getNumberOfTerms(int $dimension, int $degree): int
{
return (int)(Combinatorics::factorial($dimension + $degree) /
(Combinatorics::factorial($degree) * Combinatorics::factorial($dimension)));
}
/**
* Returns all exponent tuples with total degree <= $degree.
*
* @param int $dimension d >= 1
* @param int $degree p >= 0
* @param bool $reverse
* @return list<list<int>>
*/
public static function all(int $dimension, int $degree, bool $reverse): array
{
$gen = self::iterate($dimension, $degree, $reverse);
return \iterator_to_array($gen, false);
}
/**
* Generator over all exponent tuples with total degree <= $degree.
* Uses generators to keep memory usage low for large d/p.
*
* @param int $dimension
* @param int $degree
* @param bool $reverse
* @return Generator<int, list<int>> yields int[] (length = $dimension)
*/
public static function iterate(int $dimension, int $degree, bool $reverse): Generator
{
if ($dimension < 1) {
throw new InvalidArgumentException("dimension must be >= 1.");
}
if ($degree < 0) {
throw new InvalidArgumentException("degree must be >= 0.");
}
$current = \array_fill(0, $dimension, 0);
if ($reverse) {
// Degrees 0..p; within each degree use lexicographic order
for ($g = 0; $g <= $degree; $g++) {
yield from self::recursiveDistributeRevLex($dimension, $g, 0, $current);
}
} else {
// Degrees 0..p; within each degree use lexicographic order
for ($g = 0; $g <= $degree; $g++) {
yield from self::recursiveDistributeLex($dimension, $g, 0, $current);
}
}
}
/**
* Recursive helper: distributes `remaining` units across positions in lexicographic order.
*
* @param int $dimension
* @param int $remaining
* @param int $pos
* @param list<int> $current Variable reference for performance.
* @return Generator<int, list<int>>
*/
private static function recursiveDistributeLex(int $dimension, int $remaining, int $pos, array &$current): Generator
{
if ($pos === $dimension - 1) {
$current[$pos] = $remaining;
yield $current;
return;
}
for ($e = 0; $e <= $remaining; $e++) {
$current[$pos] = $e;
yield from self::recursiveDistributeLex($dimension, $remaining - $e, $pos + 1, $current);
}
}
/**
* Recursive helper: distributes `remaining` units across positions in reverse-lex order.
*
* @param int $dimension
* @param int $remaining
* @param int $pos
* @param list<int> $current Variable reference for performance.
* @return Generator<int, list<int>>
*/
private static function recursiveDistributeRevLex(int $dimension, int $remaining, int $pos, array &$current): Generator
{
if ($pos === $dimension - 1) {
$current[$pos] = $remaining;
yield $current;
return;
}
// reverse-lex: prioritize larger exponents at earlier positions
for ($e = $remaining; $e >= 0; $e--) {
$current[$pos] = $e;
yield from self::recursiveDistributeRevLex($dimension, $remaining - $e, $pos + 1, $current);
}
}
}