File Coverage

blib/lib/Bio/RNA/Treekin/Record.pm
Criterion Covered Total %
statement 110 157 70.0
branch 28 40 70.0
condition 2 5 40.0
subroutine 24 34 70.5
pod 13 14 92.8
total 177 250 70.8


line stmt bran cond sub pod time code
1             # Bio/RNA/Treekin/Record.pm
2              
3             # Stores a data from a single row of the Treekin file, i.e. the populations of
4             # all minima at a given time point.
5             package Bio::RNA::Treekin::Record;
6             our $VERSION = '0.05';
7              
8 4     4   53 use v5.14; # required for non-destructive subst m///r
  4         15  
9 4     4   20 use strict;
  4         7  
  4         96  
10 4     4   23 use warnings;
  4         8  
  4         114  
11              
12 4     4   2407 use Moose;
  4         1943792  
  4         30  
13 4     4   36028 use MooseX::StrictConstructor;
  4         129236  
  4         17  
14 4     4   42001 use namespace::autoclean;
  4         13  
  4         29  
15              
16 4     4   2129 use autodie qw(:all);
  4         47786  
  4         24  
17 4     4   83003 use Scalar::Util qw(reftype openhandle);
  4         10  
  4         334  
18 4     4   28 use List::Util qw(first pairmap max uniqnum all);
  4         8  
  4         305  
19 4     4   26 use Carp qw(croak);
  4         18  
  4         183  
20              
21 4     4   2533 use Bio::RNA::Treekin::PopulationDataRecord;
  4         21  
  4         293  
22              
23 4     4   38 use overload '""' => \&stringify;
  4         10  
  4         46  
