Algorithm-Networksort

 view release on metacpan or  search on metacpan

lib/Algorithm/Networksort.pm  view on Meta::CPAN

	inputradius => 2,
	compradius => 2,
	inputline => 2,
	compline => 2,
);

#
# Default parameters for text-based Knuth diagrams.
#
my %textset = (
	inputbegin => "o-",
	inputline => "---",
	inputcompline => "-|-",
	inputend => "-o\n",
	compbegin => "-^-",
	compend => "-v-",
	gapbegin => "  ",
	gapcompline => " | ",
	gapnone => "   ",
	gapend => "  \n",
);

#
# Default graphing color parameters.
#
my %colorset = (
	foreground => undef,
	inputbegin => undef,
	inputline => undef,
	inputend => undef,
	compbegin => undef,
	compline=> undef,
	compend => undef,
	background => undef,
);

has algorithm => (
	isa => 'Str', is => 'ro',
	default => 'bosenelson',
);

has inputs => (
	isa => 'Int', is => 'ro', required => 1,
);

has nwid => (
	isa => 'Str', is => 'rw', required => 0,
	predicate => 'has_nwid',
);

has comparators => (
	isa => 'ArrayRef[ArrayRef[Int]]', is => 'rw', required => 0,
	predicate => 'has_comparators',
);

has network => (
	isa => 'ArrayRef[ArrayRef[Int]]', is => 'rw', required => 0,
	predicate => 'has_network',
);

has ['depth', 'length'] => (
	isa => 'Int', is => 'rw', required => 0,
	init_arg => 0,
);

has creator => (
	isa => 'Str', is => 'ro', required => 0,
	default => sub { return "Perl module " . __PACKAGE__ .  ", " .
		"version $VERSION";}
);

has title => (
	isa => 'Str', is => 'rw', required => 0,
	predicate => 'has_title'
);

has formats => (
	isa => 'ArrayRef[Str]', is => 'rw', required => 0,
	init_arg => undef,
);

has grouped_format => (
	isa => 'Str', is => 'rw', required => 0,
	default => "%s,\n",
);

has index_base => (
	isa => 'ArrayRef[Value]', is => 'rw', required => 0,
);

#
# Variables to track sorting statistics
#
my $swaps = 0;

=pod

=encoding UTF-8

=head1 NAME

Algorithm::Networksort - Create Sorting Networks.

=begin html

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="84" viewbox="0 0 90 84">
  <title>Bose-Nelson Sort for N = 4</title>
  <defs>
    <g id="I_1c13" style="stroke-width:2; fill:#000; stroke:#000" >
      <line  x1="12" y1="0" x2="78" y2="0" />
    </g>

    <g id="C1_1c13" style="stroke-width:2;fill:#000; stroke:#000" >
      <line  x1="0" y1="0" x2="0" y2="14" />
      <circle  cx="0" cy="0" r="2" /> <circle  cx="0" cy="14" r="2" />
    </g>
    <g id="C2_1c13" style="stroke-width:2;fill:#000; stroke:#000" >
      <line  x1="0" y1="0" x2="0" y2="28" />
      <circle  cx="0" cy="0" r="2" /> <circle  cx="0" cy="28" r="2" />
    </g>

lib/Algorithm/Networksort.pm  view on Meta::CPAN

Use a naive odd-even transposition sort. This is a primitive sort closely
related to bubble sort except that it is more parallel. Because other
algorithms are more efficient, this sort is included for illustrative
purposes.

=item 'balanced'

This network is described in the 1983 paper "The Balanced Sorting Network"
by M. Dowd, Y. Perl, M Saks, and L. Rudolph. It is not a particularly
efficient sort but it has some interesting properties due to the fact
that it is constructed as a series of successive identical sub-blocks,
somewhat like with 'oddeventrans'.

=item 'none'

Do not generate a set of comparators. Instead, take the set from an
outside source, using the C<comparators> option.

    #
    # Test our own 5-input network.
    #
    @cmptr = ([1,2], [0,2], [0,1], [3,4], [0,3], [1,4], [2,4], [1,3], [2,3]);

    $nw = Algorithm::Networksort->new(inputs => 5,
                algorithm => 'none',
                comparators => [@cmptr]);

