File Coverage

blib/lib/Math/DifferenceSet/Planar.pm
Criterion Covered Total %
statement 835 835 100.0
branch 288 296 97.3
condition 60 69 86.9
subroutine 133 133 100.0
pod 79 79 100.0
total 1395 1412 98.8


line stmt bran cond sub pod time code
1             package Math::DifferenceSet::Planar;
2              
3 6     6   135473 use strict;
  6         18  
  6         251  
4 6     6   29 use warnings;
  6         11  
  6         411  
5 6     6   34 use Carp qw(croak);
  6         13  
  6         467  
6 6     6   4995 use Math::DifferenceSet::Planar::Data;
  6         20  
  6         338  
7 6         38 use Math::Prime::Util qw(
8             is_prime is_power is_prime_power euler_phi factor_exp gcd
9             mulmod addmod invmod powmod divmod logint
10 6     6   13663 );
  6         72427  
11              
12             # Math::DifferenceSet::Planar=ARRAY(...)
13             # ............ index ............ # ........... value ...........
14 6     6   2570 use constant _F_ORDER => 0;
  6         12  
  6         436  
15 6     6   40 use constant _F_BASE => 1;
  6         11  
  6         385  
16 6     6   37 use constant _F_EXPONENT => 2;
  6         14  
  6         450  
17 6     6   33 use constant _F_MODULUS => 3;
  6         12  
  6         321  
18 6     6   32 use constant _F_ZETA => 4; # "zeta" value
  6         12  
  6         287  
19 6     6   32 use constant _F_ETA => 5; # "eta" value, initially undef
  6         12  
  6         295  
20 6     6   51 use constant _F_THETA => 6; # translation amount from canonical set
  6         11  
  6         358  
21 6     6   34 use constant _F_PRINC_ELEMS => 7; # principal elements arrayref
  6         10  
  6         300  
22 6     6   41 use constant _F_SUPPL_ELEMS => 8; # supplemental elements arrayref
  6         17  
  6         279  
23 6     6   32 use constant _F_LAMBDA => 9; # plane logarithm value or undef
  6         11  
  6         396  
24 6     6   36 use constant _F_ELEMENTS => 10; # elements arrayref, initially undef
  6         11  
  6         422  
25 6     6   43 use constant _F_X_START => 11; # index of start element in elements
  6         13  
  6         307  
26 6     6   44 use constant _F_X_GAP => 12; # index of max gap element in elements
  6         13  
  6         293  
27 6     6   33 use constant _NFIELDS => 13;
  6         18  
  6         424  
28              
29             # usable native integer bits, typically 63 or 31
30 6     6   34 use constant _NATIVE_BITS => logint(~0, 2);
  6         11  
  6         655  
31             # max order safe to use with native integer arithmetic
32 6     6   40 use constant _MAX_SMALL_ORDER => int( sqrt(2)*((1<<(_NATIVE_BITS>>1))-0.5) );
  6         12  
  6         72751  
