File Coverage

blib/lib/Math/SimpleHisto/XS.pm
Criterion Covered Total %
statement 117 144 81.2
branch 49 78 62.8
condition 6 15 40.0
subroutine 14 15 93.3
pod 3 6 50.0
total 189 258 73.2


line stmt bran cond sub pod time code
1             package Math::SimpleHisto::XS;
2 11     11   289289 use 5.008001;
  11         38  
  11         425  
3 11     11   79 use strict;
  11         30  
  11         688  
4 11     11   59 use warnings;
  11         26  
  11         352  
5 11     11   60 use Carp qw(croak);
  11         16  
  11         12244  
6              
7             our $VERSION = '1.30'; # Committed to floating point version numbers!
8              
9             require XSLoader;
10             XSLoader::load('Math::SimpleHisto::XS', $VERSION);
11              
12             require Math::SimpleHisto::XS::RNG;
13              
14             require Exporter;
15             our @ISA = qw(Exporter);
16             our @EXPORT_OK = qw(
17             INTEGRAL_CONSTANT
18             );
19             #INTEGRAL_POL1
20              
21             our %EXPORT_TAGS = (
22             'all' => \@EXPORT_OK,
23             );
24              
25             our @JSON_Modules = qw(JSON::XS JSON::PP JSON);
26             our $JSON_Implementation;
27             our $JSON;
28              
29             foreach my $json_module (@JSON_Modules) {
30             if (eval "require $json_module; 1;") {
31             $JSON = $json_module->new;
32             $JSON->indent(0) if $JSON->can('indent');
33             $JSON->space_before(0) if $JSON->can('space_before');
34             $JSON->space_after(0) if $JSON->can('space_after');
35             $JSON->canonical(0) if $JSON->can('canonical');
36             $JSON_Implementation = $json_module;
37             last if $JSON;
38             }
39             }
40              
41             sub new {
42 26     26 1 40911 my $class = shift;
43 26         124 my %opt = @_;
44              
45 26 100       131 if (defined $opt{bins}) {
46 10         22 my $bins = $opt{bins};
47 10 50       46 croak("Cannot combine the 'bins' parameter with other parameters") if keys %opt > 1;
48 10 50 33     153 croak("The 'bins' parameter needs to be a reference to an array of bins")
      33        
49             if not ref($bins)
50             or not ref($bins) eq 'ARRAY'
51             or not @$bins > 1;
52 10         125 return $class->_new_histo_bins($bins);
53             }
54             else {
55 16         41 foreach (qw(min max nbins)) {
56 48 50       153 croak("Need parameter '$_'") if not defined $opt{$_};
57             }
58             }
59              
60 16         279 return $class->_new_histo(@opt{qw(nbins min max)});
61             }
62              
63             # See ExtUtils::Constant
64             sub AUTOLOAD {
65             # This AUTOLOAD is used to 'autoload' constants from the constant()
66             # XS function.
67              
68 1     1   757 my $constname;
69 1         2 our $AUTOLOAD;
70 1         7 ($constname = $AUTOLOAD) =~ s/.*:://;
71 1 50       4 croak('&' . __PACKAGE__ . "::constant not defined") if $constname eq 'constant';
72 1         5 my ($error, $val) = constant($constname);
73 1 50       2 if ($error) { croak($error); }
  0         0  
74             {
75 11     11   65 no strict 'refs';
  11         21  
  11         1108  
  1         2  
76 1     4   8 *$AUTOLOAD = sub { $val };
  4         836  
77             }
78 1         6 goto &$AUTOLOAD;
79             }
80              
81              
82 11     11   61 use constant _PACK_FLAG_VARIABLE_BINS => 0;
  11         21  
  11         19447  