Internally, this is what L<nwsrt_best()|Algorithm::Networksort::Best/nwsrt_best()>
of L<Algorithm::Networksort::Best> uses.

=back

=item 'comparators'

The list of comparators provided by the user instead of by an algorithm.

=back

=cut

sub BUILD
{
	my $self = shift;
	my $alg = $self->algorithm();
	my $inputs = $self->inputs();

	my @network;
	my @grouped;

	#
	# Catch errors
	#
	croak "Input size must be 2 or greater" if ($inputs < 2);

	#
	# Providing our own-grown network?
	#
	if ($alg eq 'none')
	{
		croak "No comparators provided" unless ($self->has_comparators);
		$self->length(scalar @{ $self->comparators });

		#
		# Algorithm::Networksort::Best will set these, so
		# only go through with this if this is a user-provided
		# sequence of comparators.
		#
		unless ($self->has_network and $self->depth > 0)
		{
			@grouped = $self->group();
			$self->network($self->comparators);
			$self->depth(scalar @grouped);
			$self->network([map { @$_ } @grouped]);
			$self->title("Unknown $inputs-Inputs Comparator Set") unless ($self->has_title());
		}

		$self->nwid("nonalgorithmic-" . sprintf("%02d", $inputs)) unless ($self->has_nwid());

		return $self;
	}

	croak "Unknown algorithm '$alg'" unless (exists $algname{$alg});
	$self->nwid($alg . sprintf("%02d", $inputs));

	@network = bosenelson($inputs) if ($alg eq 'bosenelson');
	@network = hibbard($inputs) if ($alg eq 'hibbard');
	@network = batcher($inputs) if ($alg eq 'batcher');
	@network = bitonic($inputs) if ($alg eq 'bitonic');
	@network = bubble($inputs) if ($alg eq 'bubble');
	@network = oddeventransposition($inputs) if ($alg eq 'oddeventrans');
	@network = balanced($inputs) if ($alg eq 'balanced');
	@network = oddevenmerge($inputs) if ($alg eq 'oddevenmerge');

	$self->title($algname{$alg}  . " for N = " . $inputs) unless ($self->has_title);
	$self->length(scalar @network);
	$self->comparators(\@network);	# The 'raw' list of comparators.

	#
	# Re-order the comparator list using the parallel grouping for
	# the graphs. The resulting parallelism means less stalling
	# when used in a pipeline.
	#
	@grouped = $self->group();

	#
	###### @grouped
	#
	$self->depth(scalar @grouped);
	$self->network([map { @$_ } @grouped]);

	return $self;
}

=head3 comparators()

The comparators in their 'raw' form, as generated by its algorithm.

For the comparators re-ordered in such a way as to take advantage
of parallelism, see L<network()>.

=head3 network()

Returns the comparators re-ordered from the 'raw' form, providing a
parallelized version of the comparator list; e.g., the best order possible
to prevent stalling in a CPU's pipeline.

This is the form used when printing the sorting network using L<formats()>.

=cut

=head3 algorithm_name()

Return the full text name of the algorithm, given its key name.

=cut

sub algorithm_name
{
	my $self = shift;
	my $algthm = $_[0] // $self->algorithm();

	return $algname{$algthm} if (defined $algthm);
	return "";
}

