# Copyright 2011, 2012, 2013, 2014, 2015, 2016 Kevin Ryde
# This file is part of Math-PlanePath.
#
# Math-PlanePath is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# Math-PlanePath is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with Math-PlanePath. If not, see <http://www.gnu.org/licenses/>.
# A006218 - cumulative count of divisors
#
# Dirichlet:
# n * (log(n) + 2*gamma - 1) + O(sqrt(n)) gamma=0.57721... Euler-Mascheroni
#
# n * (log(n) + 2*gamma - 1) + O(log(n)*n^(1/3))
#
# Chandrasekharan: bounds
# n log(n) + (2 gamma - 1) n - 4 sqrt(n) - 1
# <= a(n) <=
# n log(n) + (2 gamma - 1) n + 4 sqrt(n)
#
# a(n)=2 * sum[ i=1 to floor(sqrt(n)) of floor(n/i) ] - floor(sqrt(n))^2
#
# cf A003988,A010766 - triangle with values floor(i/j)
#
# http://mathworld.wolfram.com/DirichletDivisorProblem.html
#
# compile-command: "math-image --path=DivisibleColumns --all"
#
# math-image --path=DivisibleColumns --output=numbers --all
#
package Math::PlanePath::DivisibleColumns;
use 5.004;
use strict;
use vars '$VERSION', '@ISA';
$VERSION = 124;
use Math::PlanePath;
*_sqrtint = \&Math::PlanePath::_sqrtint;
@ISA = ('Math::PlanePath');
use Math::PlanePath::Base::Generic
'is_infinite',
'round_nearest';
# uncomment this to run the ### lines
# use Smart::Comments;
use constant parameter_info_array =>
[ { name => 'divisor_type',
share_key => 'divisor_type_allproper',
display => 'Divisor Type',
type => 'enum',
choices => ['all','proper'],
default => 'all',
description => 'Divisor type, with "proper" meaning divisors d<X, so excluding d=X itself.',
},
# { name => 'direction',
# share_key => 'direction_updown',
# display => 'Direction',
# type => 'enum',
# default => 'up',
# choices => ['up','down'],
# choices_display => ['Down','Up'],
# description => 'Number points upwards or downwards in the columns.',
# },
Math::PlanePath::Base::Generic::parameter_info_nstart0(),
];
use constant default_n_start => 0;
use constant class_x_negative => 0;
use constant class_y_negative => 0;
use constant n_frac_discontinuity => .5;
# X=2,Y=1 when proper
# X=1,Y=1 when not
sub x_minimum {
my ($self) = @_;
return ($self->{'proper'} ? 2 : 1);
}
use constant y_minimum => 1;
sub diffxy_minimum {
my ($self) = @_;
# octant Y<=X so X-Y>=0
return ($self->{'proper'} ? 1 : 0);
}
use constant dx_minimum => 0;
use constant dx_maximum => 1;
use constant dir_maximum_dxdy => (1,-1); # South-East
#------------------------------------------------------------------------------
sub new {
my $self = shift->SUPER::new (@_);
my $divisor_type = ($self->{'divisor_type'} ||= 'all');
$self->{'proper'} = ($divisor_type eq 'proper'); # bool
$self->{'direction'} ||= 'up';
if (! defined $self->{'n_start'}) {
$self->{'n_start'} = $self->default_n_start;
}
return $self;
}
my @x_to_n = (0,0,1);
sub _extend {
### _extend(): $#x_to_n
my $x = $#x_to_n;
push @x_to_n, $x_to_n[$x] + _count_divisors($x);
# if ($x > 2) {
# if (($x & 3) == 2) {
# $x >>= 1;
# $next_n += $x_to_n[$x] - $x_to_n[$x-1];
# } else {
# $next_n +=
# }
# }
### last x: $#x_to_n
### second last: $x_to_n[$#x_to_n-2]
### last: $x_to_n[$#x_to_n-1]
### diff: $x_to_n[$#x_to_n-1] - $x_to_n[$#x_to_n-2]
### divisors of: $#x_to_n - 2
### divisors: _count_divisors($#x_to_n-2)
### assert: $x_to_n[$#x_to_n-1] - $x_to_n[$#x_to_n-2] == _count_divisors($#x_to_n-2)
}
sub n_to_xy {
my ($self, $n) = @_;
### DivisibleColumns n_to_xy(): "$n"
$n = $n - $self->{'n_start'}; # to N=0 basis, and warn on undef
# $n<-0.5 works with Math::BigInt circa Perl 5.12, it seems
if ($n < -0.5) {
return;
}
if (is_infinite($n)) {
return ($n,$n);
}
my $frac;
{
my $int = int($n);
if ($n == $int) {
$frac = 0;
} else {
$frac = $n - $int; # -.5 <= $frac < 1
$n = $int; # BigFloat int() gives BigInt, use that
if ($frac > .5) {
$frac--;
$n += 1;
# now -.5 <= $frac < .5
}
}
### $n
### n: "$n"
### $frac
### assert: $frac >= -.5
### assert: $frac < .5
}
my $proper = $self->{'proper'} || 0; # cannot add false '' to BigInt
### $proper
my $x;
if ($proper) {
$x = 2;
### proper adjusted n: $n
} else {
$x = 1;
}
for (;;) {
while ($x > $#x_to_n) {
_extend();
}
$n += $proper;
### consider: "n=$n x=$x x_to_n=".$x_to_n[$x]
if ($x_to_n[$x] > $n) {
$x--;
last;
}
$x++;
}
$n -= $x_to_n[$x];
$n -= $proper;
### $x
### x_to_n: $x_to_n[$x]
### x_to_n next: $x_to_n[$x+1]
### remainder: $n
my $y = 1;
for (;;) {
unless ($x % $y) {
if (--$n < 0) {
return ($x, $frac + $y);
}
}
if (++$y > $x) {
### oops, not enough in this column
return;
}
}
}
# Feturn a count of the number of integers dividing $x, including 1 and $x
# itself. Cf Math::Factor::XS maybe.
sub _count_divisors {
my ($x) = @_;
my $ret = 1;
unless ($x % 2) {
my $count = 1;
do {
$x /= 2;
$count++;
} until ($x % 2);
$ret *= $count;
}
my $limit = _sqrtint($x);
for (my $d = 3; $d <= $limit; $d+=2) {
unless ($x % $d) {
my $count = 1;
do {
$x /= $d;
$count++;
} until ($x % $d);
$limit = sqrt($x);
$ret *= $count;
}
}
if ($x > 1) {
$ret *= 2;
}
return $ret;
}
sub xy_is_visited {
my ($self, $x, $y) = @_;
$x = round_nearest ($x);
$y = round_nearest ($y);
return ($y >= 1
&& ($self->{'proper'}
? $x >= 2 && $y <= int($x/2)
: $x >= 1 && $y <= $x)
&& ($x%$y) == 0);
}
sub xy_to_n {
my ($self, $x, $y) = @_;
### DivisibleColumns xy_to_n(): "$x,$y"
$x = round_nearest ($x);
$y = round_nearest ($y);
if (is_infinite($x)) { return $x; }
if (is_infinite($y)) { return $y; }
my $proper = $self->{'proper'};
if ($proper) {
if ($x < 2
|| $y < 1
|| $y > int($x/2)
|| ($x%$y)) {
return undef;
}
} else {
if ($x < 1
|| $y < 1
|| $y > $x
|| ($x%$y)) {
return undef;
}
}
while ($#x_to_n < $x) {
_extend();
}
### x_to_n: $x_to_n[$x]
my $n = $x_to_n[$x] - ($proper ? $x-1 : 1);
### base n: $n
for (my $i = 1+$proper; $i <= $y; $i++) {
unless ($x % $i) {
$n += 1;
}
}
return $n + $self->{'n_start'};
}
# not exact
sub rect_to_n_range {
my ($self, $x1,$y1, $x2,$y2) = @_;
### DivisibleColumns rect_to_n_range(): "$x1,$y1 $x2,$y2"
$x1 = round_nearest($x1);
$y1 = round_nearest($y1);
$x2 = round_nearest($x2);
$y2 = round_nearest($y2);
($x1,$x2) = ($x2,$x1) if $x1 > $x2;
($y1,$y2) = ($y2,$y1) if $y1 > $y2;
### rounded ...
### $x2
### $y2
if ($self->{'proper'}) {
if ($x2 < 2 # rect all negative
|| $y2 < 1 # rect all negative
|| 2*$y1 > $x2) { # rect all above X=2Y octant
### outside proper divisors ...
return (1, 0);
}
if ($x1 < 2) { $x1 = 2; }
} else {
if ($x2 < 1 # rect all negative
|| $y2 < 1 # rect all negative
|| $y1 > $x2) { # rect all above X=Y diagonal
### outside all divisors ...
return (1, 0);
}
if ($x1 < 1) { $x1 = 1; }
}
if (is_infinite($x2)) {
return ($self->{'n_start'}, $x2);
}
my ($n_lo, $n_hi);
if ($x1 <= $#x_to_n) {
$n_lo = $x_to_n[$x1];
} else {
$n_lo = _count_divisors_cumulative($x1-1);
}
if ($x2 < $#x_to_n) {
$n_hi = $x_to_n[$x2+1];
} else {
$n_hi = _count_divisors_cumulative($x2);
}
$n_hi -= 1;
### rect at: "x=".($x2+1)." x_to_n=".($x_to_n[$x2+1]||'none')
if ($self->{'proper'}) {
$n_lo -= $x1-1;
$n_hi -= $x2;
}
return ($n_lo + $self->{'n_start'},
$n_hi + $self->{'n_start'});
}
# Return a total count of all the divisors of all the integers 1 to $x
# inclusive.
sub _count_divisors_cumulative {
my ($x) = @_;
my $total = 0;
my $limit = _sqrtint($x);
foreach my $i (1 .. $limit) {
$total += int($x/$i);
}
return 2*$total - $limit*$limit;
}
1;
__END__
=for stopwords Ryde Math-PlanePath sqrt OEIS
=head1 NAME
Math::PlanePath::DivisibleColumns -- X divisible by Y in columns
=head1 SYNOPSIS
use Math::PlanePath::DivisibleColumns;
my $path = Math::PlanePath::DivisibleColumns->new;
my ($x, $y) = $path->n_to_xy (123);
=head1 DESCRIPTION
This path visits points X,Y where X is divisible by Y going by columns from
Y=1 to YE<lt>=X.
18 | 57
17 | 51
16 | 49
15 | 44
14 | 40
13 | 36
12 | 34
11 | 28
10 | 26
9 | 22 56
8 | 19 48
7 | 15 39
6 | 13 33 55
5 | 9 25 43
4 | 7 18 32 47
3 | 4 12 21 31 42 54
2 | 2 6 11 17 24 30 38 46 53
1 | 0 1 3 5 8 10 14 16 20 23 27 29 35 37 41 45 50 52
Y=0|
+---------------------------------------------------------
X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Starting N=0 at X=1,Y=1 means the values 1,3,5,8,etc horizontally on Y=1 are
the sums
i=K
sum numdivisors(i)
i=1
The current implementation is fairly slack and is slow on medium to large N.
=head1 Proper Divisors
C<divisor_type =E<gt> 'proper'> gives only proper divisors of X, meaning
that Y=X itself is excluded.
=cut
# math-image --path=DivisibleColumns,divisor_type=proper --output=numbers --all --size=134
=pod
9 | 39
8 | 33
7 | 26
6 | 22 38
5 | 16 29
4 | 11 21 32
3 | 7 13 20 28 37
2 | 3 6 10 15 19 25 31 36
1 | 0 1 2 4 5 8 9 12 14 17 18 23 24 27 30 34 35
Y=0|
+---------------------------------------------------------
X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
The pattern is the same, but the X=Y line skipped. The high line going up
is at Y=X/2, when X is even, that being the highest proper divisor.
=head2 N Start
The default is to number points starting N=0 as shown above. An optional
C<n_start> can give a different start with the same shape, For example
to start at 1,
=cut
# math-image --path=DivisibleColumns,n_start=1 --all --output=numbers --size=50x16
=pod
n_start => 1
9 | 23
8 | 20
7 | 16
6 | 14
5 | 10
4 | 8 19
3 | 5 13 22
2 | 3 7 12 18
1 | 1 2 4 6 9 11 15 17 21
Y=0|
+------------------------------
X=0 1 2 3 4 5 6 7 8 9
=head1 FUNCTIONS
See L<Math::PlanePath/FUNCTIONS> for behaviour common to all path classes.
=over 4
=item C<$path = Math::PlanePath::DivisibleColumns-E<gt>new ()>
=item C<$path = Math::PlanePath::DivisibleColumns-E<gt>new (divisor_type =E<gt> $str, n_start =E<gt> $n)>
Create and return a new path object. C<divisor_type> (a string) can be
"all" (the default)
"proper"
=item C<($x,$y) = $path-E<gt>n_to_xy ($n)>
Return the X,Y coordinates of point number C<$n> on the path. Points begin
at 0 and if C<$n E<lt> 0> then the return is an empty list.
=back
=head1 FORMULAS
=head2 Rectangle to N Range
The cumulative divisor count up to and including a given X column can be
calculated from the fairly well-known sqrt formula, a sum from 1 to sqrt(X).
S = floor(sqrt(X))
/ i=S \
numdivs cumulative = 2 * | sum floor(X/i) | - S^2
\ i=1 /
This means the N range for 0 to X can be calculated without working out all
each column count up to X. In the current code if column counts have been
worked out then they're used, otherwise this formula.
=head1 OEIS
This pattern is in Sloane's Online Encyclopedia of Integer Sequences in the
following forms,
=over
L<http://oeis.org/A061017> (etc)
=back
n_start=0 (the default)
A006218 N on Y=1 row, cumulative count of divisors
A077597 N on X=Y diagonal, cumulative count divisors - 1
n_start=1
A061017 X coord, each n appears countdivisors(n) times
A027750 Y coord, list divisors of successive k
A056538 X/Y, divisors high to low
divisor_type=proper (and default n_start=0)
A027751 Y coord divisor_type=proper, divisors of successive n
(extra initial 1)
divisor_type=proper, n_start=2
A208460 X-Y, being X subtract each proper divisor
A208460 has "offset" 2, hence C<n_start=2> to match that. The same with
all divisors would simply insert an extra 0 for the difference at X=Y.
=head1 SEE ALSO
L<Math::PlanePath>,
L<Math::PlanePath::CoprimeColumns>
=head1 HOME PAGE
L<http://user42.tuxfamily.org/math-planepath/index.html>
=head1 LICENSE
Copyright 2011, 2012, 2013, 2014, 2015, 2016 Kevin Ryde
Math-PlanePath is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.
Math-PlanePath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
Math-PlanePath. If not, see <http://www.gnu.org/licenses/>.
=cut