File Coverage

blib/lib/CayleyDickson.pm
Criterion Covered Total %
statement 119 168 70.8
branch 47 78 60.2
condition 3 6 50.0
subroutine 23 37 62.1
pod 23 28 82.1
total 215 317 67.8


line stmt bran cond sub pod time code
1             #
2             # CayleyDickson.pm - Cayley-Dickson constructions and algebriac manipulations
3             #
4             # author: Jeffrey B Anderson - truejeffanderson at gmail.com
5             #
6             # reference: https://en.wikipedia.org/wiki/Cayley-Dickson_construction
7             #
8              
9              
10             package CayleyDickson;
11 5     5   272432 use strict;
  5         41  
  5         119  
12 5     5   21 no warnings;
  5         8  
  5         161  
13 5     5   4913 use overload qw(- subtract + add * multiply / divide "" as_string eq eq);
  5         4403  
  5         27  
14 5     5   899 use constant SYMBOLS => ['', 'i' .. 'z', map('a' . $_, ('a' .. 'z')), ( map('b' . $_, ('a' .. 'z')) ) x 100];
  5         9  
  5         3054  
15             our $VERSION = 0.04;
16              
17 5     5   31 use constant DEBUG => 0;
  5         9  
  5         185  
18 5     5   23 use constant VERBOSE => 0;
  5         8  
  5         288  
19              
20             #
21             # DOUBLING_PRODUCT: multiplication rules for (a,b)×(c,d)
22             #
23             # valid string options...
24             #
25             # P0 => ( c×a - B×d , d×A + b×c )
26             # P1 => ( c×a - d×B , A×d + c×b )
27             # P2 => ( a×c - B×d , d×A + b×c )
28             # P3 => ( a×c - d×B , A×d + c×b )
29             # Pt0 => ( c×a - b×D , a×d + C×b )
30             # Pt1 => ( c×a - D×b , d×a + b×C )
31             # Pt2 => ( a×c - b×D , a×d + C×b )
32             # Pt3 => ( a×c - D×b , d×a + b×C )
33             #
34             # ...where lower case is the value and upper case is the conjugate.
35             #
36             # ref: http://jwbales.us/cdproducts.html
37             #
38             #use constant DOUBLING_PRODUCT => 'P3';
39 5     5   29 use constant DOUBLING_PRODUCT => 'Pt3';
  5         8  
  5         216  
40              
41              
42              
43             #
44             # I_SQUARED is the square of the first imaginary unit i.
45             # valid number options:
46             #
47             # 1 => Split numbers
48             # 0 => Dual numbers
49             # -1 => Cayley-Dickson numbers # default/tested
50             #
51              
52 5     5   23 use constant I_SQUARED => -1;
  5         7  
  5         7750  