#
# @network = hibbard($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# Translated from the ALGOL listed in T. N. Hibbard's article, A Simple
# Sorting Algorithm, Journal of the ACM 10:142-50, 1963.
#
# The ALGOL code was overly dependent on gotos.  This has been changed.
#
sub hibbard
{
	my $inputs = shift;
	my @comparators;
	my($bit, $xbit, $ybit);

	#
	# $t = ceiling(log2($inputs - 1)); but we'll
	# find it using the length of the bitstring.
	#
	my $t = length sprintf("%b", $inputs - 1);

	my $lastbit = 1 << $t;

	#
	# $x and $y are the comparator endpoints.
	# We begin with values of zero and one.
	#
	my($x, $y) = (0, 1);

	while (1 == 1)
	{
		#
		# Save the comparator pair, and calculate the next
		# comparator pair.
		#
		### hibbard() top of loop:
		##### @comparators
		#
		push @comparators, [$x, $y];

		#
		# Start with a check of X and Y's respective bits,
		# beginning with the zeroth bit.
		#
		$bit = 1;
		$xbit = $x & $bit;
	 	$ybit = $y & $bit;

		#
		# But if the X bit is 1 and the Y bit is
		# zero, just clear the X bit and move on.
		#
 		while ($xbit != 0 and $ybit == 0)
		{
			$x &= ~$bit;

			$bit <<= 1;
			$xbit = $x & $bit;
	 		$ybit = $y & $bit;
		}

 		if ($xbit != 0)		#  and $ybit != 0
		{
			$y &= ~$bit;
			next;
		}

		#
		# The X bit is zero if we've gotten this far.
		#
 		if ($ybit == 0)
		{
			$x |= $bit;
			$y |= $bit;
			$y &= ~$bit if ($y > $inputs - 1);
			next;
		}

		#
		# The X bit is zero, the Y bit is one, and we might
		# return the results.
		#
		do
		{
			return @comparators if ($bit == $lastbit);

			$x &= ~$bit;
			$y &= ~$bit;

			$bit <<= 1;	# Next bit.

			if ($y & $bit)
			{
				$x &= ~$bit;
				next;
			}

			$x |= $bit;
			$y |= $bit;
		} while ($y > $inputs - 1);

		#
		# No return, so loop onwards.
		#
		$bit = 1 if ($y < $inputs - 1);
		$x &= ~$bit;
		$y |= $bit;
	}
}

#
# @network = bosenelson($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# The Bose-Nelson algorithm.
#
sub bosenelson
{
	my $inputs = shift;

	return bn_split(0, $inputs);
}

#
# @comparators = bn_split($i, $length);
#
# The helper function that divides the range to be sorted.
#
# Note that the work of splitting the ranges is performed with the
# 'length' variables.  The $i variable merely acts as a starting
# base, and could easily have been 1 to begin with.
#
sub bn_split
{
	my($i,  $length) = @_;
	my @comparators = ();

	#
	### bn_split():
	#### $i
	#### $length
	#

	if ($length >= 2)
	{
		my $mid = $length >> 1;

		push @comparators, bn_split($i, $mid);
		push @comparators, bn_split($i + $mid, $length - $mid);
		push @comparators, bn_merge($i, $mid, $i + $mid, $length - $mid);
	}

	#
	### bn_split() returns
	##### @comparators
	#
	return @comparators;
}

#
# @comparators = bn_merge($i, $length_i, $j, $length_j);
#
# The other helper function that adds comparators to the list, for a
# given pair of ranges.
#
# As with bn_split, the different conditions all depend upon the
# lengths of the ranges.  The $i and $j variables merely act as
# starting bases.
#
sub bn_merge
{
	my($i, $length_i, $j, $length_j) = @_;
	my @comparators = ();

	#
	### bn_merge():
	#### $i
	#### $length_i
	#### $j
	#### $length_j
	#
	if ($length_i == 1 && $length_j == 1)
	{
		push @comparators, [$i, $j];
	}
	elsif ($length_i == 1 && $length_j == 2)
	{
		push @comparators, [$i, $j + 1];
		push @comparators, [$i, $j];
	}
	elsif ($length_i == 2 && $length_j == 1)
	{
		push @comparators, [$i, $j];
		push @comparators, [$i + 1, $j];
	}
	else
	{
		my $i_mid = int($length_i/2);
		my $j_mid = int(($length_i & 1)? $length_j/2: ($length_j + 1)/2);

		push @comparators, bn_merge($i, $i_mid, $j, $j_mid);
		push @comparators, bn_merge($i + $i_mid, $length_i - $i_mid, $j + $j_mid, $length_j - $j_mid);
		push @comparators, bn_merge($i + $i_mid, $length_i - $i_mid, $j, $j_mid);
	}

	#
	### bn_merge() returns
	##### @comparators
	#
	return @comparators;
}

#
# @network = batcher($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# Batcher's sort as laid out in Knuth, Sorting and Searching, algorithm 5.2.2M.
#
sub batcher
{
	my $inputs = shift;
	my @network;

	#
	# $t = ceiling(log2($inputs)); but we'll
	# find it using the length of the bitstring.
	#
	my $t = length sprintf("%b", $inputs);

	my $p = 1 << ($t -1);

	while ($p > 0)
	{
		my $q = 1 << ($t -1);
		my $r = 0;
		my $d = $p;

		while ($d > 0)
		{
			for my $i (0 .. $inputs - $d - 1)
			{
				push @network, [$i, $i + $d] if (($i & $p) == $r);
			}

			$d = $q - $p;
			$q >>= 1;
			$r = $p;
		}
		$p >>= 1;
	}

	return @network;
}



