File Coverage

blib/lib/Math/DifferenceSet/Planar.pm
Criterion Covered Total %
statement 609 609 100.0
branch 256 262 97.7
condition 66 72 91.6
subroutine 87 87 100.0
pod 48 48 100.0
total 1066 1078 98.8


line stmt bran cond sub pod time code
1             package Math::DifferenceSet::Planar;
2              
3 5     5   63880 use strict;
  5         29  
  5         124  
4 5     5   24 use warnings;
  5         8  
  5         118  
5 5     5   21 use Carp qw(croak);
  5         7  
  5         308  
6 5     5   1981 use Math::DifferenceSet::Planar::Data;
  5         16  
  5         192  
7 5     5   5979 use Math::BigInt try => 'GMP';
  5         149613  
  5         71  
8 5         26 use Math::Prime::Util qw(
9             is_power is_prime_power euler_phi factor_exp gcd
10             mulmod addmod invmod powmod divmod
11 5     5   117212 );
  5         47561  
12              
13             # Math::DifferenceSet::Planar=ARRAY(...)
14             # ........... index ........... # ........... value ...........
15 5     5   769 use constant _F_ORDER => 0;
  5         9  
  5         228  
16 5     5   25 use constant _F_BASE => 1;
  5         13  
  5         180  
17 5     5   28 use constant _F_EXPONENT => 2;
  5         8  
  5         166  
18 5     5   27 use constant _F_MODULUS => 3;
  5         9  
  5         174  
19 5     5   24 use constant _F_N_PLANES => 4; # number of distinct planes this size
  5         12  
  5         205  
20 5     5   28 use constant _F_ELEMENTS => 5; # elements arrayref
  5         12  
  5         181  
21 5     5   24 use constant _F_ROTATORS => 6; # rotators arrayref, initially empty
  5         29  
  5         263  
22 5     5   35 use constant _F_INDEX_MIN => 7; # index of smallest element
  5         9  
  5         196  
23 5     5   25 use constant _F_GENERATION => 8; # database generation
  5         10  
  5         200  
24 5     5   28 use constant _F_LOG => 9; # plane logarithm value, may be undef
  5         21  
  5         199  
25 5     5   26 use constant _F_ZETA => 10; # "zeta" value, initially undef
  5         10  
  5         202  
26 5     5   31 use constant _F_ETA => 11; # "eta" value, initially undef
  5         7  
  5         204  
27 5     5   24 use constant _F_PEAK => 12; # peak elements arrayref, initially undef
  5         9  
  5         227  
28 5     5   28 use constant _NFIELDS => 13;
  5         8  
  5         29742  