24              
25              
26             has '_population_data' => (
27             is => 'ro',
28             required => 1,
29             init_arg => 'population_data',
30             );
31              
32             has 'date' => (is => 'ro', required => 1);
33             has 'sequence' => (is => 'ro', required => 1);
34             has 'method' => (is => 'ro', required => 1);
35             has 'start_time' => (is => 'ro', required => 1);
36             has 'stop_time' => (is => 'ro', required => 1);
37             has 'temperature' => (is => 'ro', required => 1);
38             has 'basename' => (is => 'ro', required => 1);
39             has 'time_increment' => (is => 'ro', required => 1);
40             has 'degeneracy' => (is => 'ro', required => 1);
41             has 'absorbing_state' => (is => 'ro', required => 1);
42             has 'states_limit' => (is => 'ro', required => 1);
43              
44             # Add optional attributes including predicate.
45             has $_ => (
46             is => 'ro',
47             required => 0,
48             predicate => "has_$_",
49             )
50             foreach qw(
51             info
52             init_population
53             rates_file
54             file_index
55             cmd
56             of_iterations
57             );
58              
59             # Get number of population data rows stored.
60             sub population_data_count {
61 2     2 1 4412 my ($self) = @_;
62              
63 2         4 my $data_count = @{ $self->_population_data };
  2         128  
64 2         11 return $data_count;
65             }
66              
67             # Number of states / minima in this simulation.
68             # Get number of mins in the first population record; it should be the
69             # same for all records.
70             sub min_count {
71 15     15 1 27 my $self = shift;
72              
73 15         49 my $first_pop = $self->population(0);
74 15 50       42 confess 'min_count: no population data present'
75             unless defined $first_pop;
76              
77 15         48 my $min_count = $first_pop->min_count;
78              
79 15         53 return $min_count;
80             }
81              
82             # Return a list of all minima, i. e. 1..n, where n is the total number of
83             # minima.
84             sub mins {
85 0     0 1 0 my ($self) = @_;
86 0         0 my @mins = 1..$self->min_count;
87              
88 0         0 return @mins;
89             }
90              
91             # Keep only the population data for the selected minima, remove all other.
92             # Will NOT rescale populations, so they may no longer sum up to 1.
93             # Arguments:
94             # mins: List of mins to keep. Will be sorted and uniq'ed (cf. splice()).
95             # Returns the return value of splice().
96             sub keep_mins {
97 0     0 1 0 my ($self, @kept_mins) = @_;
98 0         0 @kept_mins = uniqnum sort {$a <=> $b} @kept_mins; # sort / uniq'ify
  0         0  
99 0         0 return $self->splice_mins(@kept_mins);
100             }
101              
102             # Keep only the population data for the selected minima, remove all other.
103             # May duplicate and re-order.
104             # mins: List of mins to keep. Will be used as is.
105             # Returns itself.
106             sub splice_mins {
107 0     0 1 0 my ($self, @kept_mins) = @_;
108              
109 0         0 my $min_count = $self->min_count;
110             confess 'Cannot splice, minimum out of bounds'
111 0 0   0   0 unless all {$_ >= 1 and $_ <= $min_count} @kept_mins;
  0 0       0  
112              
113             # Directly update raw population data here instead of doing tons of
114             # calls passing the same min array.
115 0         0 my @kept_indices = map {$_ - 1} @kept_mins;
  0         0  
116 0         0 for my $pop_data (@{$self->_population_data}) { # each point in time
  0         0  
117 0         0 my $raw_pop_data = $pop_data->_populations;
118 0         0 @{$raw_pop_data} = @{$raw_pop_data}[@kept_indices];
  0         0  
  0         0  
119             }
120              
121 0         0 return $self;
122             }
123              
124             # Get the maximal population for the given minimum over all time points.
125             sub max_pop_of_min {
126 0     0 1 0 my ($self, $min) = @_;
127 0         0 my $max_pop = '-Inf';
128 0         0 for my $pop_data (@{$self->_population_data}) { # each point in time
  0         0  
129 0         0 $max_pop = max $max_pop, $pop_data->of_min($min); # update max
130             }
131 0         0 return $max_pop;
132             }
133              
134             # For a given minimum, return all population values in chronological
135             # order.
136             # Arguments:
137             # min: Minimum for which to collect the population data.
138             # Returns a list of population values in chronological order.
139             sub pops_of_min {
140 0     0 1 0 my ($self, $min) = @_;
141              
142 0         0 my @pops_of_min = map { $_->of_min($min) } $self->populations;
  0         0  
143              
144 0         0 return @pops_of_min;
145             }
146              
147             # Final population data record, i.e. the result of the simulation.
148             sub final_population {
149 0     0 1 0 my ($self) = @_;
150              
151 0         0 my $final_population_data
152             = $self->population($self->population_data_count - 1);
153              
154 0         0 return $final_population_data;
155             }
156              
157             # Get the i-th population data record (0-based indexing).
158             sub population {
159 52     52 1 22164 my ($self, $i) = @_;
160              
161 52         1974 my $population_record = $self->_population_data->[$i];
162 52         130 return $population_record;
163             }
164              
165             # Return all population data records.
166             sub populations {
167 0     0 1 0 return @{ $_[0]->_population_data };
  0         0  
168             }
169              
170             # Add a new minimum with all-zero entries. Data can then be appended to
171             # this new min.
172             # Returns the index of the new minimum.
173             sub add_min {
174 0     0 1 0 my $self = shift;
175 0         0 my $new_min_count = $self->min_count + 1;
176              
177             # Increase the min count of all population data records by one.
178             $_->set_min_count($new_min_count)
179 0         0 foreach @{ $self->_population_data }, $self->init_population;
  0         0  
180              
181 0         0 return $new_min_count; # count == highest index
182             }
183              
184             # Given a list of population data records, append them to the population data
185             # of this record. The columns of the added data can be re-arranged on the fly by
186             # providing a mapping (hash ref) giving for each minimum in the population
187             # data to be added (key) a minimum in the current population data
188             # (value) to which the new minimum should be swapped. If no data is provided
189             # for some minimum of this record, its population is set to zero in the
190             # newly added entries.
191             # Arguments:
192             # pop_data_ref: ref to the array of population data to be added
193             # append_to_min_ref:
194             # hash ref describing which mininum in pop_data_ref (key)
195             # should be mapped to which minimum in this record (value)
196             # The passed population data objects are modified.
197             sub append_pop_data {
198 0     0 1 0 my ($self, $pop_data_ref, $append_to_min_ref) = @_;
199              
200 0 0       0 if (defined $append_to_min_ref) {
201 0         0 my $min_count = $self->min_count;
202 0         0 $_->transform($append_to_min_ref, $min_count) foreach @$pop_data_ref;
203             }
204              
205 0         0 push @{ $self->_population_data }, @$pop_data_ref;
  0         0  
206              
207 0         0 return;
208             }
209              
210             # Decode a single header line into a key and a value, which are returned.
211             sub _get_header_line_key_value {
212 150     150   374 my ($class, $header_line) = @_;
213              
214             # key and value separated by first ':' (match non-greedy!)
215 150         670 my ($key, $value) = $header_line =~ m{ ^ ( .+? ) : [ ] ( .* ) $ }x;
216              
217 150 50       334 confess "Invalid key in header line:\n$header_line"
218             unless defined $key;
219              
220             # Convert key to lower case and replace spaces by underscores.
221 150         503 $key = (lc $key) =~ s/\s+/_/gr;
222              
223 150         437 return ($key, $value);
224             }
225              
226             # Decode the initial population from the Treekin command line. The
227             # population is given as multiple --p0 a=x switches, where a is the state
228             # index and x is the fraction of population initially present in this
229             # state.
230             # Arguments:
231             # command: the command line string used to call treekin
232             # Returns a hash ref containing the initial population of each state a at
233             # position a (1-based).
234             sub _parse_init_population_from_cmd {
235 7     7   20 my ($class, $command) = @_;
236              
237 7         97 my @command_parts = split /\s+/, $command;
238              
239             # Extract the initial population strings given as (multiple) arguments
240             # --p0 to Treekin from the Treekin command.
241 7         19 my @init_population_strings;
242 7         21 while (@command_parts) {
243 116 100       259 if (shift @command_parts eq '--p0') {
244             # Next value should be a population value.
245 32 50       63 confess 'No argument following a --p0 switch'
246             unless @command_parts;
247 32         68 push @init_population_strings, shift @command_parts;
248             }
249             }
250              
251             # Store population of state i in index i-1.
252 7         15 my @init_population;
253 7         19 foreach my $init_population_string (@init_population_strings) {
254 32         119 my ($state, $population) = split /=/, $init_population_string;
255 32         80 $init_population[$state-1] = $population;
256             }
257              
258             # If no population was specified on the cmd line, init 100% in state 1
259 7 100       21 $init_population[0] = 1 unless @init_population_strings;
260              
261             # Set undefined states to zero.
262 7   50     105 $_ //= 0. foreach @init_population;
263              
264 7         394 my $init_population_record
265             = Bio::RNA::Treekin::PopulationDataRecord->new(
266             time => 0,
267             populations => \@init_population,
268             );
269 7         23 return $init_population_record;
270             }
271              
272             sub _parse_header_lines {
273 10     10   28 my ($class, $header_lines_ref) = @_;
274              
275 10         19 my @header_args;
276 10         37 foreach my $line (@$header_lines_ref) {
277 150         344 my ($key, $value) = $class->_get_header_line_key_value($line);
278              
279             # Implement special handling for certain keys.
280 150 100       374 if ($key eq 'rates_file') {
    100          
281             # remove (#index) from file name and store the value
282 7         47 my ($file_name, $file_index)
283             = $value =~ m{ ^ (.+) [ ] [(] [#] (\d+) [)] $ }x;
284 7         28 push @header_args, (
285             rates_file => $file_name,
286             file_index => $file_index,
287             );
288             }
289             elsif ($key eq 'cmd') {
290             # Extract initial population from Treekin command.
291 7         25 my $init_population_ref
292             = $class->_parse_init_population_from_cmd($value);
293 7         24 push @header_args, (
294             cmd => $value,
295             init_population => $init_population_ref,
296             );
297             }
298             else {
299             # For the rest, just push key and value as constructor args.
300 136         356 push @header_args, ($key => $value);
301             }
302             }
303 10         101 return @header_args;
304             }
305              
306              
307             # Read all lines from the given handle and separate it into header lines
308             # and data lines.
309             sub _read_record_lines {
310 10     10   29 my ($class, $record_handle) = @_;
311              
312             # Separate lines into header and population data. All header lines
313             # begin with a '# ' (remove it!)
314             # Note: Newer versions of treekin also add header info *below* data lines.
315 10         21 my ($current_line, @header_lines, @population_data_lines);
316 10         254 while (defined ($current_line = <$record_handle>)) {
317 342 50 33     3376 next if $current_line =~ /^@/ # drop xmgrace annotations
318             or $current_line =~ m{ ^ \s* $ }x; # or empty lines
319              
320             # Header lines start with '# ', remove it.
321 342 100       1049 if ($current_line =~ s/^# //) { # header line
322 150         795 push @header_lines, $current_line;
323             }
324             else { # data line
325 192         736 push @population_data_lines, $current_line;
326             }
327             }
328              
329             # Sanity checks.
330 10 50       96 confess 'No header lines found in Treekin file'
331             unless @header_lines;
332 10         57 chomp @header_lines;
333              
334 10 50       34 confess 'No population data lines found in Treekin file'
335             unless @population_data_lines;
336 10         41 chomp @population_data_lines;
337              
338 10         64 return \@header_lines, \@population_data_lines;
339             }
340              
341             sub _parse_population_data_lines {
342 10     10   25 my ($class, $population_data_lines_ref) = @_;
343              
344             my @population_data
345 10         26 = map { Bio::RNA::Treekin::PopulationDataRecord->new($_) }
  161         5610  
346             @$population_data_lines_ref
347             ;
348              
349 9         71 return (population_data => \@population_data);
350             }
351              
352             around BUILDARGS => sub {
353             my $orig = shift;
354             my $class = shift;
355              
356             # Call original constructor if passed more than one arg.
357             return $class->$orig(@_) unless @_ == 1;
358              
359             # Retrive file handle or pass on hash ref to constructor.
360             my $record_handle;
361             if (reftype $_[0]) {
362             if (reftype $_[0] eq reftype {}) { # arg hash passed,
363             return $class->$orig(@_); # pass on as is
364             }
365             elsif (reftype $_[0] eq reftype \*STDIN) { # file handle passed
366             $record_handle = shift;
367             }
368             else {
369             croak 'Invalid ref type passed to constructor';
370             }
371             }
372             else { # file name passed
373             my $record_file = shift;
374             open $record_handle, '<', $record_file;
375             }
376              
377             # Read in file.
378             my ($header_lines_ref, $population_data_lines_ref)
379             = $class->_read_record_lines($record_handle);
380              
381             # Parse file.
382             my @header_args = $class->_parse_header_lines($header_lines_ref);
383             my @data_args
384             = $class->_parse_population_data_lines($population_data_lines_ref);
385              
386             my %args = (@header_args, @data_args);
387             return $class->$orig(\%args);
388             };
389              
390             sub BUILD {
391 9     9 0 19 my $self = shift;
392              
393             # Force construction despite laziness.
394 9         35 $self->min_count;
395              
396             # Adjust min count of initial population as it was not known when
397             # initial values were extracted from Treekin cmd.
398 9 100       349 $self->init_population->set_min_count( $self->min_count )
399             if $self->has_init_population;
400             }
401              
402             sub stringify {
403 8     8 1 189 my $self = shift;
404              
405             # Format header line value of rates file entry.
406             my $make_rates_file_val = sub {
407 6     6   173 $self->rates_file . ' (#' . $self->file_index . ')';
408 8         40 };
409              
410             # Header
411 8 100       310 my @header_entries = (
    100          
    100          
412             $self->has_rates_file ? ('Rates file' => $make_rates_file_val->()) : (),
413             $self->has_info ? ('Info' => $self->info) : (),
414             $self->has_cmd ? ('Cmd' => $self->cmd) : (),
415             'Date' => $self->date,
416             'Sequence' => $self->sequence,
417             'Method' => $self->method,
418             'Start time' => $self->start_time,
419             'Stop time' => $self->stop_time,
420             'Temperature' => $self->temperature,
421             'Basename' => $self->basename,
422             'Time increment' => $self->time_increment,
423             'Degeneracy' => $self->degeneracy,
424             'Absorbing state' => $self->absorbing_state,
425             'States limit' => $self->states_limit,
426             );
427              
428 8     102   80 my $header_str = join "\n", pairmap { "# $a: $b" } @header_entries;
  102         230  
429              
430             # Population data
431             my $population_str
432 8         39 = join "\n", map { "$_" } @{ $self->_population_data };
  132         401  
  8         258  
433              
434             # Footer (new Treekin versions only).
435 8 100       320 my $footer_str = $self->has_of_iterations
436             ? '# of iterations: ' . $self->of_iterations
437             : q{};
438              
439 8         90 my $self_as_str = $header_str . "\n" . $population_str;
440 8 100       31 $self_as_str .= "\n" . $footer_str if $footer_str;
441              
442 8         117 return $self_as_str;
443             }
444              
445             __PACKAGE__->meta->make_immutable;
446              
447             1; # End of Bio::RNA::Treekin::Record
448              
449              
450             __END__
451              
452              
453             =pod
454              
455             =encoding UTF-8
456              
457             =head1 NAME
458              
459             Bio::RNA::Treekin::Record - Parse, query, and manipulate I<Treekin> output.
460              
461             =head1 SYNOPSIS
462              
463             use Bio::RNA::Treekin;
464              
465             =head1 DESCRIPTION
466              
467             Parses a regular output file of I<Treekin>. Allows to query population data
468             as well as additional info from the header. New minima can be generated. The
469             stringification returns, again, a valid I<Treekin> file which can be, e. g.,
470             visualized using I<Grace>.
471              
472             =head1 ATTRIBUTES
473              
474             These attributes of the class allow to query various data from the header of
475             the input file.
476              
477             =head2 date
478              
479             The time and date of the I<Treekin> run.
480              
481             =head2 sequence
482              
483             The RNA sequence for which the simulation was computed.
484              
485             =head2 method
486              
487             The method used to build the transition matrix as documented for the
488             C<--method> switch of I<Treekin>.
489              
490             =head2 start_time
491              
492             Initial time of the simulation.
493              
494             =head2 stop_time
495              
496             Time at which the simulation stops.
497              
498             =head2 temperature
499              
500             Temperature of the simulation in degrees Celsius.
501              
502             =head2 basename
503              
504             Name of the input file. May be C<< <stdin> >> if data was read from standard
505             input.
506              
507             =head2 time_increment
508              
509             Factor by which the time is multiplied in each simulation step (roughly, the
510             truth is more complicated).
511              
512             =head2 degeneracy
513              
514             Whether to consider degeneracy in transition rates.
515              
516             =head2 absorbing_state
517              
518             The states specified as absorbing do not have any outgoing transitions and
519             thus serve as "population sinks" during the simulation.
520              
521             =head2 states_limit
522              
523             Maximum number of states (???). Value is always (?) 2^31 = 2147483647.
524              
525             =head2 info
526              
527             A free text field containing additional comments.
528              
529             Only available in I<some> of the records of a I<BarMap> multi-record file. Use
530             predicate C<has_info> to check whether this attribute is available.
531              
532             =head2 init_population
533              
534             The initial population specified by the user. This information is extracted
535             from the C<cmd> attribute.
536              
537             Only available in I<BarMap>'s multi-record files. Use predicate
538             C<has_init_population> to check whether this attribute is available.
539              
540             =head2 rates_file
541              
542             The file that the rate matrix was read from.
543              
544             Only available in I<BarMap>'s multi-record files. Use predicate
545             C<has_rates_file> to check whether this attribute is available.
546              
547             =head2 file_index
548              
549             Zero-based index given to the input files in the order they were read.
550             Extracted from the C<rates_file> attribute.
551              
552             Use predicate C<has_file_index> to check whether this attribute is available.
553              
554             =head2 cmd
555              
556             The command used to invoke I<Treekin>. Only available in I<BarMap>'s
557             multi-record files.
558              
559             Use predicate C<has_cmd> to check whether this attribute is available.
560              
561              
562             =head1 METHODS
563              
564             These methods allow the construction, querying and manipulation of the record
565             objects and its population data.
566              
567             =head2 Bio::RNA::Treekin::Record->new($treekin_file)
568              
569             =head2 Bio::RNA::Treekin::Record->new($treekin_handle)
570              
571             Construct a new record from a (single) I<Treekin> file.
572              
573             =head2 $record->population_data_count
574              
575             Return the number of population data records, i. e. the number of simulated
576             time steps, including the start time.
577              
578             =head2 $record->min_count
579              
580             Return the number of minima.
581              
582             =head2 $record->mins
583              
584             Return the list of all contained minima, i. e. C<< 1...$record->min_count >>
585              
586             =head2 $record->keep_mins(@kept_minima)
587              
588             Remove all minima but the ones from C<@kept_minima>. The list is sorted and
589             de-duplicated first.
590              
591             =head2 $record->splice_mins(@kept_minima)
592              
593             Like C<keep_mins()>, but do not sort / de-duplicate, but use C<@kept_minima>
594             as is. This can be used to remove, duplicate or reorder minima.
595              
596             =head2 $record->max_pop_of_min($minimum)
597              
598             Get the maximum population value of all time points for a specific C<$minimum>.
599              
600             =head2 $record->pops_of_min($minimum)
601              
602             Get a list of the populations at all time points (in chronological order) for
603             a single C<$minimum>.
604              
605             =head2 $record->final_population
606              
607             Get the last population data record, an object of class
608             L<Bio::RNA::Treekin::PopulationDataRecord>. It contains the population data
609             for all minima at the C<stop_time>.
610              
611             =head2 $record->population($i)
612              
613             Get the C<$i>-th population data record, an object of class
614             L<Bio::RNA::Treekin::PopulationDataRecord>. C<$i> is a zero-based index in
615             chronological order.
616              
617             =head2 $record->populations
618              
619             Returns the list of all population data records. Useful for iterating.
620              
621             =head2 $record->add_min
622              
623             Add a single new minimum with all-zero entries. Data can then be appended to
624             this new min using C<append_pop_data()>.
625              
626             Returns the index of the new minimum.
627              
628             =head2 $record->append_pop_data($pop_data_ref, $append_to_min_ref)
629              
630             Given a list of population data records C<$pop_data_ref>, append them to the
631             population data of this record.
632              
633             The columns of the added data can be
634             re-arranged on the fly by providing a mapping C<$append_to_min_ref> (a hash
635             ref) giving for each minimum in C<$pop_data_ref> (key) a
636             minimum in the current population data (value) to which the new minimum should
637             be swapped. If no data is provided for some minimum of this record, its
638             population is set to zero in the newly added entries.
639              
640             =head2 $record->stringify
641              
642             =head2 "$record"
643              
644             Returns the record as a I<Treekin> file.
645              
646             =head1 AUTHOR
647              
648             Felix Kuehnl, C<< <felix@bioinf.uni-leipzig.de> >>
649              
650              
651             =head1 BUGS
652              
653             Please report any bugs or feature requests by raising an issue at
654             L<https://github.com/xileF1337/Bio-RNA-Treekin/issues>.
655              
656             You can also do so by mailing to C<bug-bio-rna-treekin at rt.cpan.org>,
657             or through the web interface at
658             L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Bio-RNA-Treekin>. I will be
659             notified, and then you'll automatically be notified of progress on your bug as
660             I make changes.
661              
662              
663             =head1 SUPPORT
664              
665             You can find documentation for this module with the perldoc command.
666              
667             perldoc Bio::RNA::Treekin
668              
669              
670             You can also look for information at:
671              
672             =over 4
673              
674             =item * Github: the official repository
675              
676             L<https://github.com/xileF1337/Bio-RNA-Treekin>
677              
678             =item * RT: CPAN's request tracker (report bugs here)
679              
680             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Bio-RNA-Treekin>
681              
682             =item * AnnoCPAN: Annotated CPAN documentation
683              
684             L<http://annocpan.org/dist/Bio-RNA-Treekin>
685              
686             =item * CPAN Ratings
687              
688             L<https://cpanratings.perl.org/d/Bio-RNA-Treekin>
689              
690             =item * Search CPAN
691              
692             L<https://metacpan.org/release/Bio-RNA-Treekin>
693              
694             =back
695              
696              
697             =head1 LICENSE AND COPYRIGHT
698              
699             Copyright 2019-2021 Felix Kuehnl.
700              
701             This program is free software: you can redistribute it and/or modify
702             it under the terms of the GNU General Public License as published by
703             the Free Software Foundation, either version 3 of the License, or
704             (at your option) any later version.
705              
706             This program is distributed in the hope that it will be useful,
707             but WITHOUT ANY WARRANTY; without even the implied warranty of
708             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
709             GNU General Public License for more details.
710              
711             You should have received a copy of the GNU General Public License
712             along with this program. If not, see L<http://www.gnu.org/licenses/>.
713              
714              
715             =cut
716              
717             # End of Bio/RNA/Treekin/Record.pm