#
# @network = bitonic($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# Batcher's Bitonic sort as described here:
# http://www.iti.fh-flensburg.de/lang/algorithmen/sortieren/bitonic/oddn.htm
#
sub bitonic
{
	my $inputs = shift;
	my @network;

	my ($sort, $merge);

	$sort = sub {
		my ($lo, $n, $dir) = @_;

		if ($n > 1) {
			my $m = $n >> 1;
			$sort->($lo, $m, !$dir);
			$sort->($lo + $m, $n - $m, $dir);
			$merge->($lo, $n, $dir);
		}
	};

	$merge = sub {
		my ($lo, $n, $dir) = @_;

		if ($n > 1) {
			#
			# $t = ceiling(log2($n - 1)); but we'll
			# find it using the length of the bitstring.
			#
			my $t = length sprintf("%b", $n - 1);

			my $m = 1 << ($t - 1);

			for my $i ($lo .. $lo+$n-$m-1)
			{
				push @network, ($dir)? [$i, $i+$m]: [$i+$m, $i];
			}

			$merge->($lo, $m, $dir);
			$merge->($lo + $m, $n - $m, $dir);
		}
	};

	$sort->(0, $inputs, 1);

	return @{ make_network_unidirectional(\@network) };
}


## This function "re-wires" a bi-directional sorting network
## and turns it into a normal, uni-directional network.

sub make_network_unidirectional
{
	my ($network_ref) = @_;

	my @network = @$network_ref;

	for my $i (0..$#network) {
		my $comparator = $network[$i];
		my ($x, $y) = @$comparator;

		if ($x > $y) {
			for my $j (($i+1)..$#network) {
				my $j_comparator = $network[$j];
				my ($j_x, $j_y) = @$j_comparator;

				$j_comparator->[0] = $y if $x == $j_x;
				$j_comparator->[1] = $y if $x == $j_y;
				$j_comparator->[0] = $x if $y == $j_x;
				$j_comparator->[1] = $x if $y == $j_y;
			}
			($comparator->[0], $comparator->[1]) = ($comparator->[1], $comparator->[0]);
		}
	}

	return \@network;
}

#
# @network = bubble($inputs);
#
# Simple bubble sort network, only for comparison purposes.
#
sub bubble
{
	my $inputs = shift;
	my @network;

	for my $j (reverse 0 .. $inputs - 1)
	{
		push @network, [$_, $_ + 1] for (0 .. $j - 1);
	}

	return @network;
}

#
# @network = bubble($inputs);
#
# Simple odd-even transposition network, only for comparison purposes.
#
sub oddeventransposition
{
	my $inputs = shift;
	my @network;

	my $odd;

	for my $stage (0 .. $inputs - 1)
	{
		for (my $j = $odd ? 1 : 0; $j < $inputs - 1; $j += 2)
		{
			push @network, [$j, $j+1];
		}

		$odd = !$odd;
	}

	return @network;
}

#
# @network = balanced($inputs);
#
# "The Balanced Sorting Network" by M. Dowd, Y. Perl, M Saks, and L. Rudolph
# ftp://ftp.cs.rutgers.edu/cs/pub/technical-reports/pdfs/DCS-TR-127.pdf
#
sub balanced
{
	my $inputs = shift;
	my @network;

	#
	# $t = ceiling(log2($inputs - 1)); but we'll
	# find it using the length of the bitstring.
	#
	my $t = length sprintf("%b", $inputs - 1);

	for (1 .. $t)
	{
		for (my $curr = 1 << $t; $curr > 1; $curr >>= 1)
		{
			for (my $i = 0; $i < (1 << $t); $i += $curr)
			{
				for (my $j = 0; $j < int($curr/2); $j++)
				{
					my $wire1 = $i+$j;
					my $wire2 = $i+$curr-$j-1;
					push @network, [$wire1, $wire2]
						if $wire1 < $inputs && $wire2 < $inputs;
				}
			}
		}
	}

	return @network;
}