53              
54              
55              
56             #
57             # Conjugate: z* = (a,b)* = (a*,-b)
58             #
59              
60             sub conjugate {
61 1137208     1137208 1 1213686 my $m = shift;
62              
63 1137208 100       1315038 my $a_conjugate = $m->is_complex ? $m->a : $m->a->conjugate;
64 1137208         1506399 my $negative_b = -$m->b;
65              
66 1137208         1729567 (ref $m)->new($a_conjugate, $negative_b)
67             }
68              
69              
70              
71             #
72             # Invert: 1/z = z⁻¹ = (a,b)⁻¹ = (a,b)*/(norm(a,b)²)
73             #
74              
75             sub inverse {
76 0     0 1 0 my $m = shift;
77              
78 0         0 my $conjugate = $m->conjugate;
79 0         0 my $norm = $m->norm;
80 0         0 $conjugate / ($norm ** 2)
81             }
82              
83              
84              
85             #
86             # Norm: z->norm = √(norm(a)²+norm(b)²) and norm(number) = number
87             #
88              
89             sub norm {
90 302628     302628 1 504596 my $m = shift;
91              
92 302628 100       371845 my $a = $m->is_complex ? $m->a : $m->a->norm;
93 302628 100       414575 my $b = $m->is_complex ? $m->b : $m->b->norm;
94              
95 302628         650275 sqrt($a ** 2 + $b ** 2)
96             }
97              
98              
99              
100             #
101             # Addition: z1+z2 = (a,b)+(c,d) = (a+c,b+d)
102             #
103              
104             sub add {
105 751287     751287 1 973182 my ( $m, $o ) = @_;
106              
107              
108 751287         923079 my $a = $m->a;
109 751287         990332 my $b = $m->b;
110 751287         928885 my $c = $o->a;
111 751287         906457 my $d = $o->b;
112              
113 751287         1277712 my $sum = (ref $m)->new($a + $c, $b + $d);
114 751287         862328 printf("Add: (%s) + (%s) = (%s)\n", $m, $o, $sum) if DEBUG;
115 751287         1224006 $sum
116             }
117              
118              
119              
120             #
121             # Subtraction: (a,b)-(c,d) = (a-c,b-d)
122             #
123              
124             sub subtract {
125 326055     326055 1 616326 my ( $m, $o, $swap ) = @_;
126              
127 326055 100       619993 $o = (ref $m)->new((my $v = $o), 0) unless ref $o;
128              
129 326055 100       542312 my $a = $swap ? $o->a : $m->a;
130 326055 100       474312 my $b = $swap ? $o->b : $m->b;
131 326055 100       457754 my $c = $swap ? $m->a : $o->a;
132 326055 100       459848 my $d = $swap ? $m->b : $o->b;
133              
134 326055         598464 my $difference = (ref $m)->new($a - $c, $b - $d);
135 326055         383264 printf("Sub: (%s) - (%s) = (%s)\n", ($swap ? ($o, $m) : ($m, $o)), $difference) if DEBUG;
136 326055         559784 $difference
137             }
138              
139              
140              
141             #
142             # Divide: z1/z2 = (a,b) × (c,d)⁻¹ = (a,b) × inverse(c,d)
143             #
144              
145             sub divide {
146 80080     80080 1 119426 my ( $m, $o, $swap ) = @_;
147              
148 80080         85999 my ( $a, $b );
149 80080 50       103441 $a = $swap ? $m->inverse : $m;
150 80080 50       123783 $b = $swap ? $o : (ref $o ? $o->inverse : 1 / $o);
    50          
151              
152 80080 50       124298 $swap ? $a * $b : $b * $a
153             }
154              
155              
156              
157             #
158             # Multiply: (a,b)×(c,d) = (a×c - d*×b, d×a + b×c*) where x* = conjugate(x) or x if x is a number
159             #
160              
161             sub multiply {
162 1811732     1811732 1 2908127 my ( $m, $o, $swap ) = @_;
163              
164 1811732         1750300 printf "Mult: (%s) x (%s)\n", ($swap ? ($o, $m) : ($m, $o)) if DEBUG;
165            
166 1811732         2093585 my ( $ii, $a, $as, $b, $bs, $c, $cs, $d, $ds );
167              
168 1811732         2257885 $ii = $m->i_squared;
169 1811732         2311533 $a = $m->a;
170 1811732         2252415 $b = $m->b;
171 1811732 100       2240184 if ($m->is_complex) {
172 1533048         1653386 $as = $a;
173 1533048         1565325 $bs = $b;
174             }
175             else {
176 278684         370023 $as = $m->a->conjugate;
177 278684         398184 $bs = $m->b->conjugate;
178             }
179 1811732 100       2598803 if (ref $o) {
180 1234040         1516238 $c = $o->a;
181 1234040         1542489 $d = $o->b;
182 1234040 100       1511258 if ($o->is_complex) {
183 1028332         1076890 $cs = $c;
184 1028332         1036666 $ds = $d;
185             }
186             else {
187 205708         273151 $cs = $o->a->conjugate;
188 205708         293965 $ds = $o->b->conjugate;
189             }
190             }
191             else {
192 577692         583074 $c = $o;
193 577692         570415 $cs = $o;
194 577692         589014 $d = 0;
195 577692         568598 $ds = 0;
196             }
197              
198 1811732         1951875 my $p;
199 1811732         2238112 my $dp = $m->doubling_product;
200              
201 1811732 50       5433201 if ($dp eq 'P0' ) { $p = (ref $m)->new($c * $a + $ii * $bs * $d , $d * $as + $b * $c ) }
  0 50       0  
    50          
    50          
    50          
    50          
    50          
    50          
202 0         0 elsif ($dp eq 'P1' ) { $p = (ref $m)->new($c * $a + $ii * $d * $bs, $as * $d + $c * $b ) }
203 0         0 elsif ($dp eq 'P2' ) { $p = (ref $m)->new($a * $c + $ii * $bs * $d , $d * $as + $b * $c ) } # <= special twist pattern?
204 0         0 elsif ($dp eq 'P3' ) { $p = (ref $m)->new($a * $c + $ii * $d * $bs, $as * $d + $c * $b ) }
205 0         0 elsif ($dp eq 'Pt0') { $p = (ref $m)->new($c * $a + $ii * $b * $ds, $a * $d + $cs * $b ) } # <= default
206 0         0 elsif ($dp eq 'Pt1') { $p = (ref $m)->new($c * $a + $ii * $ds * $b , $d * $a + $b * $cs) }
207 0         0 elsif ($dp eq 'Pt2') { $p = (ref $m)->new($a * $c + $ii * $b * $ds, $a * $d + $cs * $b ) }
208 1811732         3768326 elsif ($dp eq 'Pt3') { $p = (ref $m)->new($a * $c + $ii * $ds * $b , $d * $a + $b * $cs) } # <= default for REAL?
209              
210 1811732         2328037 printf("Calculated: (%s) x (%s) = (%s)\n", ($swap ? ($o, $m) : ($m, $o)), $p) if DEBUG;
211 1811732         3403006 $p
212             }
213              
214              
215              
216             #
217             # Tensor: $a->tensor($b) = A⊗ B = (a,b)⊗ (c,d) = (ac,ad,bc,bd)
218             #
219              
220             sub tensor {
221 0     0 1 0 my ( $m, $o ) = @_;
222              
223 0         0 my @pair;
224 0 0       0 if ($m->is_complex) {
225 0         0 @pair = ($m->a * $o, $m->b * $o)
226             }
227             else {
228 0         0 @pair = ($m->a->tensor($o), $m->b->tensor($o))
229             }
230 0         0 (ref $m)->new(@pair)
231             }
232              
233              
234              
235             #
236             # Creates a new CayleyDickson object
237             # expects a list of two (powers of 2) numbers or objects ...
238             #
239              
240             sub new {
241 4373872     4373872 1 5255443 my $class = shift;
242 4373872         5834116 my @values = @_;
243 4373872         4754080 my $elements = scalar @values;
244 4373872         4465294 my @pair;
245 4373872 100       5454187 if ($elements > 2) {
246 26900         58081 @pair = ( ($class->new( @values[0 .. $elements/2 - 1] )) ,
247             ($class->new( @values[$elements/2 .. $elements - 1] )) )
248             }
249             else {
250 4346972         5402347 @pair = ( $values[0] ,
251             $values[1] )
252             }
253 4373872         5600653 bless [ $class->prepare(@pair) ] => $class;
254             }
255              
256              
257              
258             #
259             # allows subclassing to modify the object pair just prior to creating the object.
260             #
261              
262 4373872     4373872 0 4356036 sub prepare { shift; @_ }
  4373872         8858631  