29              
30             our $VERSION = '0.018';
31              
32             our $_LOG_MAX_ORDER = 22.1807; # limit for integer exponentiation
33             our $_MAX_ENUM_COUNT = 32768; # limit for stored rotator set size
34             our $_MAX_MEMO_COUNT = 4096; # limit for memoized values
35             our $_DEFAULT_DEPTH = 1024; # default check_elements() depth
36             our $_USE_SPACES_DB = 1; # enable looking up rotators
37             our $_MAX_FMT_COUNT = 5; # elements printed in messages
38              
39             my $current_data = undef; # current M::D::P::Data object
40             my $generation = 0; # incremented on $current_data update
41              
42             my %memo_n_planes = (); # memoized n_planes values
43              
44             # ----- private subroutines -----
45              
46             # calculate powers of p (mod m)
47             sub _multipliers {
48 7     7   14 my ($base, $exponent, $modulus) = @_;
49 7         14 my @mult = (1);
50 7         11 my $n = 3 * $exponent;
51 7         10 my $x = $base;
52 7   66     32 while (@mult < $n && $x != 1) {
53 41         49 push @mult, $x;
54 41         115 $x = mulmod($x, $base, $modulus);
55             }
56 7 50       18 push @mult, q[...] if $x != 1;
57 7 50       16 die "assertion failed: not $n elements: @mult" if @mult != $n;
58 7         19 return @mult;
59             }
60              
61             # return complete rotator base if known or small enough, otherwise undef
62             #
63             # If structure is known:
64             #
65             # rotators -> +-----------+
66             # | radices ----------------------------> +-------+
67             # +-----------+ | r_1 |
68             # | depths -----------------> +-------+ +-------+
69             # +-----------+ | d_1 | | r_2 |
70             # | inverses -----> +-------+ +-------+ +-------+
71             # +-----------+ | i_1 | | d_2 | | ... |
72             # +-------+ +-------+ +-------+
73             # | i_2 | | ... | | r_n |
74             # +-------+ +-------+ +-------+
75             # | ... | | d_n |
76             # +-------+ +-------+
77             # | i_n |
78             # +-------+
79             #
80             # n
81             # ___ j
82             # R _ | | r k 0 < j < d
83             # j j ...j = | | k (mod M), = k k
84             # 1 2 n
85             # k = 1
86             #
87             # d - 1 _
88             # i r k = 1 (mod M), d > d > ... > d > 2
89             # k k 1 = 2 = = n =
90             #
91             #
92             # Otherwise, if number of rotators is small:
93             #
94             # rotators -> +-------+-------+-- --+-------+
95             # | R_1 | R_2 | ... | R_N |
96             # +-------+-------+-- --+-------+
97             #
98             sub _rotators {
99 16     16   22 my ($this) = @_;
100             my ( $base, $exponent, $order, $modulus, $rotators) =
101 16         27 @{$this}[_F_BASE, _F_EXPONENT, _F_ORDER, _F_MODULUS, _F_ROTATORS];
  16         49  
102 16 100       19 return $rotators if @{$rotators};
  16         37  
103 13   66     34 my $space = $_USE_SPACES_DB && $this->_data->get_space($order);
104 13 100       17505 if ($space) {
105 10         221 my ($radices, $depths) = $space->rotator_space;
106             my $inverses = [
107             map {
108 10         58 invmod(
109             powmod($radices->[$_], $depths->[$_] - 1, $modulus),
110             $modulus
111             )
112 10         20 } 0 .. $#{$radices}
  10         23  
113             ];
114 10         50 $rotators = $this->[_F_ROTATORS] = [$radices, $depths, $inverses];
115 10         34 return $rotators;
116             }
117 3 100       8 return undef if $this->[_F_N_PLANES] > $_MAX_ENUM_COUNT;
118 1         6 my @mult = _multipliers($base, $exponent, $modulus);
119 1         8 my $sieve = '1' x $modulus;
120 1         5 substr($sieve, $_, 1) = '0' for @mult;
121 1         3 @{$rotators} = (1);
  1         2  
122 1         3 for (my $x = 2; $x < $modulus ; ++$x) {
123 89 100       148 if (substr $sieve, $x, 1) {
124 13 100       21 if (0 == $modulus % $x) {
125 2         8 for (my $i = $x; $i < $modulus; $i += $x) {
126 18         29 substr($sieve, $i, 1) = '0';
127             }
128 2         5 next;
129             }
130             substr($sieve, $_, 1) = '0'
131 11         12 for map { mulmod($_, $x, $modulus) } @mult;
  66         113  
132 11         13 push @{$rotators}, $x;
  11         21  
133             }
134             }
135 1         3 return $rotators;
136             }
137              
138             # iterative rotator base generator, slow, but memory efficient
139             sub _sequential_rotators {
140 2     2   3 my ($this) = @_;
141             my ( $base, $exponent, $modulus, $n_planes) =
142 2         5 @{$this}[_F_BASE, _F_EXPONENT, _F_MODULUS, _F_N_PLANES];
  2         4  
143 2         5 my @mult = _multipliers($base, $exponent, $modulus);
144 2         3 shift @mult;
145 2         9 my @pf = map { $_->[0] } factor_exp($modulus);
  3         7  
146 2 100       6 pop @pf if $pf[-1] == $modulus;
147 2         3 my $mx = 0;
148 2         3 my $x = 0;
149             return sub {
150 22 100   22   503 return 0 if $mx >= $n_planes;
151             ELEMENT:
152 20         25 while (1) {
153 71         78 ++$x;
154 71         89 foreach my $p (@pf) {
155 86 100       126 next ELEMENT if !($x % $p);
156             }
157 62         71 foreach my $e (@mult) {
158 265 100       452 next ELEMENT if mulmod($x, $e, $modulus) < $x;
159             }
160 20         23 ++$mx;
161 20         28 return $x;
162             }
163 2         13 };
164             }
165              
166             # structured rotator base iterator, time and space efficient
167             sub _structured_rotators {
168 12     12   22 my ($this) = @_;
169 12         18 my $modulus = $this->[_F_MODULUS];
170 12         16 my ($radices, $depths, $inverses) = @{ $this->[_F_ROTATORS] };
  12         20  
171 12         16 my @index = (0) x @{$radices};
  12         23  
172 12         16 my $next = 1;
173             return sub {
174 38 100   38   1016 return 0 if !$next;
175 36         44 my $element = $next;
176 36         45 my $i = 0;
177 36         62 while ($i < @index) {
178 36 100       64 if (++$index[$i] < $depths->[$i]) {
179 32         65 $next = mulmod($next, $radices->[$i], $modulus);
180 32         62 return $element;
181             }
182             }
183             continue {
184 4         16 $index[$i] = 0;
185 4         10 $next = mulmod($next, $inverses->[$i], $modulus);
186 4         8 ++$i;
187             }
188 4         6 $next = 0;
189 4         7 return $element;
190 12         57 };
191             }
192              
193             sub _space_description {
194 21     21   35 my ($spc) = @_;
195 21         419 my $order = $spc->order;
196 21         526 my $mul_radix = $spc->mul_radix;
197 21         446 my $mul_depth = $spc->mul_depth;
198 21         195 my ($radices, $depths) = $spc->rotator_space;
199 21         35 my @space = map {; "$radices->[$_]^$depths->[$_]"} 0 .. $#{$radices};
  34         96  
  21         35  
200 21         118 return "$order: $mul_radix^$mul_depth [@space]";
201             }
202              
203             # integer exponentiation
204             sub _pow {
205 13     13   23 my ($base, $exponent) = @_;
206 13 100 100     56 return 0 if $base <= 0 || $exponent < 0;
207 10 100       51 return Math::BigInt->new($base)->bpow($exponent)
208             if log(0+$base) * $exponent > $_LOG_MAX_ORDER;
209 9         13 my $power = 1;
210 9         21 while ($exponent) {
211 26 100       43 $power *= $base if 1 & $exponent;
212 26 100       55 $exponent >>= 1 and $base *= $base;
213             }
214 9         14 return $power;
215             }
216              
217             # 0 1 2 3 4 5 6 7 8 9 10
218             # 3 7 14 25 28 29 31 41 61 99 103
219             # 7 8 9 10 0 1 2 3 4 5 6
220             # re-arrange elements of a planar difference set in conventional order
221             sub _sort_elements {
222 65     65   87 my $modulus = shift;
223 65 50       188 my @elements = sort { $a <=> $b || croak "duplicate element: $a" } @_;
  962         1510  
224 64         107 my $lo = $elements[-1] - $modulus;
225 64         77 my $hi = $elements[0];
226 64         76 my $mx = 0;
227 64   100     219 while ($hi - $lo != 1 && ++$mx < @elements) {
228 204         494 ($lo, $hi) = ($hi, $elements[$mx]);
229             }
230 64 100       391 croak "delta 1 elements missing" if $mx >= @elements;
231 62 100       112 $mx += @elements if !$mx--;
232 62 100       163 push @elements, splice @elements, 0, $mx if $mx;
233 62 100       165 return (\@elements, $mx? @elements - $mx: 0);
234             }
235              
236             # 0 1 2 3 4 5 6 7
237             # 28 29 31 41 3 7 14 25
238             # L X H
239             # L X H
240             # LX H
241             # get index of smallest element (using bisection)
242             sub _index_min {
243 45     45   72 my ($elements) = @_;
244 45         73 my ($lx, $hx) = (0, $#$elements);
245 45         63 my $he = $elements->[$hx];
246 45         84 while ($lx < $hx) {
247 115         143 my $x = ($lx + $hx) >> 1;
248 115         166 my $e = $elements->[$x];
249 115 100       160 if ($e < $he) {
250 46         66 $hx = $x;
251 46         77 $he = $e;
252             }
253             else {
254 69         124 $lx = $x + 1;
255             }
256             }
257 45         74 return $hx;
258             }
259              
260             # return data connection, creating it if not yet open
261             sub _data {
262 89 100   89   484 if (!defined $current_data) {
263 4         54 ++$generation;
264 4         29 $current_data = Math::DifferenceSet::Planar::Data->new;
265             }
266 89         918 return $current_data;
267             }
268              
269             # compose a printable abbreviated description of a set
270             sub _fmt_set {
271 4     4   10 my ($this) = @_;
272             my @e = $this->[_F_ORDER] < $_MAX_FMT_COUNT?
273 1         3 @{$this->[_F_ELEMENTS]}:
274 4 100       11 (@{$this->[_F_ELEMENTS]}[0 .. $_MAX_FMT_COUNT-1], q[...]);
  3         8  
275 4         264 return '{' . join(q[,], @e) . '}';
276             }
277              
278             # identify a plane by its rotator value with respect to a given plane
279             # (both arguments zeta-canonized, second argument with known log)
280             sub _log {
281 26     26   38 my ($this, $that) = @_;
282 26         30 my $modulus = $this->[_F_MODULUS];
283 26         47 my %t = ();
284 26         28 foreach my $e (@{$this->[_F_ELEMENTS]}) {
  26         48  
285 157         316 $t{$e} = 1;
286             }
287 26         38 my $r = 0;
288 26         40 foreach my $e (@{$that->[_F_ELEMENTS]}) {
  26         39  
289 70         116 $r = invmod($e, $modulus);
290 70 100       120 last if $r;
291             }
292 26         32 my $factor = 0;
293 26 100       46 if ($r) {
294             ELEM:
295 17         28 foreach my $o (@{$this->[_F_ELEMENTS]}) {
  17         29  
296 29 100       48 next if !$o;
297 24         44 my $ro = mulmod($r, $o, $modulus);
298 24         31 foreach my $e (@{$that->[_F_ELEMENTS]}) {
  24         37  
299 120 100       274 next ELEM if !exists $t{ mulmod($e, $ro, $modulus) };
300             }
301 16         31 $factor = $ro;
302 16         28 last;
303             }
304             }
305             else {
306 9         28 my $ri = $this->iterate_rotators;
307             ROT:
308 9         15 while (my $ro = $ri->()) {
309 11         14 foreach my $e (@{$that->[_F_ELEMENTS]}) {
  11         22  
310 49 100       124 next ROT if !exists $t{ mulmod($e, $ro, $modulus) };
311             }
312 9         12 $factor = $ro;
313 9         41 last;
314             }
315             }
316 26 100       44 if ($factor) {
317 25         49 my $log = $this->[_F_LOG] =
318             mulmod($that->[_F_LOG], $factor, $modulus);
319 25         109 return $log;
320             }
321 1         3 croak 'unaligned sets: ', _fmt_set($this), ' versus ', _fmt_set($that);
322             }
323              
324             # $factor = _find_factor($ds1, $ds2);
325             sub _find_factor {
326 37     37   64 my ($this, $that) = @_;
327 37         74 my $order = $this->order;
328 37 100       68 croak 'sets of same size expected' if $order != $that->order;
329 35   100     132 my $log_this = $this->[_F_GENERATION] == $generation && $this->[_F_LOG];
330 35   100     122 my $log_that = $this->[_F_GENERATION] == $generation && $that->[_F_LOG];
331 35         57 $this->[_F_GENERATION] = $that->[_F_GENERATION] = $generation;
332 35 100       85 if (!$log_this) {
    100          
333 3         6 my $r1 = $this->zeta_canonize;
334 3         8 my $r2 = $that->zeta_canonize;
335 3 100       20 if (!$log_that) {
336 2         8 my $r3 = Math::DifferenceSet::Planar->new($order)->zeta_canonize;
337 2         7 $log_that = $that->[_F_LOG] = _log($r2, $r3);
338             }
339 3 100       11 $log_this = $this->[_F_LOG] =
340             $this == $that? $log_that: _log($r1, $r2);
341             }
342             elsif (!$log_that) {
343 23         51 my $r1 = $this->zeta_canonize;
344 23         44 my $r2 = $that->zeta_canonize;
345 22         53 $log_that = $that->[_F_LOG] = _log($r2, $r1);
346             }
347 33         69 return divmod($log_that, $log_this, $this->modulus);
348             }
349              
350             # translation amount between a multiple of a set and another set
351             sub _delta_f {
352 30     30   46 my ($this, $factor, $that) = @_;
353 30         49 my $modulus = $this->modulus;
354 30         68 my ($x) = $this->find_delta( invmod($factor, $modulus) );
355 30         45 my $s = $that->[_F_ELEMENTS]->[0];
356 30         89 return addmod($s, -mulmod($x, $factor, $modulus), $modulus);
357             }
358              
359             # ----- class methods -----
360              
361             sub list_databases {
362 1     1 1 999 return Math::DifferenceSet::Planar::Data->list_databases;
363             }
364              
365             sub set_database {
366 4     4 1 3381 my $class = shift;
367 4         9 ++$generation;
368 4         22 $current_data = Math::DifferenceSet::Planar::Data->new(@_);
369 1         900 return $class->available_count;
370             }
371              
372             # print "ok" if Math::DifferenceSet::Planar->available(9);
373             # print "ok" if Math::DifferenceSet::Planar->available(3, 2);
374             sub available {
375 12     12 1 5124 my ($class, $base, $exponent) = @_;
376 12 100       41 my $order = defined($exponent)? _pow($base, $exponent): $base;
377 12 100 100     661 return undef if !$order || $order > $class->_data->max_order;
378 5         11410 my $pds = $class->_data->get($order, 'base');
379 5   66     9325 return !!$pds && (!defined($exponent) || $base == $pds->base);
380             }
381              
382             # print "ok" if Math::DifferenceSet::Planar->known_space(9);
383             sub known_space {
384 4     4 1 2837 my ($class, $order) = @_;
385 4 100 100     19 return 0 if $order <= 0 || $order > $class->_data->sp_max_order;
386 2         4308 my $spc = $class->_data->get_space($order);
387 2 100       3553 return 0 if !$spc;
388 1         33 my ($rad) = $spc->rotator_space;
389 1         2 return 0 + @{$rad};
  1         6  
390             }
391              
392             # $desc = Math::DifferenceSet::Planar->known_space_desc(9);
393             sub known_space_desc {
394 4     4 1 3816 my ($class, $order) = @_;
395 4 100 100     18 return undef if $order <= 0 || $order > $class->_data->sp_max_order;
396 2         3985 my $spc = $class->_data->get_space($order);
397 2 100       3461 return undef if !$spc;
398 1         26 return _space_description($spc);
399             }
400              
401             # $ds = Math::DifferenceSet::Planar->new(9);
402             # $ds = Math::DifferenceSet::Planar->new(3, 2);
403             sub new {
404 39     39 1 12376 my ($class, $base, $exponent) = @_;
405 39 100       94 my $order = defined($exponent)? _pow($base, $exponent): $base;
406 39   100     104 my $pds = $order && $class->_data->get($order);
407 39 100 100     69706 if (!$pds || defined($exponent) && $base != $pds->base) {
      100        
408 4 100       101 my $key = defined($exponent)? "$base, $exponent": $order;
409 4         361 croak "PDS($key) not available";
410             }
411 35         1554 return bless [
412             $pds->order,
413             $pds->base,
414             $pds->exponent,
415             $pds->modulus,
416             $pds->n_planes,
417             $pds->elements,
418             [], # rotators
419             0, # index_min
420             $generation,
421             1, # log
422             ], $class;
423             }
424              
425             # $ds = Math::DifferenceSet::Planar->from_elements_fast(
426             # 0, 1, 3, 9, 27, 49, 56, 61, 77, 81
427             # );
428             sub from_elements_fast {
429 32     32 1 755 my $class = shift;
430 32         52 my $order = $#_;
431 32         78 my ($base, $exponent);
432 32 100       309 $exponent = is_prime_power($order, \$base)
433             or croak "this implementation cannot handle order $order";
434 30         57 my $modulus = ($order + 1) * $order + 1;
435 30 100       52 if (grep { $_ < 0 || $modulus <= $_ } @_) {
  168 100       468  
436 2         5 my $max = $modulus - 1;
437 2         182 croak "element values inside range 0..$max expected";
438             }
439 28         64 my ($elements, $index_min) = _sort_elements($modulus, @_);
440 26         40 my $n_planes;
441 26 100       63 if (!exists $memo_n_planes{$order}) {
442 10         60 $n_planes = $memo_n_planes{$order} =
443             euler_phi($modulus) / (3 * $exponent);
444 10 100       31 %memo_n_planes = ($order => $n_planes)
445             if $_MAX_MEMO_COUNT < keys %memo_n_planes;
446             }
447             else {
448 16         26 $n_planes = $memo_n_planes{$order};
449             }
450 26         93 return bless [
451             $order,
452             $base,
453             $exponent,
454             $modulus,
455             $n_planes,
456             $elements,
457             [], # rotators
458             $index_min,
459             $generation,
460             ], $class;
461             }
462              
463             # $ds = Math::DifferenceSet::Planar->from_elements(
464             # 0, 1, 3, 9, 27, 49, 56, 61, 77, 81
465             # );
466             sub from_elements {
467 29     29 1 11079 my ($class, @elements) = @_;
468 29         76 my $this = $class->from_elements_fast(@elements);
469 23         53 my $ref = $class->new(@elements - 1);
470 23 100       353 eval { _find_factor($ref, $this) } or
  23         44  
471             croak "apparently not a planar difference set: ", _fmt_set($this);
472 21         107 return $this;
473             }
474              
475             # $bool = Math::DifferenceSet::Planar->verify_elements(
476             # 0, 1, 3, 9, 27, 49, 56, 61, 77, 81
477             # );
478             sub verify_elements {
479 16     16 1 6337 my ($class, @elements) = @_;
480 16         29 my $order = $#elements;
481 16 100       38 return undef if $order <= 1;
482 15         26 my $modulus = ($order + 1) * $order + 1;
483 15         27 my $median = ($modulus - 1) / 2;
484 15         32 my $seen = '0' x $median;
485 15         26 foreach my $r1 (@elements) {
486 66 100 100     222 return undef if $r1 < 0 || $modulus <= $r1 || $r1 != int $r1;
      100        
487 60         75 foreach my $r2 (@elements) {
488 184 100       276 last if $r1 == $r2;
489 128 100       186 my $d = $r1 < $r2? $r2 - $r1: $modulus + $r2 - $r1;
490 128 100       192 $d = $modulus - $d if $d > $median;
491 128 100       332 return !1 if substr($seen, $d-1, 1)++;
492             }
493             }
494 5         38 return $median == $seen =~ tr/1//;
495             }
496              
497             # $bool = Math::DifferenceSet::Planar->check_elements(
498             # [0, 1, 3, 9, 27, 49, 56, 61, 77, 81], 999
499             # );
500             sub check_elements {
501 14     14 1 5414 my ($class, $elem_listref, $depth, $factor) = @_;
502 14         46 my $order = $#{$elem_listref};
  14         23  
503 14 100       39 return undef if $order <= 1;
504 13         39 my $modulus = ($order + 1) * $order + 1;
505 13         26 my $median = ($modulus - 1) / 2;
506 13 100       32 $depth = $median <= $_DEFAULT_DEPTH? $median: $_DEFAULT_DEPTH
    100          
507             if !$depth;
508 13 100       25 my $limit = $median <= $depth? $median: $depth;
509 13         33 my $seen = '1' . ('0' x $limit);
510 13         14 foreach my $e (@{$elem_listref}) {
  13         24  
511 92 100 100     283 return undef if $e < 0 || $modulus <= $e || $e != int $e;
      100        
512             }
513 10         20 my @elements = sort { $a <=> $b } @{$elem_listref};
  128         166  
  10         36  
514             I1:
515 10         25 foreach my $i1 (0 .. $order) {
516 75         82 my $r1 = $elements[$i1];
517 75         91 my $i2 = $i1 - 1;
518 75         111 while ($i2 >= 0) {
519 129         143 my $r2 = $elements[$i2];
520 129         147 my $d = $r1 - $r2;
521 129 100       177 next I1 if $d > $limit;
522 93 100       187 return !1 if substr($seen, $d, 1)++;
523 92         155 --$i2;
524             }
525 38         46 $i2 = $order;
526 38         60 while ($i2 > $i1) {
527 68         76 my $r2 = $elements[$i2];
528 68         88 my $d = $modulus + $r1 - $r2;
529 68 100       114 next I1 if $d > $limit;
530 30 50       80 return !1 if substr($seen, $d, 1)++;
531 30         54 --$i2;
532             }
533             }
534 9 100       23 return !1 if 0 <= index $seen, '0';
535 7 100       15 return 2 if $median <= $depth;
536 5 100       12 if (!defined $factor) {
537 4 100       29 $factor = $order if !is_power($order, 0, \$factor);
538             }
539 5 100       9 if (1 < $factor) {
540 4         6 my $mx = $order;
541 4         9 for (my $i = 0; $i < $order; ++$i) {
542 11 100       23 $mx = $i, last if 1 == $elements[$i+1] - $elements[$i];
543             }
544 4 100       9 push @elements, splice @elements, 0, $mx if $mx;
545 4         6 my ($multiple) = eval { _sort_elements(
546             $modulus,
547 4         8 map { mulmod($_, $factor, $modulus) } @elements
  33         57  
548             )};
549 4 100       43 return 0 if !defined $multiple;
550 3         4 my $d1 = $elements[0] - $multiple->[0];
551 3 50       7 my $d2 = $d1 >= 0? $d1 - $modulus: $d1 + $modulus;
552 3 50       7 ($d1, $d2) = ($d2, $d1) if abs($d2) > abs($d1); # optimization
553 3         9 for (my $i = 1; $i <= $order; ++$i) {
554 18         22 my $d = $elements[$i] - $multiple->[$i];
555 18 100 66     52 return 0 if $d != $d1 && $d != $d2;
556             }
557             }
558 3         9 return 1;
559             }
560              
561             # $it1 = Math::DifferenceSet::Planar->iterate_available_sets;
562             # $it2 = Math::DifferenceSet::Planar->iterate_available_sets(10, 20);
563             # while (my $ds = $it2->()) {
564             # ...
565             # }
566             sub iterate_available_sets {
567 5     5 1 6137 my ($class, @minmax) = @_;
568 5         12 my $dit = $class->_data->iterate(@minmax);
569             return sub {
570 28     28   582 my $pds = $dit->();
571 28 100       13027 return undef if !$pds;
572 25         500 return bless [
573             $pds->order,
574             $pds->base,
575             $pds->exponent,
576             $pds->modulus,
577             $pds->n_planes,
578             $pds->elements,
579             [], # rotators
580             0, # index_min
581             $generation,
582             1, # log
583             ], $class;
584 5         39 };
585             }
586              
587             # $min = Math::DifferenceSet::Planar->available_min_order;
588 1     1 1 3799 sub available_min_order { $_[0]->_data->min_order }
589              
590             # $max = Math::DifferenceSet::Planar->available_max_order;
591 1     1 1 2948 sub available_max_order { $_[0]->_data->max_order }
592              
593             # $count = Math::DifferenceSet::Planar->available_count;
594 2     2 1 837 sub available_count { $_[0]->_data->count }
595              
596             # $min = Math::DifferenceSet::Planar->known_space_min_order;
597 1     1 1 3484 sub known_space_min_order { $_[0]->_data->sp_min_order }
598              
599             # $max = Math::DifferenceSet::Planar->known_space_max_order;
600 1     1 1 2886 sub known_space_max_order { $_[0]->_data->sp_max_order }
601              
602             # $count = Math::DifferenceSet::Planar->known_space_count;
603 1     1 1 2767 sub known_space_count { $_[0]->_data->sp_count }
604              
605             # $it3 = Math::DifferenceSet::Planar->iterate_known_spaces;
606             # $it3 = Math::DifferenceSet::Planar->iterate_known_spaces(10,20);
607             # while (my $spc = $it3->()) {
608             # print "$spc\n";
609             # }
610             sub iterate_known_spaces {
611 4     4 1 5084 my ($class, @minmax) = @_;
612 4         12 my $dit = $class->_data->iterate_spaces(@minmax);
613             return sub {
614 23     23   968 my $spc = $dit->();
615 23 100       10559 return undef if !$spc;
616 20         41 return _space_description($spc);
617 4         26 };
618             }
619              
620             # ----- object methods -----
621              
622             # $o = $ds->order;
623             # $p = $ds->order_base;
624             # $n = $ds->order_exponent;
625             # $m = $ds->modulus;
626             # $np = $ds->n_planes;
627             # @e = $ds->elements;
628             # $e0 = $ds->element(0);
629 373     373 1 1944 sub order { $_[0]->[_F_ORDER ] }
630 1     1 1 4 sub order_base { $_[0]->[_F_BASE ] }
631 1     1 1 5 sub order_exponent { $_[0]->[_F_EXPONENT] }
632 236     236 1 625 sub modulus { $_[0]->[_F_MODULUS ] }
633 3     3 1 521 sub n_planes { $_[0]->[_F_N_PLANES] }
634 40     40 1 10954 sub elements { @{ $_[0]->[_F_ELEMENTS] } }
  40         128  
635 5     5 1 23 sub element { $_[0]->[_F_ELEMENTS]->[$_[1]] }
636 1     1 1 437 sub start_element { $_[0]->[_F_ELEMENTS]->[ 0 ] }
637              
638 1     1 1 435 sub min_element { $_[0]->[_F_ELEMENTS]->[$_[0]->[_F_INDEX_MIN] ] }
639 1     1 1 5 sub max_element { $_[0]->[_F_ELEMENTS]->[$_[0]->[_F_INDEX_MIN]-1] }
640              
641             # @e = $ds->elements_sorted;
642             sub elements_sorted {
643 3     3 1 955 my ($this) = @_;
644 3         5 my $elements = $this->[_F_ELEMENTS];
645 3 100       10 return @$elements if !wantarray;
646 2         6 my $index_min = $this->[_F_INDEX_MIN];
647 2         6 return @{$elements}[$index_min .. $#$elements, 0 .. $index_min - 1];
  2         8  
648             }
649              
650             # $ds1 = $ds->translate(1);
651             sub translate {
652 87     87 1 1827 my ($this, $delta) = @_;
653 87         117 my $modulus = $this->[_F_MODULUS];
654 87 100 100     269 $delta %= $modulus unless -$modulus < $delta && $delta < $modulus;
655 87 100       166 return $this if !$delta;
656             my @elements =
657 79         97 map { addmod($_, $delta, $modulus) } @{$this->[_F_ELEMENTS]};
  584         911  
  79         134  
658 79         116 my $that = bless [@{$this}], ref $this;
  79         193  
659 79         127 $that->[_F_ELEMENTS] = \@elements;
660 79 100       178 $that->[_F_INDEX_MIN] =
661             $elements[0] < $elements[-1]? 0: _index_min(\@elements);
662 79 100       164 if (defined (my $z = $that->[_F_ZETA])) {
663 62         150 $that->[_F_ZETA] = addmod($z,
664             mulmod($delta, $this->[_F_ORDER]-1, $modulus), $modulus);
665             }
666 79 100       176 if (defined (my $e = $that->[_F_ETA])) {
667 6         13 $that->[_F_ETA] = addmod($e,
668             mulmod($delta, $this->[_F_BASE]-1, $modulus), $modulus);
669             }
670 79         135 $this->[_F_PEAK] = undef;
671 79         176 return $that;
672             }
673              
674             # $ds2 = $ds->canonize;
675 15     15 1 919 sub canonize { $_[0]->translate(- $_[0]->[_F_ELEMENTS]->[0]) }
676              
677             # $ds2 = $ds->gap_canonize;
678             sub gap_canonize {
679 1     1 1 428 my ($this) = @_;
680 1         4 my $delta = ($this->largest_gap)[1];
681 1         3 return $this->translate(-$delta);
682             }
683              
684             # $ds2 = $ds->zeta_canonize;
685             sub zeta_canonize {
686 61     61 1 1494 my ($this) = @_;
687 61         111 my $zeta = $this->zeta;
688 60         98 my $order = $this->order;
689 60         86 my $modulus = $this->modulus;
690 60 100       120 if ( ($order - 1) % 3 ) {
691 35 100       70 return $this if !$zeta;
692 21         48 my $delta = divmod($zeta, $order - 1, $modulus);
693 21         48 return $this->translate(-$delta);
694             }
695 25 100 100     63 return $this if !$zeta && !$this->contains(0);
696 22         38 $modulus /= 3;
697 22         135 my $delta = divmod($zeta, $order - 1, $modulus);
698 22         43 $delta += $modulus while $this->contains($delta); # 0..2 iterations
699 22         47 return $this->translate(-$delta);
700             }
701              
702             # $it = $ds->iterate_rotators;
703             # while (my $m = $it->()) {
704             # ...
705             # }
706             sub iterate_rotators {
707 16     16 1 923 my ($this) = @_;
708 16         35 my $rotators = $this->_rotators;
709 16 100       172 return $this->_sequential_rotators if !$rotators;
710 14 100       53 return $this->_structured_rotators if 'ARRAY' eq ref $rotators->[0];
711 2         2 my $mx = 0;
712 2 100   14   10 return sub { $mx < @{$rotators}? $rotators->[$mx++]: 0 };
  14         912  
  14         28  
713             }
714              
715             # $it = $ds->iterate_planes;
716             # while (my $ds = $it->()) {
717             # ...
718             # }
719             sub iterate_planes {
720 1     1 1 441 my ($this) = @_;
721 1         3 my $r_it = $this->iterate_rotators;
722             return sub {
723 13     13   510 my $r = $r_it->();
724 13 100       28 return $r? $this->multiply($r)->canonize: undef;
725 1         4 };
726             }
727              
728             # @pm = $ds->multipliers;
729             sub multipliers {
730 5     5 1 893 my ($this) = @_;
731             my ( $base, $exponent, $modulus) =
732 5         8 @{$this}[_F_BASE, _F_EXPONENT, _F_MODULUS];
  5         14  
733 5 100       15 return 3 * $exponent if !wantarray;
734 4         13 return _multipliers($base, $exponent, $modulus);
735             }
736              
737             # $ds3 = $ds->multiply($m);
738             sub multiply {
739 40     40 1 6813 my ($this, $factor) = @_;
740 40         62 my $modulus = $this->[_F_MODULUS];
741 40         52 $factor %= $modulus;
742 40 100       91 return $this if 1 == $factor;
743 34 100       162 croak "$_[1]: factor is not coprime to modulus"
744             if gcd($modulus, $factor) != 1;
745             my ($elements, $index_min) = _sort_elements(
746             $modulus,
747 33         47 map { mulmod($_, $factor, $modulus) } @{$this->[_F_ELEMENTS]}
  301         461  
  33         54  
748             );
749 33         51 my $that = bless [@{$this}], ref $this;
  33         84  
750 33         49 $that->[_F_ELEMENTS ] = $elements;
751 33         43 $that->[_F_INDEX_MIN] = $index_min;
752 33 100       58 if (defined(my $log = $that->[_F_LOG])) {
753 32         61 $that->[_F_LOG] = mulmod($log, $factor, $modulus);
754             }
755 33   66     109 $that->[_F_ZETA] &&= mulmod($that->[_F_ZETA], $factor, $modulus);
756 33   66     66 $that->[_F_ETA] &&= mulmod($that->[_F_ETA], $factor, $modulus);
757 33         44 $this->[_F_PEAK] = undef;
758 33         72 return $that;
759             }
760              
761             # ($e1, $e2) = $ds->find_delta($delta);
762             sub find_delta {
763 99     99 1 2009 my ($this, $delta) = @_;
764 99         151 my $order = $this->order;
765 99         153 my $modulus = $this->modulus;
766 99         121 my $elements = $this->[_F_ELEMENTS];
767 99         141 my $de = $delta % $modulus;
768 99         143 my $up = $de + $de < $modulus;
769 99 100       162 $de = $modulus - $de if !$up;
770 99         151 my ($lx, $ux, $c) = (0, 0, 0);
771 99         116 my ($le, $ue) = @{$elements}[0, 0];
  99         162  
772 99         122 my $bogus = 0;
773 99         174 while ($c != $de) {
774 919 100       1178 if ($c < $de) {
775 555 100       817 if(++$ux > $order) {
776 53         67 $ux = 0;
777             }
778 555         633 $ue = $elements->[$ux];
779 555 100       796 $bogus = 1, last if $ux == $lx;
780             }
781             else {
782 364 100       553 $bogus = 1, last if ++$lx > $order;
783 363         430 $le = $elements->[$lx];
784             }
785 917 100       1548 $c = $ue < $le? $modulus + $ue - $le: $ue - $le;
786             }
787 99 100       390 croak "bogus set: delta not found: $delta (mod $modulus)" if $bogus;
788 97 100       230 return $up? ($le, $ue): ($ue, $le);
789             }
790              
791             # ($e1, $e2) = $ds->peak_elements
792             sub peak_elements {
793 2     2 1 781 my ($this) = @_;
794 2         3 my $peak = $this->[_F_PEAK];
795 2 100       7 if (!defined $peak) {
796 1         4 $peak = [$this->find_delta($this->modulus >> 1)];
797 1         4 $this->[_F_PEAK] = $peak;
798             }
799 2         4 return @{$peak};
  2         6  
800             }
801              
802             # ($e1, $e2, $delta) = $ds->largest_gap;
803             sub largest_gap {
804 2     2 1 460 my ($this) = @_;
805 2         5 my $modulus = $this->modulus;
806 2         4 my $p_max;
807             my $e_max;
808 2         3 my $d_max = 0;
809 2         4 my $e_pre = $this->element($this->order);
810 2         5 foreach my $e ($this->elements) {
811 20         21 my $d = $e - $e_pre;
812 20 100       35 $d += $modulus if $d < 0;
813 20 100       55 if ($d > $d_max) {
814 6         7 $d_max = $d;
815 6         7 $p_max = $e_pre;
816 6         7 $e_max = $e;
817             }
818 20         22 $e_pre = $e;
819             }
820 2         6 return ($p_max, $e_max, $d_max);
821             }
822              
823             # $e = $ds->zeta
824             sub zeta {
825 65     65 1 560 my ($this) = @_;
826 65         95 my $zeta = $this->[_F_ZETA];
827 65 100       122 if (!defined $zeta) {
828 56         73 my $order = $this->[_F_ORDER];
829 56         73 my $modulus = $this->[_F_MODULUS];
830 56         89 my $start = $this->[_F_ELEMENTS]->[0];
831 56         111 (undef, my $x) = $this->find_delta($order + 1);
832 55         227 $zeta = $this->[_F_ZETA] =
833             addmod(mulmod($x, $order, $modulus), -$start, $modulus);
834             }
835 64         114 return $zeta;
836             }
837              
838             # $e = $ds->eta
839             sub eta {
840 16     16 1 1433 my ($this) = @_;
841 16 100       36 return $this->zeta if $this->[_F_EXPONENT] == 1;
842 15         20 my $eta = $this->[_F_ETA];
843 15 100       27 if (!defined $eta) {
844 8         14 my $p = $this->[_F_BASE];
845 8         11 my $m = $this->[_F_MODULUS];
846 8         10 my $s = $this->[_F_ELEMENTS]->[0];
847 8         25 my ($x) = $this->find_delta( invmod($p, $m) );
848 8         28 $eta = $this->[_F_ETA] = addmod(mulmod($x, $p, $m), -$s, $m);
849             }
850 15         52 return $eta;
851             }
852              
853             # $bool = $ds->contains($e)
854             sub contains {
855 251     251 1 1417 my ($this, $elem) = @_;
856 251         288 my $elements = $this->[_F_ELEMENTS];
857 251         276 my $lx = $this->[_F_INDEX_MIN];
858 251         294 my $hx = $#{$elements};
  251         301  
859 251 100 100     510 ($lx, $hx) = (0, $lx - 1) if $lx && $elements->[0] <= $elem;
860 251         379 while ($lx <= $hx) {
861 764         891 my $x = ($lx + $hx) >> 1;
862 764         840 my $e = $elements->[$x];
863 764 100       979 if ($e <= $elem) {
864 487 100       753 return !0 if $e == $elem;
865 427         601 $lx = $x + 1;
866             }
867             else {
868 277         423 $hx = $x - 1;
869             }
870             }
871 191         360 return !1;
872             }
873              
874             # $cmp = $ds1->compare($ds2);
875             sub compare {
876 23     23 1 7118 my ($this, $that) = @_;
877 23         49 my $order = $this->order;
878 23         37 my $cmp = $order <=> $that->order;
879 23 100       51 return $cmp if $cmp;
880 17         24 my $lx = $this->[_F_INDEX_MIN];
881 17         24 my $rx = $that->[_F_INDEX_MIN];
882 17         24 my $le = $this->[_F_ELEMENTS];
883 17         19 my $re = $that->[_F_ELEMENTS];
884 17         37 foreach my $i (0 .. $order) {
885 57         71 $cmp = $le->[$lx] <=> $re->[$rx];
886 57 100       87 return $cmp if $cmp;
887 51 100       83 ++$lx <= $order or $lx = 0;
888 51 100       80 ++$rx <= $order or $rx = 0;
889             }
890 11         39 return 0;
891             }
892              
893             # $bool = $ds1->same_plane($ds2);
894             sub same_plane {
895 17     17 1 7528 my ($this, $that) = @_;
896 17         37 my $order = $this->order;
897 17 100       29 return !1 if $order != $that->order;
898 11         18 my $le = $this->[_F_ELEMENTS];
899 11         18 my $re = $that->[_F_ELEMENTS];
900 11         18 my $delta0 = $re->[0] - $le->[0];
901 11 100       26 if (!$delta0) {
902 6         13 foreach my $x (1 .. $order) {
903 13 100       39 return !1 if $re->[$x] != $le->[$x];
904             }
905 4         9 return !0;
906             }
907 5         10 my $modulus = $this->modulus;
908 5 100       16 my $delta1 = $delta0 < 0? $delta0 + $modulus: $delta0 - $modulus;
909 5         13 foreach my $x (1 .. $order) {
910 16         24 my $delta = $re->[$x] - $le->[$x];
911 16 100 100     49 return !1 if $delta != $delta0 && $delta != $delta1;
912             }
913 3         11 return !0;
914             }
915              
916             # @e = $ds1->common_elements($ds2);
917             sub common_elements {
918 16     16 1 7212 my ($this, $that) = @_;
919 16         34 my $order = $this->order;
920 16         28 my @common = ();
921 16 100       25 return @common if $order != $that->order;
922 10         17 my $li = 0;
923 10         14 my $ri = 0;
924 10         12 my $lx = $this->[_F_INDEX_MIN];
925 10         14 my $rx = $that->[_F_INDEX_MIN];
926 10         13 my $le = $this->[_F_ELEMENTS];
927 10         13 my $re = $that->[_F_ELEMENTS];
928 10         24 my $lv = $le->[$lx];
929 10         17 my $rv = $re->[$rx];
930 10         12 while (1) {
931 37         43 my $cmp = $lv <=> $rv;
932 37 100       67 push @common, $lv if !$cmp;
933 37 100       56 if ($cmp <= 0) {
934 28 100       44 ++$lx <= $order or $lx = 0;
935 28 100       47 last if ++$li > $order;
936 21         25 $lv = $le->[$lx];
937             }
938 30 100       44 if ($cmp >= 0) {
939 23 100       38 ++$rx <= $order or $rx = 0;
940 23 100       37 last if ++$ri > $order;
941 20         23 $rv = $re->[$rx];
942             }
943             }
944 10         26 return @common;
945             }
946              
947             # ($factor, $delta) = $ds1->find_linear_map($ds2);
948             sub find_linear_map {
949 10     10 1 896 my ($this, $that) = @_;
950 10         22 my $factor = _find_factor($this, $that);
951 9         18 my $delta = _delta_f($this, $factor, $that);
952 9         24 return ($factor, $delta);
953             }
954              
955             # @factor_delta_pairs = $ds1->find_all_linear_maps($ds2);
956             sub find_all_linear_maps {
957 4     4 1 37 my ($this, $that) = @_;
958 4         8 my $f1 = eval { _find_factor($this, $that) };
  4         11  
959 4 100       58 return () if !defined $f1;
960 3         8 my $modulus = $this->modulus;
961             return
962 39         58 sort { $a->[0] <=> $b->[0] }
963             map {
964 3         9 my $f = mulmod($f1, $_, $modulus);
  21         40  
965 21         31 my $d = _delta_f($this, $f, $that);
966 21         42 [$f, $d]
967             } $this->multipliers;
968             }
969              
970             1;
971             __END__