33              
34             *canonize = \&lex_canonize;
35              
36             our $VERSION = '1.003';
37              
38             our $_MAX_ENUM_COUNT = 32768; # limit for stored rotator set size
39             our $_MAX_MEMO_COUNT = 4096; # limit for memoized values
40             our $_USE_SPACES_DB = 1; # enable looking up rotators
41              
42             my $current_data = undef; # current M::D::P::Data object
43              
44             my %memo_n_planes = (); # memoized n_planes values
45             my @memo_np_orders = (); # memoized orders FIFO
46             my %memo_rotators = (); # memoized rotators arrayrefs
47             my @memo_ro_orders = (); # memoized orders FIFO
48              
49             # ----- private subroutines -----
50              
51             # return complete rotator base if known or small enough, otherwise undef
52             #
53             # If structure is known:
54             #
55             # rotators -> +-----------+
56             # | radices ----------------------------> +-------+
57             # +-----------+ | r_1 |
58             # | depths -----------------> +-------+ +-------+
59             # +-----------+ | d_1 | | r_2 |
60             # | inverses -----> +-------+ +-------+ +-------+
61             # +-----------+ | i_1 | | d_2 | | ... |
62             # +-------+ +-------+ +-------+
63             # | i_2 | | ... | | r_n |
64             # +-------+ +-------+ +-------+
65             # | ... | | d_n |
66             # +-------+ +-------+
67             # | i_n |
68             # +-------+
69             #
70             # n
71             # ___ j
72             # R _ | | r k 0 < j < d
73             # j j ...j = | | k (mod M), = k k
74             # 1 2 n
75             # k = 1
76             #
77             # d - 1 _
78             # i r k = 1 (mod M), d > d > ... > d > 2
79             # k k 1 = 2 = = n =
80             #
81             #
82             # Otherwise, if number of rotators is small:
83             #
84             # rotators -> +-------+-------+-- --+-------+
85             # | R_1 | R_2 | ... | R_N |
86             # +-------+-------+-- --+-------+
87             #
88             # Otherwise, return undef.
89             #
90             sub _rotators {
91 23     23   70 my ($class, $order, $base, $exponent, $modulus) = @_;
92 23 100       97 return $memo_rotators{$order} if exists $memo_rotators{$order};
93 9         17 my $rotators = undef;
94 9   66     44 my $space = $_USE_SPACES_DB && $class->_data->get_space($order);
95 9 100       18601 if ($space) {
    100          
96 6         216 my ($radices, $depths) = $space->rotator_space;
97             my $inverses = [
98             map {
99 8         108 invmod(
100             powmod($radices->[$_], $depths->[$_] - 1, $modulus),
101             $modulus
102             )
103 6         21 } 0 .. $#{$radices}
  6         24  
104             ];
105 6         25 $rotators = [$radices, $depths, $inverses];
106             }
107             elsif (_n_planes($order, $exponent, $modulus) <= $_MAX_ENUM_COUNT) {
108 1         113 my $mult = 3 * $exponent;
109 1         9 my $sieve = '1' x $modulus;
110 1         4 substr($sieve, 1, 1) = '0';
111 1         3 my $e = 1;
112 1         5 for (2 .. $mult) {
113 11         26 $e = mulmod($e, $base, $modulus);
114 11         20 substr($sieve, $e, 1) = '0';
115             }
116 1         4 my @rot = (1);
117 1         5 for (my $x = 2; $x < $modulus ; ++$x) {
118 96 100       261 if (substr $sieve, $x, 1) {
119 14 100       37 if (0 == $modulus % $x) {
120 3         8 for (my $i = $x; $i < $modulus; $i += $x) {
121 148         316 substr($sieve, $i, 1) = '0';
122             }
123 3         10 next;
124             }
125 11         22 substr($sieve, $x, 1) = '0';
126 11         17 my $e = $x;
127 11         25 for (2 .. $mult) {
128 121         226 $e = mulmod($e, $base, $modulus);
129 121         251 substr($sieve, $e, 1) = '0';
130             }
131 11         28 push @rot, $x;
132 11 100       36 last if $mult == @rot;
133             }
134             }
135 1         5 $rotators = \@rot;
136             }
137 9         32 $memo_rotators{$order} = $rotators;
138 9         26 push @memo_ro_orders, $order;
139 9         63 delete $memo_rotators{shift @memo_ro_orders}
140             while $_MAX_MEMO_COUNT < @memo_ro_orders;
141 9         50 return $rotators;
142             }
143              
144             # iterative rotator base generator, slow, but memory efficient
145             sub _sequential_rotators {
146 2     2   5 my ($order, $base, $exponent, $modulus) = @_;
147 2         7 my $n_planes = _n_planes($order, $exponent, $modulus);
148 2         5 my $mult = 3 * $exponent;
149 2         14 my @pf = map { $_->[0] } factor_exp($modulus);
  3         11  
150 2 100       8 pop @pf if $pf[-1] == $modulus;
151 2         4 my $mx = 0;
152 2         4 my $x = 0;
153             return sub {
154 22 100   22   1101 return 0 if $mx >= $n_planes;
155             ELEMENT:
156 20         34 while (1) {
157 71         106 ++$x;
158 71         121 foreach my $p (@pf) {
159 86 100       207 next ELEMENT if !($x % $p);
160             }
161 62         97 my $e = $x;
162 62         114 for (2 .. $mult) {
163 265         556 $e = mulmod($e, $base, $modulus);
164 265 100       612 next ELEMENT if $e < $x;
165             }
166 20         34 ++$mx;
167 20         58 return $x;
168             }
169 2         25 };
170             }
171              
172             # structured rotator base iterator, time and space efficient
173             sub _structured_rotators {
174 19     19   52 my ($modulus, $rotators) = @_;
175 19         54 my ($radices, $depths, $inverses) = @{$rotators};
  19         56  
176 19         58 my @index = (0) x @{$radices};
  19         114  
177 19         37 my $next = 1;
178             return sub {
179 183 100   183   2434 return 0 if !$next;
180 178         289 my $element = $next;
181 178         356 my $i = 0;
182 178         443 while ($i < @index) {
183 184 100       592 if (++$index[$i] < $depths->[$i]) {
184 164         546 $next = mulmod($next, $radices->[$i], $modulus);
185 164         530 return $element;
186             }
187             }
188             continue {
189 20         43 $index[$i] = 0;
190 20         71 $next = mulmod($next, $inverses->[$i], $modulus);
191 20         57 ++$i;
192             }
193 14         31 $next = 0;
194 14         37 return $element;
195 19         180 };
196             }
197              
198             # format a planar difference set space description, like '7^3 [2^6 5^2]'
199             sub _space_description {
200 21     21   46 my ($spc) = @_;
201 21         665 my $order = $spc->order;
202 21         819 my $mul_radix = $spc->mul_radix;
203 21         697 my $mul_depth = $spc->mul_depth;
204 21         301 my ($radices, $depths) = $spc->rotator_space;
205 21         44 my @space = map {; "$radices->[$_]^$depths->[$_]"} 0 .. $#{$radices};
  34         147  
  21         50  
206 21         171 return "$order: $mul_radix^$mul_depth [@space]";
207             }
208              
209             # integer exponentiation
210             sub _pow {
211 25     25   62 my ($base, $exponent) = @_;
212 25 100 100     166 return 0 if $base <= 0 || $exponent < 0;
213 23 100       144 return 0 if logint($base, 2) * $exponent >= _NATIVE_BITS;
214 17         34 my $power = 1;
215 17         86 while ($exponent) {
216 47 100       141 $power *= $base if 1 & $exponent;
217 47 100       145 $exponent >>= 1 and $base *= $base;
218             }
219 17         39 return $power;
220             }
221              
222             # check whether order is small enough and calculate modulus
223             sub _modulus {
224 137     137   309 my ($order) = @_;
225 137 100       416 if ($order > _MAX_SMALL_ORDER) {
226 1         225 croak "order $order too large for this platform\n";
227             }
228 136         373 return +($order + 1) * $order + 1;
229             }
230              
231             # calculate minimal equivalent of a factor
232             sub _min_factor {
233 617     617   1471 my ($factor, $base, $modulus) = @_;
234 617         1001 my $f0 = $factor;
235 617         1384 my $f = mulmod($f0, $base, $modulus);
236 617         1596 while ($f != $f0) {
237 2263 100       4648 $factor = $f if $f < $factor;
238 2263         5975 $f = mulmod($f, $base, $modulus);
239             }
240 617         1473 return $factor;
241             }
242              
243             # boolean whether an ordered strictly increasing list contains an element
244             sub _ol_contains {
245 312     312   642 my ($haystack, $needle) = @_;
246 312         526 my $lx = 0;
247 312         549 my $hx = $#{$haystack};
  312         614  
248 312         824 while ($lx <= $hx) {
249 1209         2054 my $x = ($lx + $hx) >> 1;
250 1209         4163 my $v = $haystack->[$x];
251 1209         1977 my $cmp = $needle <=> $v;
252 1209 100       2571 return !0 if !$cmp;
253 1179 100       2350 if ($cmp < 0) {
254 679         1544 $hx = $x - 1;
255             }
256             else {
257 500         1125 $lx = $x + 1;
258             }
259             }
260 282         1021 return !1;
261             }
262              
263             # calculate number of planes with memoization
264             sub _n_planes {
265 8     8   19 my ($order, $exponent, $modulus) = @_;
266 8 100       35 return $memo_n_planes{$order} if exists $memo_n_planes{$order};
267 4         34 my $n_planes = $memo_n_planes{$order} =
268             euler_phi($modulus) / (3 *$exponent);
269 4         11 push @memo_np_orders, $order;
270 4         15 delete $memo_n_planes{shift @memo_np_orders}
271             while $_MAX_MEMO_COUNT < @memo_np_orders;
272 4         18 return $n_planes;
273             }
274              
275             # arrange elements of a planar difference set in numerical order
276             # return arrayref of sorted list, start index, gap index
277             sub _sort_elements {
278 235     235   470 my $modulus = shift;
279 235         1167 my @elements = sort { $a <=> $b } @_;
  12225         20796  
280 235         504 my $xs = my $xl = $#elements;
281 235         598 my $xg = my $xh = 0;
282 235         544 my $lo = $elements[$xl] - $modulus;
283 235         526 my $hi = $elements[0];
284 235         478 my $mg = my $ml = my $d = $hi - $lo;
285 235         682 while ($xh < $#elements) {
286 3646         6509 $xl = $xh++;
287 3646         6722 ($lo, $hi) = ($hi, $elements[$xh]);
288 3646         5779 $d = $hi - $lo;
289 3646 100       9943 $ml = $d, $xs = $xl if $d < $ml;
290 3646 100       9900 $mg = $d, $xg = $xh if $d > $mg;
291             }
292 235 100       880 croak "duplicate element: $elements[$xs]" if !$ml;
293 234 100       849 croak 'delta 1 elements missing' if $ml > 1;
294 233         882 return (\@elements, $xs, $xg);
295             }
296              
297             # generate fill elements (0, 1 or 2 values)
298             sub _fill_elements {
299 201     201   553 my ($order, $modulus) = @_;
300 201         512 my $type = $order % 3;
301 201 100       561 return () if $type == 2;
302 186 100       506 return (0) if $type == 0;
303 156         332 my $m3 = $modulus / 3;
304 156         515 return ($m3, $m3 << 1);
305             }
306              
307             # recall elements or generate them, updating object attributes
308             sub _elements {
309 740     740   1612 my ($this) = @_;
310 740         1348 my $elements = $this->[_F_ELEMENTS];
311 740 100       2071 return $elements if $elements;
312             my ( $order, $base, $exponent, $modulus, $theta) =
313 197         454 @{$this}[_F_ORDER, _F_BASE, _F_EXPONENT, _F_MODULUS, _F_THETA];
  197         626  
314 197         470 my $mult = 3 * $exponent;
315 197         364 my @elem = ();
316 197         394 foreach my $e0 (@{$this->[_F_PRINC_ELEMS]}) {
  197         552  
317 294         518 my $e = $e0;
318 294         722 push @elem, addmod($e0, $theta, $modulus);
319 294         846 for (2 .. $mult) {
320 1440         6248 $e = mulmod($e, $base, $modulus);
321 1440         3667 push @elem, addmod($e, $theta, $modulus);
322             }
323             }
324 197         369 foreach my $e0 (@{$this->[_F_SUPPL_ELEMS]}) {
  197         601  
325 413         1074 push @elem, addmod($e0, $theta, $modulus);
326 413         930 my $e = mulmod($e0, $base, $modulus);
327 413         977 while ($e != $e0) {
328 1189         2503 push @elem, addmod($e, $theta, $modulus);
329 1189         3317 $e = mulmod($e, $base, $modulus);
330             }
331             }
332 197         550 foreach my $e0 (_fill_elements($order, $modulus)) {
333 334         918 push @elem, addmod($e0, $theta, $modulus);
334             }
335 197         601 ($elements) = @{$this}[_F_ELEMENTS, _F_X_START, _F_X_GAP] =
  197         654  
336             _sort_elements($modulus, @elem);
337 197         782 return $elements;
338             }
339              
340             # return data connection, creating it if not yet open
341             sub _data {
342 148 100   148   1057 if (!defined $current_data) {
343 5         55 $current_data = Math::DifferenceSet::Planar::Data->new;
344             }
345 148         2002 return $current_data;
346             }
347              
348             # identify a plane by its rotator value with respect to a given plane
349             # (setting its lambda value if possible)
350             sub _log {
351 27     27   59 my ($this, $ref) = @_;
352 27         59 my $modulus = $this->[_F_MODULUS];
353 27         75 my $delta = -$this->[_F_THETA];
354 27         48 my $ref_lambda = $ref->[_F_LAMBDA];
355 27         48 my $factor = 0;
356 27         79 my %this_e = ();
357 27         83 my $this_elements = $this->_elements;
358 27         53 foreach my $e (@{$this_elements}) {
  27         94  
359 154 100       666 $this_e{$delta? addmod($e, $delta, $modulus): $e} = 1;
360             }
361 27 100       57 if (@{$ref->[_F_PRINC_ELEMS]}) {
  27         77  
362 15         64 my $inv_r = invmod($ref->[_F_PRINC_ELEMS]->[0], $modulus);
363             ELEM:
364 15         26 foreach my $o (@{$this->[_F_PRINC_ELEMS]}) {
  15         47  
365 15         129 my $ro = mulmod($inv_r, $o, $modulus);
366 15         54 foreach my $e (@{$ref->[_F_PRINC_ELEMS]}) {
  15         76  
367 16 50       91 next ELEM if !exists $this_e{ mulmod($e, $ro, $modulus) };
368             }
369 15         29 $factor = $ro;
370 15         52 last;
371             }
372             }
373             else {
374 12         79 my $ri = $this->iterate_rotators;
375             ROT:
376 12         34 while (my $ro = $ri->()) {
377 21         35 foreach my $e (@{$ref->[_F_SUPPL_ELEMS]}) {
  21         141  
378 21 100       308 next ROT if !exists $this_e{ mulmod($e, $ro, $modulus) };
379             }
380 12         25 $factor = $ro;
381 12         98 last;
382             }
383             }
384 27 50       114 croak 'unaligned sets' if !$factor;
385 27         60 my $base = $this->[_F_BASE];
386 27 100       73 if ($ref_lambda) {
387 26         98 $this->[_F_LAMBDA] =
388             _min_factor(
389             mulmod($ref_lambda, $factor, $modulus), $base, $modulus
390             );
391             }
392 27         131 return $factor;
393             }
394              
395             # $factor = _find_factor($ds1, $ds2);
396             sub _find_factor {
397 40     40   79 my ($this, $that) = @_;
398 40 100       140 return 1 if $this == $that;
399 38         130 my $order = $this->order;
400 38 100       118 croak 'sets of same size expected' if $order != $that->order;
401 36         72 my $log_this = $this->[_F_LAMBDA];
402 36         70 my $log_that = $that->[_F_LAMBDA];
403 36 100       117 if (!$log_that) {
    100          
404 26         86 $log_that = _log($that, $this);
405             }
406             elsif (!$log_this) {
407 1         6 $log_this = _log($this, $that);
408             }
409 36 100       138 return $log_this? divmod($log_that, $log_this, $this->modulus): $log_that;
410             }
411              
412             # translation amount between a multiple of a set and another set
413             sub _delta_f {
414 56     56   193 my ($this, $factor, $that) = @_;
415 56         124 my $modulus = $this->modulus;
416 56         228 my ($x) = $this->find_delta( invmod($factor, $modulus) );
417 56         166 my $elements = $that->_elements;
418 56         124 my $s = $elements->[$that->[_F_X_START]];
419 56         262 return addmod($s, -mulmod($x, $factor, $modulus), $modulus);
420             }
421              
422             # $bool = _is_mult($factor, $base, $mult, $modulus);
423             sub _is_mult {
424 187     187   534 my ($factor, $base, $mult, $modulus) = @_;
425 187 100       518 return !0 if $factor == $base;
426 184         328 my $p = $base;
427 184         610 for (2 .. $mult-1) {
428 808         1644 $p = mulmod($p, $base, $modulus);
429 808 100       1969 return !0 if $factor == $p;
430             }
431 183         552 return !1;
432             }
433              
434             # ($order, $base, $exponent, $key) = _order_from_params(@_);
435             sub _order_from_params {
436 75     75   197 my ($order, $exponent) = @_;
437 75         157 my $base = undef;
438 75 100       223 if (defined $exponent) {
439 16         27 $base = $order;
440 16 100       632 croak "order base $base is not a prime" if !is_prime($base);
441 13         37 $order = _pow($base, $exponent);
442 13 100 100     996 croak "order $base ** $exponent too large for this platform"
443             if !$order || $order > _MAX_SMALL_ORDER;
444             }
445             else {
446 59 100       331 croak "order $order too large for this platform"
447             if $order > _MAX_SMALL_ORDER;
448 58         304 $exponent = is_prime_power($order, \$base);
449 58 100       485 croak "order $order is not a prime power" if !$exponent;
450             }
451 63         249 return ($order, $base, $exponent);
452             }
453              
454             # ($this, $order, $base, $exponent, $modulus) = _full_params(@_);
455             sub _full_params {
456 38     38   96 my $this = shift;
457 38         88 my ($order, $base, $exponent, $modulus);
458 38 100       111 if (@_) {
459 6         38 ($order, $base, $exponent) = _order_from_params(@_);
460 2         8 $modulus = _modulus($order);
461             }
462             else {
463 32 100       233 croak 'parameters expected if called as a class method' if !ref $this;
464             ( $order, $base, $exponent, $modulus) =
465 31         71 @{$this}[_F_ORDER, _F_BASE, _F_EXPONENT, _F_MODULUS];
  31         139  
466             }
467 33         259 return ($this, $order, $base, $exponent, $modulus);
468             }
469              
470             # $bool = $class->_known_ref('ref_std', $base, $exponent);
471             sub _known_ref {
472 7     7   21 my ($class, $attribute, $base, $exponent) = @_;
473 7 100       23 my $order = defined($exponent)? _pow($base, $exponent): $base;
474 7 100 100     34 return !1 if !$order || $order > $class->_data->max_order;
475 5         13654 my $pds = $class->_data->get($order, 'base', $attribute);
476             return
477 5   100     10643 $pds && (!defined($exponent) || $base == $pds->base) &&
478             $pds->$attribute != 0;
479             }
480              
481             # identity transformation
482 1     1   10 sub _no_change { $_[0] }
483              
484             # $it = $class->_iterate_refs('ref_std', 'zeta_canonize', 10, 20);
485             sub _iterate_refs {
486 6     6   22 my ($class, $attribute, $transform, @minmax) = @_;
487 6         24 my $dit = $class->_data->iterate_refs($attribute, @minmax);
488             return sub {
489 7     7   1420 my $pds = $dit->();
490 7 100       21944 return undef if !$pds;
491 1         3 my $this = eval { $class->_from_pds($pds->order, $pds) };
  1         42  
492 1   33     31 return $this && $this->multiply($pds->$attribute)->$transform;
493 6         87 };
494             }
495              
496             # $ds = Math::DifferenceSet::Planar->_from_pds($order, $pds);
497             sub _from_pds {
498 79     79   678 my ($class, $order, $pds) = @_;
499 79         387 my $modulus = _modulus($order);
500 79         2894 my $base = $pds->base;
501 79 100       1607 my $exponent = $base == $order? 1: logint($order, $base);
502 79         334 my $main = $pds->main_elements;
503 79         228 my (@princ, @suppl) = ();
504 79         131 foreach my $e (@{$main}) {
  79         266  
505 208 100       772 if (gcd($modulus, $e) == 1) {
506 139         351 push @princ, $e;
507             }
508             else {
509 69         156 push @suppl, $e;
510             }
511             }
512 79         202 my $lambda = undef;
513 79 100       2232 if (my $log = $pds->ref_std) {
514 77 100       1212 $lambda =
515             $log == 1? 1: _min_factor(invmod($log, $modulus), $base, $modulus);
516             }
517              
518 79         733 return bless [
519             $order,
520             $base,
521             $exponent,
522             $modulus,
523             0, # zeta
524             0, # eta
525             0, # theta
526             \@princ,
527             \@suppl,
528             $lambda,
529             undef, # elements
530             undef, # index_start
531             undef, # index_gap
532             ], $class;
533             }
534              
535             # ----- class methods -----
536              
537             sub list_databases {
538 1     1 1 1210 return Math::DifferenceSet::Planar::Data->list_databases;
539             }
540              
541             sub set_database {
542 6     6 1 6658 my $class = shift;
543 6         43 $current_data = Math::DifferenceSet::Planar::Data->new(@_);
544 3         1771 return $class->available_count;
545             }
546              
547             # print "ok" if Math::DifferenceSet::Planar->available(9);
548             # print "ok" if Math::DifferenceSet::Planar->available(3, 2);
549             sub available {
550 15     15 1 1247529 my ($class, $base, $exponent) = @_;
551 15 100       81 my $order = defined($exponent)? _pow($base, $exponent): $base;
552 15 100 100     108 return !1 if !$order || $order > $class->_data->max_order;
553 6         20086 my $pds = $class->_data->get($order, 'base');
554 6   66     17496 return !!$pds && (!defined($exponent) || $base == $pds->base);
555             }
556              
557             # print "ok" if Math::DifferenceSet::Planar->known_space(9);
558             sub known_space {
559 4     4 1 3641 my ($class, $order) = @_;
560 4 100 100     35 return 0 if $order <= 0 || $order > $class->_data->sp_max_order;
561 2         5886 my $spc = $class->_data->get_space($order);
562 2 100       5213 return 0 if !$spc;
563 1         65 my ($rad) = $spc->rotator_space;
564 1         4 return 0 + @{$rad};
  1         8  
565             }
566              
567             # $desc = Math::DifferenceSet::Planar->known_space_desc(9);
568             sub known_space_desc {
569 4     4 1 7156 my ($class, $order) = @_;
570 4 100 100     27 return undef if $order <= 0 || $order > $class->_data->sp_max_order;
571 2         6923 my $spc = $class->_data->get_space($order);
572 2 100       5482 return undef if !$spc;
573 1         33 return _space_description($spc);
574             }
575              
576             # $ds = Math::DifferenceSet::Planar->new(9);
577             # $ds = Math::DifferenceSet::Planar->new(3, 2);
578             sub new {
579 47     47 1 295454 my $class = shift;
580 47         173 my ($order, $base, $exponent) = _order_from_params(@_);
581 43         163 my $pds = $class->_data->get($order);
582 43 100       133527 if (!$pds) {
583 1         39 my $key = join q[, ], @_;
584 1         180 croak "PDS($key) not available";
585             }
586 42         1578 return $class->_from_pds($order, $pds);
587             }
588              
589             sub lex_reference {
590 6     6 1 3889 my $class = shift;
591 6         23 my ($order, $base, $exponent) = _order_from_params(@_);
592 4         16 my $pds = $class->_data->get($order);
593 4 100 100     17526 if ($pds && (my $lambda = $pds->ref_lex)) {
594 2         177 return $class->_from_pds($order, $pds)->multiply($lambda)->lex_canonize;
595             }
596 2         135 return undef;
597             }
598              
599             sub gap_reference {
600 5     5 1 8504 my $class = shift;
601 5         17 my ($order, $base, $exponent) = _order_from_params(@_);
602 4         14 my $pds = $class->_data->get($order);
603 4 100 100     8935 if ($pds && (my $lambda = $pds->ref_gap)) {
604             return
605 2         169 $class->_from_pds($order, $pds)->multiply($lambda)->gap_canonize;
606             }
607 2         144 return undef;
608             }
609              
610             sub std_reference {
611 11     11 1 3766 my $class = shift;
612 11         37 my ($order, $base, $exponent) = _order_from_params(@_);
613 10         34 my $pds = $class->_data->get($order);
614 10 100 100     24169 if ($pds && (my $lambda = $pds->ref_std)) {
615             return
616 7         567 $class->_from_pds($order, $pds)->zeta_canonize->multiply($lambda);
617             }
618 3         117 return undef;
619             }
620              
621             # $ds = Math::DifferenceSet::Planar->from_elements_fast(
622             # 0, 1, 3, 9, 27, 49, 56, 61, 77, 81
623             # );
624             sub from_elements_fast {
625 42     42 1 3030 my $class = shift;
626 42         101 my $order = $#_;
627 42         94 my ($base, $exponent);
628 42 100       617 $exponent = is_prime_power($order, \$base)
629             or croak "this implementation cannot handle order $order";
630 40         146 my $modulus = _modulus($order);
631 40 100       122 if (grep { $_ < 0 || $modulus <= $_ } @_) {
  223 100       807  
632 2         5 my $max = $modulus - 1;
633 2         385 croak "element values inside range 0..$max expected";
634             }
635 38         126 my ($elements, $index_start, $index_gap) = _sort_elements($modulus, @_);
636 36         88 my $n_mult = 3 * $exponent;
637              
638             # find zeta and theta
639 36         79 my ($lx, $ux, $c) = (0, 0, 0);
640 36         106 my ($e0, $e2, $e3) = @{$elements}[$index_start, 0, 0];
  36         137  
641 36         110 my $is_m3 = $order % 3 == 1;
642 36 100       121 my $de = $is_m3? $modulus/3: $order + 1;
643 36         67 my $bogus = 0;
644 36         118 while ($c != $de) {
645 292 100       613 if ($c < $de) {
646 181 100       406 $ux = 0 if ++$ux > $order;
647 181         358 $e3 = $elements->[$ux];
648 181 50       464 $bogus = 1, last if $ux == $lx;
649             }
650             else {
651 111 100       241 $bogus = 1, last if ++$lx > $order;
652 109         192 $e2 = $elements->[$lx];
653             }
654 290 100       748 $c = $e3 < $e2? $modulus + $e3 - $e2: $e3 - $e2;
655             }
656 36 100       614 croak "delta $de elements missing\n" if $bogus;
657 34         75 my ($zeta, $theta);
658 34 100       85 if ($is_m3) {
659 18         85 $theta = addmod($e3, $de, $modulus);
660 18         97 $zeta = mulmod($theta, $order - 1, $modulus);
661             }
662             else {
663 16         118 $zeta = addmod(mulmod($e3, $order, $modulus), -$e0, $modulus);
664 16   66     95 $theta = $zeta && divmod($zeta, $order - 1, $modulus);
665             }
666              
667             my @elems =
668 34         71 sort { $a <=> $b } map { addmod($_, -$theta, $modulus) } @{$elements};
  300         549  
  189         524  
  34         94  
669 34         78 my @princ = ();
670 34         69 my @suppl = ();
671 34         73 my %todo = map {($_ => 1)} @elems;
  189         651  
672 34         168 foreach my $start (@elems) {
673 188 100       607 next if !exists $todo{$start};
674 66         153 delete $todo{$start};
675 66         171 my $this = mulmod($start, $base, $modulus);
676 66         131 my $count = 1;
677 66         150 while ($this != $start) {
678 124 100       333 if (!defined delete $todo{$this}) {
679 1         155 croak
680             "bogus set: prime divisor $base of order $order " .
681             'is not a multiplier';
682             }
683 123         209 ++$count;
684 123         416 $this = mulmod($this, $base, $modulus);
685             }
686 65 100       209 if ($count == $n_mult) {
    100          
687 20 100       96 if (gcd($start, $modulus) == 1) {
688 19         70 push @princ, $start;
689             }
690             else {
691 1         4 push @suppl, $start;
692             }
693             }
694             elsif ($count >= 3) {
695 18         88 push @suppl, $start;
696             }
697             }
698 33 100       181 my $eta = $exponent == 1? $zeta: undef;
699              
700 33         316 return bless [
701             $order,
702             $base,
703             $exponent,
704             $modulus,
705             $zeta,
706             $eta,
707             $theta,
708             \@princ,
709             \@suppl,
710             undef, # lambda
711             $elements,
712             $index_start,
713             $index_gap,
714             ], $class;
715             }
716              
717             # $ds = Math::DifferenceSet::Planar->from_elements(
718             # 0, 1, 3, 9, 27, 49, 56, 61, 77, 81
719             # );
720             sub from_elements {
721 33     33 1 482700 my $class = shift;
722 33         128 my $this = $class->from_elements_fast(@_);
723 25 50       83 if(my $ref = eval { $class->new(@_ - 1) }) {
  25         117  
724 25 100       733 eval {
725 25         105 my ($factor, $delta) = $ref->find_linear_map($this);
726 25         85 !$ref->multiply($factor)->translate($delta)->compare($this)
727             } or
728             croak 'apparently not a planar difference set';
729             }
730 24         256 return $this;
731             }
732              
733             # $ds = Math::DifferenceSet::Planar->from_lambda($order, $lambda);
734             # $ds = Math::DifferenceSet::Planar->from_lambda($order, $lambda, $theta);
735             sub from_lambda {
736 10     10 1 13661 my ($class, $order, $lambda, $theta) = @_;
737 10         24 my ($base, $exponent);
738 10         76 $exponent = is_prime_power($order, \$base);
739 10 100       223 croak "this implementation cannot handle order $order" if !$exponent;
740 9         30 my $modulus = _modulus($order);
741 8 100       297 croak "impossible lambda value $lambda" if gcd($modulus, $lambda) != 1;
742 7         23 my $l = mulmod($lambda, $base, $modulus);
743 7         21 while ($l != $lambda) {
744 13 100       181 croak "non-canonical lambda value $lambda" if $l < $lambda;
745 12         36 $l = mulmod($l, $base, $modulus);
746             }
747 6 100 100     368 croak "non-canonical theta value $theta"
      100        
748             if $theta && ($theta < 0 || $modulus <= $theta);
749 4         17 my $ref = $class->std_reference($order);
750 4 100       225 croak "reference set of order $order not available" if !$ref;
751 3         9 my $this = $ref->multiply($lambda);
752 3 100       19 return $theta? $this->translate($theta): $this;
753             }
754              
755             # $bool = Math::DifferenceSet::Planar->verify_elements(
756             # 0, 1, 3, 9, 27, 49, 56, 61, 77, 81
757             # );
758             sub verify_elements {
759 8     8 1 6722 my ($class, @elements) = @_;
760 8         23 my $order = $#elements;
761 8 100       30 return undef if $order <= 1;
762 7         22 my $modulus = _modulus($order);
763 7         19 my $median = ($modulus - 1) / 2;
764 7         21 my $seen = '0' x $median;
765 7         20 foreach my $r1 (@elements) {
766 19 100 100     121 return undef if $r1 < 0 || $modulus <= $r1 || $r1 != int $r1;
      100        
767 16         29 foreach my $r2 (@elements) {
768 28 100       66 last if $r1 == $r2;
769 13 100       32 my $d = $r1 < $r2? $r2 - $r1: $modulus + $r2 - $r1;
770 13 100       33 $d = $modulus - $d if $d > $median;
771 13 100       73 return !1 if substr($seen, $d-1, 1)++;
772             }
773             }
774 3         18 return $median == $seen =~ tr/1//;
775             }
776              
777             # $it1 = Math::DifferenceSet::Planar->iterate_available_sets;
778             # $it2 = Math::DifferenceSet::Planar->iterate_available_sets(10, 20);
779             # while (my $ds = $it2->()) {
780             # ...
781             # }
782             sub iterate_available_sets {
783 5     5 1 10624 my ($class, @minmax) = @_;
784 5         22 my $dit = $class->_data->iterate(@minmax);
785             return sub {
786 28     28   690 my $pds = $dit->();
787 28 100       20807 return undef if !$pds;
788 25         962 my $this = $class->_from_pds($pds->order, $pds);
789 25         118 return $this;
790 5         63 };
791             }
792              
793 1     1 1 3923 sub available_min_order { $_[0]->_data->min_order }
794 1     1 1 3287 sub available_max_order { $_[0]->_data->max_order }
795 4     4 1 1158 sub available_count { $_[0]->_data->count }
796              
797             sub known_std_ref {
798 5     5 1 1345 my $class = shift;
799 5         18 return $class->_known_ref('ref_std', @_);
800             }
801              
802             sub known_lex_ref {
803 1     1 1 6263 my $class = shift;
804 1         7 return $class->_known_ref('ref_lex', @_);
805             }
806              
807             sub known_gap_ref {
808 1     1 1 6196 my $class = shift;
809 1         6 return $class->_known_ref('ref_gap', @_);
810             }
811              
812             sub iterate_known_std_refs {
813 4     4 1 7895 my ($class, @minmax) = @_;
814 4         20 return $class->_iterate_refs('ref_std', '_no_change', @minmax);
815             }
816              
817             sub iterate_known_lex_refs {
818 1     1 1 1075 my ($class, @minmax) = @_;
819 1         7 return $class->_iterate_refs('ref_lex', 'lex_canonize', @minmax);
820             }
821              
822             sub iterate_known_gap_refs {
823 1     1 1 1118 my ($class, @minmax) = @_;
824 1         6 return $class->_iterate_refs('ref_gap', 'gap_canonize', @minmax);
825             }
826              
827 2     2 1 2267 sub known_std_ref_min_order { $_[0]->_data->ref_min_order('ref_std') }
828 2     2 1 11596 sub known_std_ref_max_order { $_[0]->_data->ref_max_order('ref_std') }
829 2     2 1 10986 sub known_std_ref_count { $_[0]->_data->ref_count( 'ref_std') }
830 2     2 1 559 sub known_lex_ref_min_order { $_[0]->_data->ref_min_order('ref_lex') }
831 2     2 1 11232 sub known_lex_ref_max_order { $_[0]->_data->ref_max_order('ref_lex') }
832 2     2 1 10981 sub known_lex_ref_count { $_[0]->_data->ref_count( 'ref_lex') }
833 2     2 1 6227 sub known_gap_ref_min_order { $_[0]->_data->ref_min_order('ref_gap') }
834 2     2 1 10956 sub known_gap_ref_max_order { $_[0]->_data->ref_max_order('ref_gap') }
835 2     2 1 11180 sub known_gap_ref_count { $_[0]->_data->ref_count( 'ref_gap') }
836              
837             # $min = Math::DifferenceSet::Planar->known_space_min_order;
838 1     1 1 6732 sub known_space_min_order { $_[0]->_data->sp_min_order }
839              
840             # $max = Math::DifferenceSet::Planar->known_space_max_order;
841 1     1 1 5201 sub known_space_max_order { $_[0]->_data->sp_max_order }
842              
843             # $count = Math::DifferenceSet::Planar->known_space_count;
844 1     1 1 3226 sub known_space_count { $_[0]->_data->sp_count }
845              
846             # $it3 = Math::DifferenceSet::Planar->iterate_known_spaces;
847             # $it3 = Math::DifferenceSet::Planar->iterate_known_spaces(10,20);
848             # while (my $spc = $it3->()) {
849             # print "$spc\n";
850             # }
851             sub iterate_known_spaces {
852 4     4 1 8477 my ($class, @minmax) = @_;
853 4         16 my $dit = $class->_data->iterate_spaces(@minmax);
854             return sub {
855 23     23   1336 my $spc = $dit->();
856 23 100       15702 return undef if !$spc;
857 20         61 return _space_description($spc);
858 4         41 };
859             }
860              
861             # ----- object methods -----
862              
863             # $o = $ds->order;
864             # $p = $ds->order_base;
865             # $n = $ds->order_exponent;
866             # $m = $ds->modulus;
867             # $z = $ds->zeta;
868             # $t = $ds->theta;
869             # $l = $ds->lambda;
870 379     379 1 3395 sub order { $_[0]->[_F_ORDER ] }
871 1     1 1 55 sub order_base { $_[0]->[_F_BASE ] }
872 1     1 1 5 sub order_exponent { $_[0]->[_F_EXPONENT] }
873 170     170 1 910 sub modulus { $_[0]->[_F_MODULUS ] }
874 6     6 1 1795 sub zeta { $_[0]->[_F_ZETA ] }
875 5     5 1 53 sub theta { $_[0]->[_F_THETA ] }
876 4     4 1 2528 sub lambda { $_[0]->[_F_LAMBDA ] }
877              
878             sub min_element {
879 1     1 1 1106 my ($this) = @_;
880 1         5 my $elements = $this->_elements;
881 1         7 return $elements->[0];
882             }
883              
884             sub max_element {
885 1     1 1 4 my ($this) = @_;
886 1         6 my $elements = $this->_elements;
887 1         8 return $elements->[-1];
888             }
889              
890             sub n_planes {
891 3     3 1 98 my ($this, $order, $base, $exponent, $modulus) = _full_params(@_);
892 3         15 return _n_planes($order, $exponent, $modulus)
893             }
894              
895             # @e = $ds->elements;
896             sub elements {
897 40     40 1 24903 my ($this) = @_;
898 40         169 my $elements = $this->_elements;
899 40 100       124 return 0+@{$elements} if !wantarray;
  1         4  
900 39         76 my $x_start = $this->[_F_X_START];
901 39         80 return @{$elements}[$x_start .. $#{$elements}, 0 .. $x_start-1];
  39         227  
  39         129  
902             }
903              
904             # $e0 = $ds->element(0);
905             sub element {
906 7     7 1 19 my ($this, $index) = @_;
907 7         34 my $n_elems = $this->[_F_ORDER] + 1;
908 7 100 100     56 return undef if $index < -$n_elems || $n_elems <= $index;
909 5         14 my $elements = $this->_elements;
910 5         12 my $x_eff = $this->[_F_X_START] + $index;
911 5 100       15 $x_eff -= $n_elems if $x_eff >= $n_elems;
912 5         23 return $elements->[$x_eff];
913             }
914              
915             # @e = $ds->elements_sorted;
916             sub elements_sorted {
917 5     5 1 1883 my ($this) = @_;
918 5         22 my $elements = $this->_elements;
919 5         12 return @{$elements};
  5         25  
920             }
921              
922             # $e0 = $ds->element_sorted(0);
923             sub element_sorted {
924 2     2 1 1272 my ($this, $index) = @_;
925 2         11 my $elements = $this->_elements;
926 2         15 return $elements->[$index];
927             }
928              
929             # $ds1 = $ds->translate(1);
930             sub translate {
931 166     166 1 3736 my ($this, $delta) = @_;
932 166         370 my $modulus = $this->[_F_MODULUS];
933 166         337 $delta %= $modulus;
934 166 100       555 return $this if !$delta;
935 127         1493 my $that = bless [@{$this}], ref $this;
  127         631  
936 127         280 my $elements = $this->[_F_ELEMENTS];
937 127 100       371 if ($elements) {
938 109         207 my $lim = $modulus - $delta;
939 109         235 my @elems = my @wrap = ();
940 109         179 foreach my $e (@{$elements}) {
  109         280  
941 2001 100       3697 if ($e < $lim) {
942 939         1782 push @wrap, $e + $delta;
943             }
944             else {
945 1062         3097 push @elems, $e - $lim;
946             }
947             }
948 109         669 my $dx = @elems;
949 109         377 push @elems, @wrap;
950 109         506 my $x_start = addmod($this->[_F_X_START], $dx, 0+@elems);
951 109         293 my $x_gap = addmod($this->[_F_X_GAP], $dx, 0+@elems);
952 109         246 $that->[_F_ELEMENTS] = \@elems;
953 109         217 $that->[_F_X_START] = $x_start;
954 109         331 $that->[_F_X_GAP] = $x_gap;
955             }
956 127         248 my ($order, $zeta, $theta) = @{$this}[_F_ORDER, _F_ZETA, _F_THETA];
  127         411  
957 127         501 $that->[_F_ZETA] =
958             addmod($zeta, mulmod($delta, $order-1, $modulus), $modulus);
959 127         343 $that->[_F_THETA] = addmod($theta, $delta, $modulus);
960 127 100       350 if (defined (my $e = $that->[_F_ETA])) {
961 111         219 my $base = $this->[_F_BASE];
962 111         366 $that->[_F_ETA] =
963             addmod($e, mulmod($delta, $base-1, $modulus), $modulus);
964             }
965 127         697 return $that;
966             }
967              
968             # $ds2 = $ds->canonize;
969             # $ds2 = $ds->lex_canonize;
970             sub lex_canonize {
971 82     82 1 2446 my ($this) = @_;
972 82         236 my $elements = $this->_elements;
973 82         364 return $this->translate(- $elements->[$this->[_F_X_START]]);
974             }
975              
976             # $ds2 = $ds->gap_canonize;
977             sub gap_canonize {
978 4     4 1 3098 my ($this) = @_;
979 4         14 my $elements = $this->_elements;
980 4         51 return $this->translate(- $elements->[$this->[_F_X_GAP]]);
981             }
982              
983             # $ds2 = $ds->zeta_canonize;
984             sub zeta_canonize {
985 19     19 1 4012 my ($this) = @_;
986 19         84 return $this->translate(- $this->[_F_THETA]);
987             }
988              
989             # $it = $ds->iterate_rotators;
990             # while (my $m = $it->()) {
991             # ...
992             # }
993             sub iterate_rotators {
994 23     23 1 1143 my ($this, $order, $base, $exponent, $modulus) = _full_params(@_);
995 23         98 my $rotators = $this->_rotators($order, $base, $exponent, $modulus);
996 23 100       218 return _sequential_rotators($order, $base, $exponent, $modulus)
997             if !$rotators;
998 21 100       149 return _structured_rotators($modulus, $rotators)
999             if 'ARRAY' eq ref $rotators->[0];
1000 2         4 my $mx = 0;
1001 2 100   14   16 return sub { $mx < @{$rotators}? $rotators->[$mx++]: 0 };
  14         2112  
  14         46  
1002             }
1003              
1004             # $it = $ds->iterate_planes;
1005             # while (my $ds = $it->()) {
1006             # ...
1007             # }
1008             sub iterate_planes {
1009 2     2 1 2974 my ($this) = @_;
1010 2         9 my $r_it = $this->iterate_rotators;
1011             return sub {
1012 74     74   1680 my $r = $r_it->();
1013 74 100       286 return $r? $this->multiply($r)->lex_canonize: undef;
1014 2         18 };
1015             }
1016              
1017             sub iterate_planes_zc {
1018 2     2 1 1871 my ($this) = @_;
1019 2         9 my $ref = $this->zeta_canonize;
1020 2         9 my $r_it = $ref->iterate_rotators;
1021             return sub {
1022 74     74   1564 my $r = $r_it->();
1023 74 100       299 return $r? $ref->multiply($r): undef;
1024 2         15 };
1025             }
1026              
1027             sub iterate_principal_planes {
1028 1     1 1 1259 my ($this) = @_;
1029 1         6 my $ref = $this->zeta_canonize;
1030             my ( $princ_elems, $modulus) =
1031 1         4 @{$ref}[_F_PRINC_ELEMS, _F_MODULUS];
  1         3  
1032 1         3 my $pex = 0;
1033             return sub {
1034 3 100   3   21 return undef if $pex >= @{$princ_elems};
  3         12  
1035 2         12 my $r = invmod($princ_elems->[$pex++], $modulus);
1036 2         9 return $ref->multiply($r)->lex_canonize;
1037 1         17 };
1038             }
1039              
1040             sub iterate_principal_planes_zc {
1041 1     1 1 28 my ($this) = @_;
1042 1         5 my $ref = $this->zeta_canonize;
1043             my ( $princ_elems, $modulus) =
1044 1         3 @{$ref}[_F_PRINC_ELEMS, _F_MODULUS];
  1         4  
1045 1         3 my $pex = 0;
1046             return sub {
1047 3 100   3   982 return undef if $pex >= @{$princ_elems};
  3         13  
1048 2         11 my $r = invmod($princ_elems->[$pex++], $modulus);
1049 2         8 return $ref->multiply($r);
1050 1         17 };
1051             }
1052              
1053             # @pm = $ds->multipliers;
1054             sub multipliers {
1055 12     12 1 11402 my ($this, $order, $base, $exponent, $modulus) = _full_params(@_);
1056 7         20 my $n_mult = 3 * $exponent;
1057 7 100       25 return $n_mult if !wantarray;
1058 6         15 my @mult = (1, $base);
1059 6         13 my $x = $base;
1060 6         18 while (@mult < $n_mult) {
1061 27         61 $x = mulmod($x, $base, $modulus);
1062 27         64 push @mult, $x;
1063             }
1064 6         28 return @mult;
1065             }
1066              
1067             # $ds3 = $ds->multiply($m);
1068             sub multiply {
1069 220     220 1 20292 my ($this, $factor) = @_;
1070             my ( $order, $base, $exponent, $modulus) =
1071 220         409 @{$this}[_F_ORDER, _F_BASE, _F_EXPONENT, _F_MODULUS];
  220         665  
1072 220         551 $factor %= $modulus;
1073 220 100       672 return $this if 1 == $factor;
1074 188 100       943 croak "factor $factor is not coprime to modulus"
1075             if gcd($modulus, $factor) != 1;
1076 187         392 my $theta1 = $this->[_F_THETA];
1077 187   66     539 my $theta = $theta1 && mulmod($theta1, $factor, $modulus);
1078 187         386 my $mult = 3 * $exponent;
1079 187 100       882 return $this->translate($theta - $theta1) if
1080             _is_mult($factor, $base, $mult, $modulus);
1081             my ( $zeta, $eta, $lambda) =
1082 183         354 @{$this}[_F_ZETA, _F_ETA, _F_LAMBDA];
  183         527  
1083 183   66     503 $zeta &&= mulmod($zeta, $factor, $modulus);
1084 183   66     485 $eta &&= mulmod($eta, $factor, $modulus);
1085 183         340 my @princ = ();
1086 183         342 my @suppl = ();
1087 183         319 foreach my $e (@{$this->[_F_PRINC_ELEMS]}) {
  183         504  
1088 300         742 my $p0 = my $p = mulmod($e, $factor, $modulus);
1089 300         681 for (2 .. $mult) {
1090 1545         3035 $p = mulmod($p, $base, $modulus);
1091 1545 100       3582 $p0 = $p if $p0 > $p;
1092             }
1093 300         776 push @princ, $p0;
1094             }
1095 183         753 @princ = sort { $a <=> $b } @princ;
  136         433  
1096 183         298 foreach my $e (@{$this->[_F_SUPPL_ELEMS]}) {
  183         530  
1097 406         1191 push @suppl,
1098             _min_factor(mulmod($e, $factor, $modulus), $base, $modulus);
1099             }
1100 183         517 @suppl = sort { $a <=> $b } @suppl;
  344         727  
1101 183 100       478 if ($lambda) {
1102 182         542 $lambda =
1103             _min_factor(mulmod($lambda, $factor, $modulus), $base, $modulus);
1104             }
1105 183         1443 return bless [
1106             $order,
1107             $base,
1108             $exponent,
1109             $modulus,
1110             $zeta,
1111             $eta,
1112             $theta,
1113             \@princ,
1114             \@suppl,
1115             $lambda,
1116             undef, # elements
1117             undef, # index_start
1118             undef, # index_gap
1119             ], ref $this;
1120             }
1121              
1122             # ($e1, $e2) = $ds->find_delta($delta);
1123             sub find_delta {
1124 68     68 1 3466 my ($this, $delta) = @_;
1125 68         173 my $order = $this->order;
1126 68         153 my $modulus = $this->modulus;
1127 68         178 my $elements = $this->_elements;
1128 68         201 my $de = $delta % $modulus;
1129 68         154 my $up = $de + $de < $modulus;
1130 68 100       190 $de = $modulus - $de if !$up;
1131 68         174 my ($lx, $ux, $c) = (0, 0, 0);
1132 68         114 my ($le, $ue) = @{$elements}[0, 0];
  68         168  
1133 68         129 my $bogus = 0;
1134 68         200 while ($c != $de) {
1135 413 100       829 if ($c < $de) {
1136 285 100       641 if(++$ux > $order) {
1137 11         21 $ux = 0;
1138             }
1139 285         550 $ue = $elements->[$ux];
1140 285 50       558 $bogus = 1, last if $ux == $lx;
1141             }
1142             else {
1143 128 50       266 $bogus = 1, last if ++$lx > $order;
1144 128         202 $le = $elements->[$lx];
1145             }
1146 413 100       1009 $c = $ue < $le? $modulus + $ue - $le: $ue - $le;
1147             }
1148 68 50       161 croak "bogus set: delta not found: $delta (mod $modulus)" if $bogus;
1149 68 100       371 return $up? ($le, $ue): ($ue, $le);
1150             }
1151              
1152             # $e0 = $ds->start_element;
1153             sub start_element {
1154 3     3 1 860 my ($this) = @_;
1155 3         9 my $elements = $this->_elements;
1156 3         15 return $elements->[$this->[_F_X_START]];
1157             }
1158              
1159             # ($e1, $e2) = $ds->peak_elements
1160             sub peak_elements {
1161 2     2 1 1324 my ($this) = @_;
1162 2         8 return $this->find_delta($this->modulus >> 1);
1163             }
1164              
1165             # ($e1, $e2, $delta) = $ds->largest_gap;
1166             sub largest_gap {
1167 4     4 1 1027 my ($this) = @_;
1168 4         13 my $elements = $this->_elements;
1169 4         8 my $x2 = $this->[_F_X_GAP];
1170 4         10 my ($e1, $e2) = @{$elements}[$x2 - 1, $x2];
  4         9  
1171 4 100       15 my $delta = $x2? $e2 - $e1: $this->modulus + $e2 - $e1;
1172 4         14 return ($e1, $e2, $delta);
1173             }
1174              
1175             # $e = $ds->eta
1176             sub eta {
1177 18     18 1 3119 my ($this) = @_;
1178 18 100       68 return $this->zeta if $this->[_F_EXPONENT] == 1;
1179 17         60 my $eta = $this->[_F_ETA];
1180 17 100       46 if (!defined $eta) {
1181 7         15 my $p = $this->[_F_BASE];
1182 7         14 my $m = $this->[_F_MODULUS];
1183 7         33 my ($x) = $this->find_delta( invmod($p, $m) );
1184 7         33 my $s = $this->[_F_ELEMENTS]->[$this->[_F_X_START]];
1185 7         39 $eta = $this->[_F_ETA] = addmod(mulmod($x, $p, $m), -$s, $m);
1186             }
1187 17         107 return $eta;
1188             }
1189              
1190 3     3 1 15 sub plane_principal_elements { @{$_[0]->[_F_PRINC_ELEMS]} }
  3         15  
1191 3     3 1 526 sub plane_supplemental_elements { @{$_[0]->[_F_SUPPL_ELEMS]} }
  3         10  
1192              
1193             sub plane_fill_elements {
1194 3     3 1 662 my ($this) = @_;
1195 3         9 my @fe = _fill_elements(@{$this}[_F_ORDER, _F_MODULUS]);
  3         16  
1196 3         13 return @fe;
1197             }
1198              
1199             sub plane_derived_elements_of {
1200 6     6 1 1088 my ($this, @elem) = @_;
1201 6         13 my ($base, $modulus) = @{$this}[_F_BASE, _F_MODULUS];
  6         16  
1202 6         11 my @de = ();
1203 6         13 foreach my $e0 (@elem) {
1204 6         21 my $e = mulmod($e0, $base, $modulus);
1205 6         16 while ($e != $e0) {
1206 21         42 push @de, $e;
1207 21         62 $e = mulmod($e, $base, $modulus);
1208             }
1209             }
1210 6         32 return @de;
1211             }
1212              
1213             sub plane_unit_elements {
1214 3     3 1 1976 my ($this) = @_;
1215 3 100       11 if (!wantarray) {
1216 2         8 return 3 * $this->[_F_EXPONENT] * @{$this->[_F_PRINC_ELEMS]};
  2         9  
1217             }
1218             return
1219 2         7 map { ($_, $this->plane_derived_elements_of($_)) }
1220 1         2 @{$this->[_F_PRINC_ELEMS]};
  1         5  
1221             }
1222              
1223             sub plane_nonunit_elements {
1224 2     2 1 1859 my ($this) = @_;
1225 2 100       9 if (!wantarray) {
1226 1         7 return $this->[_F_ORDER] + 1 - $this->plane_unit_elements;
1227             }
1228             return
1229             (
1230             (
1231 3         6 map { @{$_} }
  3         8  
1232 3 50       6 sort { @{$b} <=> @{$a} || $a->[0] <=> $b->[0] }
  3         7  
  3         13  
1233 3         12 map { [$_, $this->plane_derived_elements_of($_)] }
1234 1         4 @{$this->[_F_SUPPL_ELEMS]}
1235             ),
1236 1         3 _fill_elements(@{$this}[_F_ORDER, _F_MODULUS])
  1         5  
1237             );
1238             }
1239              
1240             # $bool = $ds->contains($e)
1241             sub contains {
1242 312     312 1 3632 my ($this, $elem) = @_;
1243 312         797 my $elements = $this->_elements;
1244 312         765 return _ol_contains($elements, $elem);
1245             }
1246              
1247             # $cmp = $ds1->compare($ds2);
1248             sub compare {
1249 51     51 1 15312 my ($this, $that) = @_;
1250 51         204 my $order = $this->order;
1251 51         236 my $cmp = $order <=> $that->order;
1252 51 100       200 return $cmp if $cmp;
1253 45         122 my $le = $this->_elements;
1254 45         118 my $re = $that->_elements;
1255 45         197 foreach my $x (0 .. $order) {
1256 215         451 $cmp = $le->[$x] <=> $re->[$x];
1257 215 100       668 return $cmp if $cmp;
1258             }
1259 38         203 return 0;
1260             }
1261              
1262             # $cmp = $ds1->compare_topdown($ds2);
1263             sub compare_topdown {
1264 16     16 1 15913 my ($this, $that) = @_;
1265 16         58 my $order = $this->order;
1266 16         37 my $cmp = $order <=> $that->order;
1267 16 100       55 return $cmp if $cmp;
1268 10         30 my $le = $this->_elements;
1269 10         25 my $re = $that->_elements;
1270 10         19 my $x = $order;
1271 10         29 while ($x >= 0) {
1272 19         41 $cmp = $le->[$x] <=> $re->[$x];
1273 19 100       54 return $cmp if $cmp;
1274 13         44 --$x;
1275             }
1276 4         14 return 0;
1277             }
1278              
1279             # $bool = $ds1->same_plane($ds2);
1280             sub same_plane {
1281 21     21 1 16265 my ($this, $that) = @_;
1282 21         77 my $order = $this->order;
1283 21 100       58 return !1 if $order != $that->order;
1284 15         34 my $l1 = $this->[_F_LAMBDA];
1285 15         29 my $l2 = $that->[_F_LAMBDA];
1286 15 100 66     109 return $l1 == $l2 if $l1 && $l2;
1287 3         5 my $le = $this->[_F_PRINC_ELEMS];
1288 3         5 my $re;
1289 3 100       5 if (@{$le}) {
  3         10  
1290 1         3 $re = $that->[_F_PRINC_ELEMS];
1291             }
1292             else {
1293 2         6 $le = $this->[_F_SUPPL_ELEMS];
1294 2         6 $re = $that->[_F_SUPPL_ELEMS];
1295             }
1296 3         6 foreach my $x (0 .. $#{$le}) {
  3         13  
1297 3 100       20 return !1 if $re->[$x] != $le->[$x];
1298             }
1299 2         17 return !0;
1300             }
1301              
1302             # @e = $ds1->common_elements($ds2);
1303             sub common_elements {
1304 16     16 1 16016 my ($this, $that) = @_;
1305 16         62 my $order = $this->order;
1306 16         40 my @common = ();
1307 16 100       52 return @common if $order != $that->order;
1308 10         19 my $li = 0;
1309 10         19 my $ri = 0;
1310 10         33 my $le = $this->_elements;
1311 10         25 my $re = $that->_elements;
1312 10         25 my $lv = $le->[0];
1313 10         20 my $rv = $re->[0];
1314 10         18 while (1) {
1315 37         56 my $cmp = $lv <=> $rv;
1316 37 100       88 push @common, $lv if !$cmp;
1317 37 100       85 if ($cmp <= 0) {
1318 28 100       67 last if ++$li > $order;
1319 21         35 $lv = $le->[$li];
1320             }
1321 30 100       65 if ($cmp >= 0) {
1322 23 100       52 last if ++$ri > $order;
1323 20         70 $rv = $re->[$ri];
1324             }
1325             }
1326 10         47 return @common;
1327             }
1328              
1329             # ($factor, $delta) = $ds1->find_linear_map($ds2);
1330             sub find_linear_map {
1331 36     36 1 2111 my ($this, $that) = @_;
1332 36         121 my $factor = _find_factor($this, $that);
1333 35         165 my $delta = _delta_f($this, $factor, $that);
1334 35         112 return ($factor, $delta);
1335             }
1336              
1337             # @factor_delta_pairs = $ds1->find_all_linear_maps($ds2);
1338             sub find_all_linear_maps {
1339 4     4 1 42 my ($this, $that) = @_;
1340 4         10 my $f1 = eval { _find_factor($this, $that) };
  4         14  
1341 4 100       17 return () if !defined $f1;
1342 3         9 my $modulus = $this->modulus;
1343             return
1344 38         66 sort { $a->[0] <=> $b->[0] }
1345             map {
1346 3         12 my $f = mulmod($f1, $_, $modulus);
  21         47  
1347 21         47 my $d = _delta_f($this, $f, $that);
1348 21         77 [$f, $d]
1349             } $this->multipliers;
1350             }
1351              
1352             1;
1353             __END__