File Coverage

blib/lib/MooX/PDL/Role/Proxy.pm
Criterion Covered Total %
statement 109 114 95.6
branch 16 22 72.7
condition 1 3 33.3
subroutine 32 34 94.1
pod 14 14 100.0
total 172 187 91.9


line stmt bran cond sub pod time code
1             package MooX::PDL::Role::Proxy;
2              
3             # ABSTRACT: treat a container of ndarrays (piddles) as if it were an ndarray (piddle)
4              
5 6     6   869236 use v5.10;
  6         45  
6 6     6   48 use strict;
  6         14  
  6         161  
7 6     6   32 use warnings;
  6         13  
  6         345  
8              
9             our $VERSION = '0.07';
10              
11 6     6   3894 use Types::Standard -types;
  6         641354  
  6         97  
12              
13 6     6   33230 use PDL::Primitive ();
  6         20  
  6         137  
14 6     6   1746 use Hash::Wrap;
  6         8009  
  6         67  
15 6     6   5750 use Scalar::Util ();
  6         13  
  6         106  
16              
17 6     6   3825 use Moo::Role;
  6         33906  
  6         112  
18 6     6   9614 use Lexical::Accessor;
  6         46122  
  6         77  
19              
20 6     6   3086 use namespace::clean;
  6         35611  
  6         58  
21              
22             use constant {
23 6         589 INPLACE_SET => 1,
24             INPLACE_STORE => 2,
25 6     6   4215 };
  6         15  
26              
27 6     6   3428 use MooX::TaggedAttributes -tags => [qw( piddle ndarray )];
  6         92303  
  6         37  