263              
264              
265              
266             #
267             # hold the left number/object in a and the right number/object in b.
268             #
269              
270 11920124     11920124 1 11380750 sub a { ${(shift)}[0] }
  11920124         17892288  
271 7128286     7128286 1 6870719 sub b { ${(shift)}[1] }
  7128286         8871453  
272              
273              
274              
275             #
276             # flat: list of the scalar values pointed to by a,b references in the object references in order ...
277             #
278              
279             sub flat {
280 3602     3602 1 3740 my $m = shift;
281 3602 100       4134 $m->is_complex ? ($m->a, $m->b) : ($m->a->flat, $m->b->flat)
282             }
283              
284              
285             #
286             # print the beautiful objects in terse human format ...
287             #
288              
289             sub as_string {
290 372     372 1 590 my $m = shift;
291              
292 372         484 my $string = '';
293 372         416 my $i = 0;
294              
295 372         606 my @flat = $m->flat;
296 372         683 foreach my $t (@flat) {
297 3974 100       5159 if ($t) {
298 2711         3680 my ($sign, $value, $unit) = ('','','');
299 2711 100       4570 if ($t < 0) {
    100          
300 689         760 $sign = '-';
301             }
302             elsif (length $string) {
303 1775         1952 $sign = '+';
304             }
305 2711 100 100     4857 unless (abs($t) == 1 and $i) {
306             #if (abs($t) !=1 or not $i) {
307 2609         2904 $value = abs($t);
308             }
309 2711         2666 $unit = ${ SYMBOLS() }[$i];
  2711         3855  
310 2711         8048 $string .= sprintf '%s%s%s', $sign, $value, $unit;
311             }
312 3974         4832 $i ++
313             }
314 372 50       4137 $string || '0';
315             }
316              
317             sub as_polarity {
318 0     0 0 0 my $m = shift;
319 0         0 my $string = '';
320 0         0 foreach my $t ($m->flat) {
321 0 0       0 $string .= $t > 0 ? '+' : ($t < 0 ? '-' : '0');
    0          
322             }
323             $string
324 0         0 }
325              
326              
327             sub as_e {
328 0     0 0 0 my $m = shift;
329              
330 0         0 my $string = '';
331 0         0 my $i = 0;
332              
333 0         0 foreach my $t ($m->flat) {
334 0 0       0 if ($t) {
335 0         0 my ($sign, $value, $unit) = ('+','','');
336 0 0       0 if ($t < 0) {
    0          
337 0         0 $sign = '-';
338             }
339             elsif (length $string) {
340 0         0 $sign = '+';
341             }
342             #if (abs($t) != 1 or $i == 0) {
343 0 0       0 if (abs($t)) {
344 0         0 $value = abs($t);
345             }
346 0         0 $unit = 'e' . $i;
347 0         0 $string .= sprintf '%s%s%s', $sign, $value, $unit;
348             #$string .= sprintf '%s%s%s', $sign, '', '';
349             }
350 0         0 $i ++
351             }
352 0 0       0 $string || '0'
353             }
354              
355              
356              
357             #
358             # compare the string format of this object to the given string
359             #
360              
361 0     0 0 0 sub eq { shift->as_string eq shift }
362              
363              
364              
365             #
366             # override these methods to test other algebras or the dual and split number systems ...
367             #
368             # doubling_product:See DOUBLING constant above for option choices. Override this method in your subclass if you like.
369             #
370             # i_squared: algebra selection. See I_SQUARED constant above for option choices. Override this method in your subclass if you like.
371             #
372              
373 1811732     1811732 1 2253107 sub doubling_product { DOUBLING_PRODUCT }
374 1811732     1811732 1 2047837 sub i_squared { I_SQUARED }
375              
376              
377              
378             #
379             # additional meta tools ...
380             #
381              
382 0     0 0 0 sub is_real { 0 } # you could not be here if you are real
383 4791838     4791838 1 5691972 sub is_complex { not ref (shift->a) }
384 0     0 1   sub is_quaternion { shift->_child_is('is_complex' ) }
385 0     0 1   sub is_octonion { shift->_child_is('is_quaternion' ) }
386 0     0 1   sub is_sedenion { shift->_child_is('is_octonion' ) }
387 0     0 1   sub is_trigintaduonions { shift->_child_is('is_sedenion' ) }
388 0     0 1   sub is_sexagintaquatronions { shift->_child_is('is_trigintaduonions' ) }
389 0     0 1   sub is_centumduodetrigintanions { shift->_child_is('is_sexagintaquatronions' ) }
390 0     0 1   sub is_ducentiquinquagintasexions { shift->_child_is('is_centumduodetrigintanions' ) }
391             #sub is_etc ...
392              
393              
394             #
395             # determine if the child is of the given type by common cayley dickson name ...
396             #
397              
398             sub _child_is {
399 0     0     my $m = shift;
400 0           my $method = shift;
401 0 0 0       not $m->is_complex and $m->a->can($method) and $m->a->$method;
402             }
403              
404             =encoding utf8
405              
406             =pod
407              
408             =head1 NAME
409              
410             CayleyDickson - create and operate with hypercomplex numbers
411              
412             =head1 SYNOPSIS
413              
414             =over 4
415              
416             use Tangle;
417             my $q1 = Tangle->new(1,0);
418             print "q1 = $q1\n";
419             $q1->x_gate;
420             print "X(q1) = $q1\n";
421             $q1->hadamard;
422             print "H(X(q1)) = $q1\n";
423              
424             my $q2 = Tangle->new(1,0);
425             print "q2 = $q2\n";
426              
427             # perform CNOT($q1 ⊗ $q2)
428             $q1->cnot($q2);
429              
430             print "q1 = $q1\n";
431             print "q2 = $q2\n";
432              
433             $q1->x_gate;
434             print "X(q1) = $q1\n";
435             print "entanglement causes q2 to automatically changed: $q2\n";
436              
437             =back
438              
439             =head1 DESCRIPTION
440              
441             =over 3
442              
443             Cayley-Dickson construction and operations are defined here: https://en.wikipedia.org/wiki/Cayley–Dickson_construction
444              
445             This object provides natural and intuitive operations on these numbers by overriding the native numeric operations (+,-,/,*)
446              
447             =back
448              
449             =head1 USAGE
450              
451              
452             =head2 new()
453              
454             =over 3
455              
456             # create a new CayleyDickson number "i" ...
457             my $q1 = CayleyDickson->new(0,1);
458              
459              
460             # create a new CayleyDickson number "1+2i+3j+4k" ...
461             my $q2 = CayleyDickson->new(1,2,3,4);
462              
463              
464             # create a bigger CayleyDickson number (a Sedenion) ...
465             my $q3 = CayleyDickson->new(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
466              
467              
468             # create a CayleyDickson number from others ...
469             my $one = CayleyDickson->new(0,1);
470             my $zero = CayleyDickson->new(1,0);
471             my $quaternion = CayleyDickson->new($one,$zero);
472              
473             =back
474              
475             =head2 conjugate()
476              
477             =over 3
478              
479             if z = (a,b)
480             then conjugate z = z* = (a,b)* = (a*,-b)
481             or conjugate(number) = number
482            
483             printf "The conjugate of q1 is: %s\n", $q1->conjugate;
484              
485             =back
486              
487             =head2 inverse()
488              
489             =over 3
490              
491             if z = (a,b)
492             then inverse z = z⁻¹ = (a,b)⁻¹ = (a,b)*/(norm(a,b)²)
493             or inverse(number) = number
494            
495             printf "The inverse of q1 is: %s\n", $q1->inverse;
496              
497             =back
498              
499             =head2 norm()
500              
501             =over 3
502              
503             if z = (a,b)
504             then norm z = norm(a,b) = √(norm(a)²+norm(b)²)
505             or norm(number) = number
506            
507             printf "The norm of q1 is: %s\n", $q1->norm;
508              
509             =back
510              
511             =head2 add()
512              
513             =over 3
514              
515             # ass z1 + z2 = (a,b)+(c,d) = (a+c,b+d)
516            
517             printf "The sum of q1 + q2 is: %s\n", $q1 + $q2;
518              
519             =back
520              
521             =head2 subtract()
522              
523             =over 3
524              
525             # subtract z1 - z2 = (a,b)-(c,d) = (a-c,b-d)
526              
527             printf "The difference of q1 - q2 is: %s\n", $q1 - $q2;
528              
529             =back
530              
531             =head2 divide()
532              
533             =over 3
534              
535             # divide z1 / z2 = z1 × inverse(z2)
536            
537             printf "The division of q1 / q2 is: %s\n", $q1 / $q2;
538              
539             =back
540              
541             =head2 multiply()
542              
543             =over 3
544              
545             # Multiply: (a,b)×(c,d) = (a×c - d*×b, d×a + b×c*) where x* = conjugate(x) or x if x is a number
546              
547             printf "The product of q1 * q2 is: %s\n", $q1 * $q2;
548              
549             =back
550              
551             =head2 new()
552              
553             =over 3
554              
555             create a new CayleyDickson number of any size ...
556              
557             # create the number 1+j-k ...
558             my $c = CayleyDickson->new( -1, 0, 1, -1 );
559              
560             # create an octonion ...
561             my $c = CayleyDickson->new( 3, 7, -2, 8, 0, 3, 3, 5 );
562              
563             # create a representation of the Horne bell state |+-> ...
564             my $c = CayleyDickson->new( 1/2, 1/2, 1/2 ,-1/2 );
565              
566             # create a 128 number construction: 1+2i+3j+4k+ .. + 128 ....
567             my $c = CayleyDickson->new(1 .. 128);
568              
569             =back
570              
571             =head2 tensor()
572              
573             =over 3
574              
575             Tensors two Cayley Dickson numbers to calculate a new number of higher dimensional construction.
576             reference: https://en.wikipedia.org/wiki/Tensor_product
577              
578             # calculate the tensor of c1 ⊗ c2 ...
579             $d = $c1->tensor($c2);
580              
581             $d will be a number of the product of the dimensions of c1 and c2.
582              
583             =back
584              
585             =head2 a()
586              
587             =head2 b()
588              
589             =over 3
590              
591             returns the two objects or numbers held by this object
592              
593             =back
594              
595             =head2 flat()
596              
597             =over 3
598              
599             return all the coefficients of the number as an array
600            
601             printf "[%s]\n", join( ', ', $q1->flat);
602              
603              
604             =back
605              
606             =head2 as_string()
607              
608             =over 3
609              
610             called automatically when this object is requested in a string form.
611             if you want to force the object to be resolved as a string ...
612              
613             printf "q1 as a string = %s\n", $q1->as_string;
614              
615             =back
616              
617             =head2 i_squared()
618              
619             =over 3
620              
621             returns the square of i: i² = -1
622              
623             normally this will be -1, but you can change it to +1 or 0 using the constant I_SQUARED
624              
625              
626              
627             =back
628              
629             =head2 doubling_product()
630              
631             =over 3
632              
633             something
634              
635             =back
636              
637             =head2 is_complex()
638              
639             =head2 is_quaternion()
640              
641             =head2 is_octonion()
642              
643             =head2 is_sedenion()
644              
645             =head2 is_trigintaduonions()
646              
647             =head2 is_sexagintaquatronions()
648              
649             =head2 is_centumduodetrigintanions()
650              
651             =head2 is_ducentiquinquagintasexions()
652              
653             returns true if the given object has depth equal to the function name
654              
655             if ($q1->is_octionion) {
656             print "q1 is an Octonion\n";
657             }
658             else {
659             print "q1 is NOT an Octonion\n";
660             }
661              
662             =back
663              
664             =head1 SUMMARY
665              
666             =over 3
667              
668             This object holds Cayley Dickson numbers and provides math operations on them.
669              
670             =back
671              
672             =head1 AUTHOR
673              
674             Jeff Anderson
675             truejeffanderson@gmail.com
676              
677             =cut
678              
679              
680             1;
681              
682             __END__