83              
84             my $native_pack_len;
85             SCOPE: {
86             require bytes;
87             my $intlen = bytes::length(pack('I', 0));
88             $native_pack_len = 8 + 4 + 16 + 4 + $intlen*2 + 16;
89             }
90              
91             sub dump {
92 11     11 1 108073 my $self = shift;
93 11         32 my $type = shift;
94 11         37 $type = lc($type);
95              
96 11         107 my ($min, $max, $nbins, $nfills, $overflow, $underflow, $data_ary, $bins_ary)
97             = $self->_get_info;
98              
99 11 100 66     91 if ($type eq 'simple') {
    100          
    50          
100 7 100       298 return join(
101             ';',
102             $VERSION,
103             $min, $max, $nbins,
104             $nfills, $overflow, $underflow,
105             join('|', @$data_ary),
106             (defined($bins_ary) ? join('|', @$bins_ary) : ''),
107             );
108             }
109             elsif ($type eq 'json' or $type eq 'yaml') {
110 2         24 my $struct = {
111             version => $VERSION,
112             min => $min, max => $max, nbins => $nbins,
113             nfills => $nfills, overflow => $overflow, underflow => $underflow,
114             data => $data_ary,
115             };
116 2 100       10 $struct->{bins} = $bins_ary if defined $bins_ary;
117              
118 2 50       7 if ($type eq 'json') {
119 2 50       7 if (not defined $JSON) {
120 0         0 die "Cannot use JSON dump mode since no JSON handling module could be loaded: "
121             . join(', ', @JSON_Modules);
122             }
123 2         15 return $JSON->encode($struct);
124             }
125             else { # type eq yaml
126 0         0 require YAML::Tiny;
127 0         0 return YAML::Tiny::Dump($struct);
128             }
129             }
130             elsif ($type eq 'native_pack') {
131 2         12 my $flags = 0;
132 2 100       14 vec($flags, _PACK_FLAG_VARIABLE_BINS, 1) = $bins_ary ? 1 : 0;
133              
134 2 100       7 my $len = $native_pack_len + 8 * (scalar(@$data_ary) + scalar(@{$bins_ary || []}));
  2         19  
135 2 100       32 return pack(
136             'd V d2 V I2 d2 d*',
137             $VERSION,
138             $len,
139             $min, $max,
140             $flags,
141             $nbins,
142             $nfills, $overflow, $underflow,
143             @$data_ary,
144 2         7 @{$bins_ary || []}
145             );
146             }
147             else {
148 0         0 croak("Unknown dump type: '$type'");
149             }
150 0         0 die "Must not be reached";
151             }
152              
153              
154             sub _check_version {
155 11     11   21 my $version = shift;
156 11         23 my $type = shift;
157 11 50       83 if (not $version) {
    50          
158 0         0 croak("Invalid '$type' dump format");
159             }
160             elsif ($VERSION-$version < -1.) {
161 0         0 croak("Dump was generated with an incompatible newer version ($version) of this module ($VERSION)");
162             }
163             }
164              
165             sub new_from_dump {
166 11     11 1 3727 my $class = shift;
167 11         29 my $type = shift;
168 11         21 my $dump = shift;
169 11         26 $type = lc($type);
170              
171 11 50       48 croak("Need dump string") if not defined $dump;
172              
173 11         27 my $version;
174             my $hashref;
175 11 100       53 if ($type eq 'simple') {
    100          
    50          
    50          
176 7         62 ($version, my @rest) = split /;/, $dump, -1;
177 7         18 my $nexpected = 9;
178              
179 7         27 _check_version($version, 'simple');
180 7 50       51 if ($version <= 1.) { # no bins array in VERSION < 1
    50          
181 0         0 $nexpected--;
182             }
183             elsif (@rest != $nexpected-1) {
184 0         0 croak("Invalid 'simple' dump format, wrong number of elements in top level structure");
185             }
186              
187             $hashref = {
188 7         119 min => $rest[0], max => $rest[1], nbins => $rest[2],
189             nfills => $rest[3], overflow => $rest[4], underflow => $rest[5],
190             data => [split /\|/, $rest[6]]
191             };
192 7 100 66     80 if ($version >= 1. and $rest[7] ne '') {
193 3         30 $hashref->{bins} = [split /\|/, $rest[7]];
194             }
195             }
196             elsif ($type eq 'json') {
197 2 50       7 if (not defined $JSON) {
198 0         0 die "Cannot use JSON dump mode since no JSON handling module could be loaded: "
199             . join(', ', @JSON_Modules);
200             }
201 2         10 $hashref = $JSON->decode($dump);
202 2         3633 $version = $hashref->{version};
203 2         8 _check_version($version, 'json');
204 2 50       8 croak("Invalid JSON dump, not a hashref") if not ref($hashref) eq 'HASH';
205             }
206             elsif ($type eq 'yaml') {
207 0         0 require YAML::Tiny;
208 0         0 my @docs = YAML::Tiny::Load($dump);
209 0 0 0     0 if (@docs != 1 or not ref($docs[0]) eq 'HASH') {
210 0         0 croak("Invalid YAML dump, not a single YAML document or not containing a hashref");
211             }
212 0         0 $hashref = $docs[0];
213 0         0 $version = $hashref->{version};
214 0         0 _check_version($version, 'yaml');
215             }
216             elsif ($type eq 'native_pack') {
217 2         10 my $version = unpack('d', $dump);
218 2         9 _check_version($version, 'native_pack');
219 2         6 my $flags_support = $version >= 1.;
220 2         5 my $prepended_length = $version >= 1.28;
221 2         4 my $ndoubles;
222              
223             # We go through all this pain about the length and the number of elements in the packed
224             # dump because that'll allow us to prevent reading beyond the end of a given dump.
225 2 50       8 if ($prepended_length) {
226 2         9 (undef, my $len) = unpack('d V', $dump);
227 2         3 $len -= $native_pack_len;
228 2         6 $ndoubles = $len / 8;
229             }
230 2 50       34 my $pack_str = $flags_support
    50          
231             ? ($prepended_length ? "d V d2 V I2 d2 d$ndoubles" : 'd3 V I2 d2 d*')
232             : 'd3 I2 d2 d*';
233              
234 2         16 my @things = unpack($pack_str, $dump);
235 2         6 $version = shift @things;
236 2         14 $hashref = {version => $version};
237              
238 2 50       206 foreach (($prepended_length ? ('data_length') : ()),
    50          
239             qw(min max),
240             ($flags_support ? ('flags') : ()),
241             qw(nbins nfills overflow underflow))
242             {
243 16         39 $hashref->{$_} = shift(@things);
244             }
245              
246 2 50       9 if ($flags_support) {
247 2         6 my $flags = delete $hashref->{flags};
248 2 100       9 if (vec($flags, _PACK_FLAG_VARIABLE_BINS, 1)) {
249 1         6 $hashref->{bins} = [splice(@things, $hashref->{nbins})];
250             }
251             }
252              
253 2         10 $hashref->{data} = \@things;
254             }
255             else {
256 0         0 croak("Unknown dump type: '$type'");
257             }
258              
259 11         31 my $self;
260 11 100       36 if (defined $hashref->{bins}) {
261 5         28 $self = $class->new(bins => $hashref->{bins});
262             }
263             else {
264 6         35 $self = $class->new(
265             min => $hashref->{min},
266             max => $hashref->{max},
267             nbins => $hashref->{nbins},
268             );
269             }
270              
271 11         53 $self->set_nfills($hashref->{nfills});
272 11         41 $self->set_overflow($hashref->{overflow});
273 11         35 $self->set_underflow($hashref->{underflow});
274 11         66 $self->set_all_bin_contents($hashref->{data});
275              
276 11         61 return $self;
277             }
278              
279              
280             sub STORABLE_freeze {
281 4     4 0 97417 my $self = shift;
282 4         13 my $cloning = shift;
283 4         21 my $serialized = $self->dump('simple');
284 4         579 return $serialized;
285             }
286              
287             sub STORABLE_thaw {
288 4     4 0 63 my $self = shift;
289 4         9 my $cloning = shift;
290 4         11 my $serialized = shift;
291 4         26 my $new = ref($self)->new_from_dump('simple', $serialized);
292 4         14 $$self = $$new;
293             # Pesky DESTROY :P
294 4         18 bless($new => 'Math::SimpleHisto::XS::Doesntexist');
295 4         38 $new = undef;
296             }
297              
298             sub to_soot {
299 0     0 0   my ($self, $name, $title) = @_;
300 0 0         $name = '' if not defined $name;
301 0 0         $title = '' if not defined $title;
302              
303 0           require SOOT;
304 0           my $th1d = TH1D->new($name, $title, $self->nbins, $self->min, $self->max);
305 0           $th1d->SetBinContent($_, $self->bin_content($_-1)) for 1..$self->nbins;
306 0           $th1d->SetEntries($self->nfills);
307              
308 0           return $th1d;
309             }
310              
311             1;
312             __END__