File Coverage

blib/lib/Math/DifferenceSet/Planar.pm
Criterion Covered Total %
statement 797 797 100.0
branch 277 286 96.8
condition 63 72 87.5
subroutine 127 127 100.0
pod 75 75 100.0
total 1339 1357 98.6


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