28              
29             my $croak = sub {
30             require Carp;
31             goto \&Carp::croak;
32             };
33              
34             my $can_either = sub {
35             my $self = shift;
36             for ( @_ ) {
37             return $self->can( $_ ) // next;
38             }
39             };
40              
41             lexical_has clone_v2 => (
42             is => 'lazy',
43             weak_ref => 1,
44             reader => \( my $clone_v2 ),
45             default => sub { $_[0]->can( '_clone_with_ndarrays' ) },
46             );
47              
48             lexical_has clone_v1 => (
49             is => 'lazy',
50             weak_ref => 1,
51             reader => \( my $clone_v1 ),
52             default => sub { $_[0]->can( 'clone_with_piddles' ) },
53             );
54              
55             lexical_has clone_args => (
56             is => 'rw',
57             reader => \( my $get_clone_args ),
58             clearer => \( my $clear_clone_args ),
59             writer => \( my $set_clone_args ),
60             predicate => \( my $has_clone_args ),
61             );
62              
63             lexical_has attr_subs => (
64             is => 'ro',
65             isa => HashRef,
66             reader => \( my $attr_subs ),
67             default => sub { {} },
68             );
69              
70             lexical_has 'is_inplace' => (
71             is => 'rw',
72             clearer => \( my $clear_inplace ),
73             reader => \( my $is_inplace ),
74             writer => \( my $set_inplace ),
75             default => 0
76             );
77              
78              
79             my $clone = sub {
80             my ( $self, $attrs ) = @_;
81              
82             if ( my $func = $self->$clone_v2 ) {
83             $self->$func( $attrs, $self->$has_clone_args ? $self->$get_clone_args : () );
84             }
85             elsif ( $func = $self->$clone_v1 ) {
86             $self->$func( %$attrs );
87             }
88             else {
89             $croak->( "couldn't find clone method for class '@{[ ref $self ]}'" );
90             }
91             };
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102              
103              
104              
105              
106              
107              
108              
109              
110             has _ndarrays => (
111             is => 'lazy',
112             isa => ArrayRef [Str],
113             init_arg => undef,
114             clearer => 1,
115             builder => sub {
116 47     47   556 my $self = shift;
117 47         190 my $tags = $self->_tags->tag_hash;
118             # make backwards compatible with 'piddle'. the returned hash
119             # is locked, so only access keys known to exist
120             [
121 47         80 map { keys %{ $tags->{$_} } }
  47         939  
122 47         3052 grep { /^ndarray|piddle$/ } keys %$tags
  47         207  
123             ];
124             },
125             );
126              
127             # alias for backwards compatibility
128             *_piddles = \&_ndarrays;
129             *_clear_piddles = \&_clear_ndarrays;
130             *_build_piddles = \&_build__ndarrays;
131              
132              
133              
134              
135              
136              
137              
138              
139              
140              
141              
142              
143              
144              
145              
146              
147              
148              
149             sub _apply_to_tagged_attrs {
150 34     34   82 my ( $self, $action ) = @_;
151              
152 34         125 my $inplace = $self->$is_inplace;
153              
154             my %attr = map {
155 68         2335 my $field = $_;
156 68         232 $field => $action->( $self->$field, $inplace );
157 34         456 } @{ $self->_ndarrays };
  34         778  
158              
159 34 100       951 if ( $inplace ) {
160 12         140 $self->$clear_inplace;
161              
162 12 100       103 if ( $inplace == INPLACE_SET ) {
    50          
163 11         75 $self->_set_attr( %attr );
164             }
165              
166             elsif ( $inplace == INPLACE_STORE ) {
167 1         5 for my $attr ( keys %attr ) {
168             # $attr{$attr} may be linked to $self->$attr,
169             # so if we reshape $self->$attr, it really
170             # messes up $attr{$attr}. sever it to be sure.
171 2         140 my $pdl = $attr{$attr}->sever;
172 2         14 ( my $tmp = $self->$attr->reshape( $pdl->dims ) ) .= $pdl;
173             }
174             }
175              
176             else {
177 0         0 $croak->( "unrecognized inplace flag value: $inplace\n" );
178             }
179              
180 12         173 return $self;
181             }
182              
183 22         82 return $self->$clone( \%attr );
184             }
185              
186              
187              
188              
189              
190              
191              
192              
193              
194              
195              
196              
197              
198              
199              
200              
201              
202              
203              
204              
205             sub inplace {
206 13 50   13 1 241812 $_[0]->$set_inplace( @_ > 1 ? $_[1] : INPLACE_SET );
207 13         223 $_[0];
208             }
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228             sub inplace_store {
229 1     1 1 3006 $_[0]->$set_inplace( INPLACE_STORE );
230 1         21 $_[0];
231             }
232              
233              
234              
235              
236              
237              
238              
239              
240              
241              
242              
243              
244              
245              
246              
247              
248              
249              
250              
251             sub inplace_set {
252 0     0 1 0 $_[0]->$set_inplace( INPLACE_SET );
253 0         0 $_[0];
254             }
255              
256              
257              
258              
259              
260              
261              
262              
263              
264              
265              
266              
267              
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278              
279             sub set_inplace {
280 3 50   3 1 903 2 == @_ or $croak->( "set_inplace requires two arguments" );
281 3 50       41 $_[1] >= 0
282             && $_[0]->$set_inplace( $_[1] );
283 3         14 return;
284             }
285              
286              
287              
288              
289              
290              
291              
292              
293              
294 10     10 1 3376 sub is_inplace { goto &$is_inplace }
295              
296              
297              
298              
299              
300              
301              
302              
303              
304              
305              
306              
307              
308             sub copy {
309 6     6 1 17610 my $self = shift;
310              
311 6 100       21 if ( $self->is_inplace ) {
312 1         13 $self->set_inplace( 0 );
313 1         4 return $self;
314             }
315 5         71 my %attr = map { $_ => $self->$_->copy } @{ $self->_ndarrays };
  10         272  
  5         96  
316 5         98 return $self->$clone( \%attr );
317             }
318              
319              
320              
321              
322              
323              
324              
325              
326              
327              
328             sub sever {
329 4     4 1 12705 my $self = shift;
330 4         7 $self->$_->sever for @{ $self->_ndarrays };
  4         89  
331 4         127 return $self;
332             }
333              
334              
335              
336              
337              
338              
339              
340              
341              
342              
343             sub index {
344 16     16 1 52489 my ( $self, $index ) = @_;
345 16     32   111 return $self->_apply_to_tagged_attrs( sub { $_[0]->index( $index ) } );
  32         355  
346             }
347              
348             # is there a use for this?
349             # sub which {
350             # my ( $self, $which ) = @_;
351             # return PDL::Primitive::which(
352             # 'CODE' eq ref $which
353             # ? do { local $_ = $self; $which->() }
354             # : $which
355             # );
356             # }
357              
358              
359              
360              
361              
362              
363              
364              
365              
366              
367              
368             sub at {
369 4     4 1 53247 my ( $self, @idx ) = @_;
370 4         10 wrap_hash( { map { $_ => $self->$_->at( @idx ) } @{ $self->_ndarrays } } );
  8         272  
  4         70  
371             }
372              
373              
374              
375              
376              
377              
378              
379              
380              
381              
382             sub where {
383 16     16 1 30953 my ( $self, $where ) = @_;
384              
385 16     32   121 return $self->_apply_to_tagged_attrs( sub { $_[0]->where( $where ) } );
  32         134  
386             }
387              
388              
389              
390              
391              
392              
393              
394              
395              
396              
397              
398             sub _set_clone_args {
399 1     1   2985 $_[0]->$set_clone_args( $_[1] );
400             }
401              
402              
403              
404              
405              
406              
407              
408              
409              
410              
411             sub _clear_clone_args {
412 0     0   0 $_[0]->$clear_clone_args;
413             }
414              
415              
416              
417              
418              
419              
420              
421              
422              
423              
424              
425              
426             sub _set_attr {
427 38     38   4103 my ( $self, %attr ) = @_;
428 38         147 my $subs = $self->$attr_subs;
429              
430 38         201 for my $key ( keys %attr ) {
431 76         582 my $sub = $subs->{$key};
432              
433 76 50       175 if ( !defined $sub ) {
434 76   33     537 Scalar::Util::weaken( $subs->{$key} = $self->can( "_set_${key}" )
435             // $self->can( $key ) );
436 76         145 $sub = $subs->{$key};
437             }
438              
439 76         1293 $sub->( $self, $attr{$key} );
440             }
441              
442 38         671 return $self;
443             }
444              
445              
446              
447              
448              
449              
450              
451              
452              
453              
454              
455              
456              
457              
458              
459              
460              
461              
462              
463              
464              
465              
466              
467             sub qsort {
468              
469 3     3 1 31163 $_[0]->index( $_[0]->qsorti );
470             }
471              
472              
473              
474              
475              
476              
477              
478              
479              
480              
481              
482              
483             sub qsort_on {
484              
485 2     2 1 10105 my ( $self, $attr ) = @_;
486              
487 2         43 $self->index( $attr->qsorti );
488             }
489              
490              
491              
492              
493              
494              
495              
496              
497              
498              
499              
500              
501              
502              
503              
504             sub clip_on {
505              
506 6     6 1 26165 my ( $self, $attr, $min, $max ) = @_;
507              
508 6         13 my $mask;
509              
510 6 100       18 if ( defined $min ) {
    50          
511 4         80 $mask = $attr >= $min;
512 4 100       284 $mask &= $attr < $max
513             if defined $max;
514             }
515             elsif ( defined $max ) {
516 2         13 $mask = $attr < $max;
517             }
518             else {
519 0         0 $croak->( "one of min or max must be defined\n" );
520             }
521              
522 6         220 $self->where( $mask );
523             }
524              
525              
526              
527              
528              
529              
530              
531              
532              
533              
534              
535              
536              
537             sub slice {
538              
539 2     2 1 9499 my ( $self, $slice ) = @_;
540              
541 2     4   12 return $self->_apply_to_tagged_attrs( sub { $_[0]->slice( $slice ) } );
  4         18  
542             }
543              
544              
545              
546             1;
547              
548             #
549             # This file is part of MooX-PDL-Role-Proxy
550             #
551             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
552             #
553             # This is free software, licensed under:
554             #
555             # The GNU General Public License, Version 3, June 2007
556             #
557              
558             __END__
559              
560             =pod
561              
562             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory ndarray ndarrays
563              
564             =head1 NAME
565              
566             MooX::PDL::Role::Proxy - treat a container of ndarrays (piddles) as if it were an ndarray (piddle)
567              
568             =head1 VERSION
569              
570             version 0.07
571              
572             =head1 SYNOPSIS
573              
574             package My::Class;
575              
576             use Moo;
577             use MooX::PDL::Role::Proxy;
578              
579             use PDL;
580              
581             has p1 => (
582             is => 'rw',
583             default => sub { sequence( 10 ) },
584             ndarray => 1
585             );
586              
587             has p2 => (
588             is => 'rw',
589             default => sub { sequence( 10 ) + 1 },
590             ndarray => 1
591             );
592              
593              
594             sub clone_with_ndarrays {
595             my ( $self, %ndarrays ) = @_;
596              
597             $self->new->_set_attr( %ndarrays );
598             }
599              
600              
601             my $obj = My::Class->new;
602              
603             # clone $obj and filter ndarrays.
604             my $new = $obj->where( $obj->p1 > 5 );
605              
606             =head1 DESCRIPTION
607              
608             B<MooX::PDL::Role::Proxy> is a L<Moo::Role> which turns its
609             consumer into a proxy object for some of its attributes, which are
610             assumed to be B<PDL> objects (or other proxy objects). A subset of
611             B<PDL> methods applied to the proxy object are applied to the selected
612             attributes. (See L<PDL::QuckStart> for more information on B<PDL> and
613             its objects (ndarrays)).
614              
615             As an example, consider an object representing a set of detected
616             events (think physics, not computing), which contains metadata
617             describing the events as well as ndarrays representing event position,
618             energy, and arrival time. The structure might look like this:
619              
620             {
621             metadata => \%metadata,
622             time => $time, # ndarray
623             x => $x, # ndarray
624             y => $y, # ndarray
625             energy => $energy # ndarray
626             }
627              
628             To filter the events on energy would traditionally be performed
629             explicitly on each element in the structure, e.g.
630              
631             my $mask = which( $obj->{energy} > 20 );
632              
633             my $copy = {};
634             $copy->{time} = $obj->{time}->where( $mask );
635             $copy->{x} = $obj->{x}->where( $mask );
636             $copy->{y} = $obj->{y}->where( $mask );
637             $copy->{energy} = $obj->{energy}->where( $mask );
638              
639             Or, more succinctly,
640              
641             $new->{$_} = $obj->{$_}->where( $mask ) for qw( time x y energy );
642              
643             With B<MooX::PDL::Role::Proxy> this turns into
644              
645             my $copy = $obj->where( $mask );
646              
647             Or, if the results should be stored in the same object,
648              
649             $obj->inplace->where( $mask );
650              
651             =head2 Usage and Class requirements
652              
653             Each attribute to be operated on by the common C<PDL>-like
654             operators should be given a C<ndarray> option, e.g.
655              
656             has p1 => (
657             is => 'rw',
658             default => sub { sequence( 10 ) },
659             ndarray => 1,
660             );
661              
662             (Treat the option value as an identifier for the group of ndarrays
663             which should be operated on, rather than as a boolean).
664              
665             =head2 Results of Operations
666              
667             The results of operations may either be stored L</In Place> or returned
668             in L</Cloned Objects>. By default, operations return cloned objects.
669              
670             =head3 In Place
671              
672             Use one of the following methods, L</inplace>, L</inplace_store>, L</inplace_set>.
673             to indicate that the next in-place aware operation should be performed in-place.
674             After the operation is completed, the in-place flag will be reset.
675              
676             To support inplace operations, attributes tagged with the C<ndarray>
677             option must have write accessors. They may be public or private.
678              
679             =head3 Cloned Objects
680              
681             The class must provide a a clone method. If cloning an object
682             requires extra arguments, use L</_set_clone_args> and
683             L</_clear_clone_args> to set or reset the arguments.
684              
685             If the class provides the L<_clone_with_ndarrays> method, then it will be called as
686              
687             $object->_clone_with_ndarrays( \%ndarrays, ?$arg);
688              
689             where C<$arg> will only be passed if L</_set_clone_args> was called.
690              
691             For backwards compatibility, the L<clone_with_piddles> method is supported, but
692             it is not possible to pass in extra arguments. It will be called as
693              
694             $object->clone_with_piddles ( %ndarrays );
695              
696             =head2 Nested Proxy Objects
697              
698             A class with the applied role should respond equivalently to a true
699             ndarray when the supported methods are called on it (it's a bug
700             otherwise). Thus, it is possible for a proxy object to contain
701             another, and as long as the contained object has the C<ndarray>
702             attribute set, the supported method will be applied to the
703             contained object appropriately.
704              
705             =head1 METHODS
706              
707             =head2 _ndarrays
708              
709             @ndarray_names = $obj->_ndarrays;
710              
711             This returns a list of the names of the object's attributes with a
712             C<ndarray> (or for backwards compatibility, C<piddle> ) tag set. The
713             list is lazily created by the C<_build__ndarrays> method, which can be
714             modified or overridden if required. The default action is to find all
715             tagged attributes with tags C<ndarray> or C<piddle>.
716              
717             =head2 _clear_ndarrays
718              
719             Clear the list of attributes which have been tagged as ndarrays. The
720             list will be reset to the defaults when C<_ndarrays> is next invoked.
721              
722             =head2 _apply_to_tagged_attrs
723              
724             $obj->_apply_to_tagged_attrs( \&sub );
725              
726             Execute the passed subroutine on all of the attributes tagged with
727             C<ndarray> (or C<piddle>). The subroutine will be invoked as
728              
729             sub->( $attribute, $inplace )
730              
731             where C<$inplace> will be true if the operation is to take place inplace.
732              
733             The subroutine should return the ndarray to be stored.
734              
735             Returns C<$obj> if applied in-place, or a new object if not.
736              
737             =head2 inplace
738              
739             $obj->inplace( ?$how )
740              
741             Indicate that the next I<inplace aware> operation should be done inplace.
742              
743             An optional argument indicating how the ndarrays should be updated may be
744             passed (see L</set_inplace> for more information). This API differs from
745             from the L<inplace|PDL::Core/inplace> method.
746              
747             It defaults to using the attributes' accessors to store the results,
748             which will cause triggers, etc. to be called.
749              
750             Returns C<$obj>.
751             See also L</inplace_direct> and L</inplace_accessor>.
752              
753             =head2 inplace_store
754              
755             $obj->inplace_store
756              
757             Indicate that the next I<inplace aware> operation should be done
758             inplace. NDarrays are changed inplace via the C<.=> operator, avoiding
759             any side-effects caused by using the attributes' accessors.
760              
761             It is equivalent to calling
762              
763             $obj->set_inplace( MooX::PDL::Role::Proxy::INPLACE_STORE );
764              
765             Returns C<$obj>.
766             See also L</inplace> and L</inplace_accessor>.
767              
768             =head2 inplace_set
769              
770             $obj->inplace_set
771              
772             Indicate that the next I<inplace aware> operation should be done inplace.
773             The object level attribute accessors will be used to store the results (which
774             may be the same ndarray). This will cause L<Moo> triggers, etc to be
775             called.
776              
777             It is equivalent to calling
778              
779             $obj->set_inplace( MooX::PDL::Role::Proxy::INPLACE_SET );
780              
781             Returns C<$obj>.
782             See also L</inplace_store> and L</inplace>.
783              
784             =head2 set_inplace
785              
786             $obj->set_inplace( $value );
787              
788             Change the value of the inplace flag. Accepted values are
789              
790             =over
791              
792             =item MooX::PDL::Role::Proxy::INPLACE_SET
793              
794             Use the object level attribute accessors to store the results (which
795             may be the same ndarray). This will cause L<Moo> triggers, etc to be
796             called.
797              
798             =item MooX::PDL::Role::Proxy::INPLACE_STORE
799              
800             Store the results directly in the existing ndarray using the C<.=> operator.
801              
802             =back
803              
804             =head2 is_inplace
805              
806             $bool = $obj->is_inplace;
807              
808             Test if the next I<inplace aware> operation should be done inplace
809              
810             =head2 copy
811              
812             $new = $obj->copy;
813              
814             Create a copy of the object and its ndarrays. If the C<inplace> flag
815             is set, it returns C<$obj> otherwise it is exactly equivalent to
816              
817             $obj->clone_with_ndarrays( map { $_ => $obj->$_->copy } @{ $obj->_ndarrays } );
818              
819             =head2 sever
820              
821             $obj = $obj->sever;
822              
823             Call L<PDL::Core/sever> on tagged attributes. This is done inplace.
824             Returns C<$obj>.
825              
826             =head2 index
827              
828             $new = $obj->index( NDARRAY );
829              
830             Call L<PDL::Slices/index> on tagged attributes. This is inplace aware.
831             Returns C<$obj> if applied in-place, or a new object if not.
832              
833             =head2 at
834              
835             $obj = $obj->at( @indices );
836              
837             Returns a simple object containing the results of running
838             L<PDL::Core/index> on tagged attributes. The object's attributes are
839             named after the tagged attributes.
840              
841             =head2 where
842              
843             $obj = $obj->where( $mask );
844              
845             Apply L<PDL::Primitive/where> to the tagged attributes. It is in-place aware.
846             Returns C<$obj> if applied in-place, or a new object if not.
847              
848             =head2 _set_clone_args
849              
850             $obj->_set_clone_args( $args );
851              
852             Pass the given value to the C<_clone_with_args_ndarrays> method when
853             an object must be implicitly cloned.
854              
855             =head2 _clear_clone_args
856              
857             $obj->_clear_clone_args;
858              
859             Clear out any value set by L<_set_clone_args>.
860              
861             =head2 _set_attr
862              
863             $obj->_set_attr( %attr )
864              
865             Set the object's attributes to the values in the C<%attr> hash.
866              
867             Returns C<$obj>.
868              
869             =head2 qsort
870              
871             $obj->qsort;
872              
873             Sort the ndarrays. This requires that the object has a C<qsorti> method, which should
874             return an ndarray index of the elements in ascending order.
875              
876             For example, to designate the C<radius> attribute as that which should be sorted
877             on by qsort, include the C<handles> option when declaring it:
878              
879             has radius => (
880             is => 'ro',
881             ndarray => 1,
882             isa => Piddle1D,
883             handles => ['qsorti'],
884             );
885              
886             It is in-place aware. Returns C<$obj> if applied in-place, or a new object if not.
887              
888             =head2 qsort_on
889              
890             $obj->sort_on( $ndarray );
891              
892             Sort on the specified C<$ndarray>.
893              
894             It is in-place aware.
895             Returns C<$obj> if applied in-place, or a new object if not.
896              
897             =head2 clip_on
898              
899             $obj->clip_on( $ndarray, $min, $max );
900              
901             Clip on the specified C<$ndarray>, removing elements which are outside
902             the bounds of [C<$min>, C<$max>). Either bound may be C<undef> to indicate
903             it should be ignore.
904              
905             It is in-place aware.
906              
907             Returns C<$obj> if applied in-place, or a new object if not.
908              
909             =head2 slice
910              
911             $obj->slice( $slice );
912              
913             Slice. See L<PDL::Slices/slice> for more information.
914              
915             It is in-place aware.
916             Returns C<$obj> if applied in-place, or a new object if not.
917              
918             =head1 LIMITATIONS
919              
920             There are significant limits to this encapsulation.
921              
922             =over
923              
924             =item *
925              
926             The ndarrays operated on must be similar enough in structure so that
927             the ganged operations make sense (and are valid!).
928              
929             =item *
930              
931             There is (currently) no way to indicate that there are different sets
932             of ndarrays contained within the object.
933              
934             =item *
935              
936             The object must be able to be cloned relatively easily, so that
937             non-inplace operations can create copies of the original object.
938              
939             =back
940              
941             =head1 SUPPORT
942              
943             =head2 Bugs
944              
945             Please report any bugs or feature requests to or through the web interface at: https://rt.cpan.org/Public/Dist/Display.html?Name=MooX-PDL-Role-Proxy
946              
947             =head2 Source
948              
949             Source is available at
950              
951             https://gitlab.com/djerius/moox-pdl-role-proxy
952              
953             and may be cloned from
954              
955             https://gitlab.com/djerius/moox-pdl-role-proxy.git
956              
957             =head1 AUTHOR
958              
959             Diab Jerius <djerius@cpan.org>
960              
961             =head1 COPYRIGHT AND LICENSE
962              
963             This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
964              
965             This is free software, licensed under:
966              
967             The GNU General Public License, Version 3, June 2007
968              
969             =cut