#
# @network = oddevenmerge($inputs);
#
# Batcher's odd-even merge sort as described here:
# http://www.iti.fh-flensburg.de/lang/algorithmen/sortieren/networks/oemen.htm
# http://cs.engr.uky.edu/~lewis/essays/algorithms/sortnets/sort-net.html
#
sub oddevenmerge
{
	my $inputs = shift;
	my @network;

	#
	# $t = ceiling(log2($inputs - 1)); but we'll
	# find it using the length of the bitstring.
	#
	my $t = length sprintf("%b", $inputs - 1);

	my ($add_elem, $sort, $merge);

	$add_elem = sub {
		my ($i, $j) = @_;

		push @network, [$i, $j]
			if $i < $inputs && $j < $inputs;
	};

	$sort = sub {
		my ($lo, $n) = @_;

		if ($n > 1)
		{
			my $m = int($n / 2);

			$sort->($lo, $m);
			$sort->($lo + $m, $m);
			$merge->($lo, $n, 1);
		}
	};

	$merge = sub {
		my ($lo, $n, $r) = @_;

		my $m = int($r * 2);

		if ($m < $n)
		{
			$merge->($lo, $n, $m); # even
			$merge->($lo + $r, $n, $m); # odd

			for (my $i=$lo + $r; $i + $r < $lo + $n; $i += $m)
			{
				$add_elem->($i, $i + $r);
			}
		}
		else
		{
			$add_elem->($lo, $lo + $r);
		}
	};

	$sort->(0, 1 << $t);

	return @network;
}

#
# $array_ref = $nw->sort(\@array);
#
# Use the network of comparators to sort the elements in the
# array.  Returns the reference to the array, which is sorted
# in-place.
#
# This function is for testing and statistical purposes only, as
# interpreting sorting pairs ad hoc in an interpreted language is
# going to be very slow.
#

lib/Algorithm/Networksort.pm  view on Meta::CPAN

	#
	my(@formats) = $self->formats? @{ $self->formats() }: ();
	unless (scalar @formats)
	{
		$string = $self->_dflt_formatted($network);
		chop $string;		# Remove trailing comma

		return '[' . $string . ']';
	}

	my $index_base = $self->index_base();

	for my $cmptr (@$network)
	{
		@$cmptr = @$index_base[@$cmptr] if (defined $index_base);

		for my $fmt (@formats)
		{
			$string .= sprintf($fmt, @$cmptr);
		}
	}

	return $string;
}

=head3 group()

Takes the comparator list and returns a list of comparator lists, each
sub-list representing a group of comparators that can be operate without
interfering with each other, depending on what is needed for
interference-free grouping.

There is one option available, 'grouping', that will produce a grouping
that represents parallel operations of comparators. Its values may be:

=over 3

=item 'graph'

Group the comparators as parallel as possible for graphing.

=item 'parallel'

Arrange the sequence in parallel so that it has a minimum depth. This,
after flattening the lists into a single list again, is what is used to
produce the sequence in L<network()>.

=item I<number>

Arrange the comparators every I<number> count. This is for simple
printing of the comparators, without any grouping for graphing or
maximum parallelization.

=back

The chances that you will need to use this function are slim, but the
following code snippet may represent an example:

    my $nw = Algorithm::Networksort->new(inputs => 8, algorithm => 'batcher');

    print "There are ", $nw->length(),
        " comparators in this network, grouped into\n",
        $nw->depth(), " parallel operations.\n\n";

    print $nw, "\n";

    my @grouped_network = $nw->group(grouping=>'graph');
    print "\nThis will be graphed in ", scalar @grouped_network,
        " columns.\n";

This will produce:

    There are 19 comparators in this network, grouped into 6 parallel operations.

    [[0,4], [1,5], [2,6], [3,7]]
    [[0,2], [1,3], [4,6], [5,7]]
    [[2,4], [3,5], [0,1], [6,7]]
    [[2,3], [4,5]]
    [[1,4], [3,6]]
    [[1,2], [3,4], [5,6]]

    This will be graphed in 11 columns.

=cut

sub group
{
	my $self = shift;
	my $network = $self->comparators;
	my $inputs = $self->inputs;
	my %opts = @_;

	my @node_range_stack;
	my @node_stack;
	my $grp = $opts{grouping} // 'parallel';
 
	#
	# Group the comparator nodes by N.
	#
	if ($grp =~ /^[0-9]+$/)
	{
		my @s = @$network;
		while (scalar @s)
		{
			push @node_stack, [splice(@s, 0, $grp)];
		}
		return @node_stack;
	}

	unless ($grp =~ /^(graph|parallel)$/)
	{
		carp "Unknown option '$grp'";
		return undef;
	}

	#
	# Group the comparator nodes by columns.
	#
	for my $comparator (@$network)
	{
		my($from, $to) = @$comparator;

lib/Algorithm/Networksort.pm  view on Meta::CPAN

	my @vcoord = ($grset{vt_margin}) x $inputs;

	for my $idx (0..$inputs-1)
	{
		$vcoord[$idx] += $idx * ($grset{vt_sep} + $grset{inputline});
	}

	return @vcoord;
}

sub colorswithdefaults
{
	#
	# Get the colorset, using the foreground color as the default color
	# for drawing, except for 'background', which has its own default.
	#
	my @keylist = grep{$_ !~ /background/} keys %colorset;
	my %clrset = map{$_ => ($colorset{$_} // $colorset{foreground} // '#000')}
		@keylist;
	$clrset{background} = $colorset{background};

	#
	#### colorset after defaults are set: %clrset
	#
	return %clrset;
}

#
# Helper function to see if the drawing colors are all identical.
# The hash is presumed to be one returned by colorswithdefaults().
#
sub ismonotone
{
	my (%clrs) = @_;

	my @keylist = grep{$_ !~ /foreground|background/} keys %colorset;

	return !scalar grep{$clrs{$_} ne $clrs{foreground}} @keylist;
}

#
# For the postscript setrgbcolor operator.
#
sub psrgbcolor
{
	my $color = $_[0] // '000';

	#
	# A postscript user might have this in an 'rr gg bb' format,
	# or not bother with the '#'.
	#
	$color =~ s/ //g;
	$color =~ s/^#//;

	if ($color =~ /[^0-9a-fA-F]/)
	{
		carp "Color '$color' is not in six or three hexadecimal digit RGB form.";
		$color = "000";
	}

	if (length $color == 3)
	{
		$color =~ s/(.)(.)(.)/$1$1 $2$2 $3$3/;
	}
	elsif (length $color == 6)
	{
		$color =~ s/(..)(..)(..)/$1 $2 $3/;
	}
	else
	{
		carp "Color '$color' is not in six or three hexadecimal digit RGB form.";
		$color = "0 0 0";
	}

	return join(" ", map{hex()/256} split(/ /, $color));
}

=head2 Methods For Graphing

=head3 graph_eps()

Returns a string that graphs out the network's comparators. The format
will be encapsulated postscript.

    my $nw = nwsrt(inputs = 4, algorithm => 'bitonic');

    print $nw->graph_eps();

=cut

sub graph_eps
{
	my $self = shift;
	my $network = $self->network();
	my $inputs = $self->inputs();
	my %grset = $self->graphsettings();

	my @node_stack = $self->group(grouping => 'graph');
	my $columns = scalar @node_stack;

	#
	# Set up the vertical and horizontal coordinates.
	#
	my @vcoord = vt_coords($inputs, %grset);
	my @hcoord = hz_coords($columns, %grset);

	my $xbound = $hcoord[$columns - 1] + $grset{hz_margin} + $grset{indent};
	my $ybound = $vcoord[$inputs - 1] + $grset{vt_margin};
	my $i_radius = $grset{inputradius};
	my $c_radius = $grset{compradius};

	my %clrset = colorswithdefaults();
	my $monotone = ismonotone(%clrset);

	map{$clrset{$_} = psrgbcolor($clrset{$_}) . " setrgbcolor"} keys %clrset;

	#
	# Create the necessary DSC, the arrays of vertical
	# and horizontal coordinates, and the left and right
	# margin definitions.
	#
	my $string =
		qq(%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 $xbound $ybound\n) .
		qq(%%CreationDate: ) .  localtime() .
		qq(\n%%Creator: ) . $self->creator() .



( run in 1.031 second using v1.01-cache-2.11-cpan-56fb94df46f )