Sign In/My Account | View Cart  
advertisement


Listen Print

Quantum::Entanglement
by Alex Gough | Pages: 1, 2

Objective Reality

We want to represent something which has many values (and store these somewhere) while making it look like there's only one value present. Objects in Perl are nothing more than scalars that know slightly more than usual. When a new entanglement is created, we create a new object, and return that to the calling program. Deep within the module we have a routine which is similar to:


 sub entangle {

   my $self = [ ~ data goes in here ~ ];

   return bless $self, 'Quantum::Entanglement';

 }

exactly how we store the data is covered below. We then turn this into a 'core' function by importing it into the namespace which asked for it.

When Worlds Collide

We've created a superposition of values and sent it back to our user. What needs to happen when they write something like:

 $talk = entangle( 1=>'Ships',    1=>'Sealing Wax',
                   1=>'Cabbages', 1=>'Kings'        );
 $more = $talk . ' yada yada yada';

We want to redefine the meaning of concatenation when an entangled object is involved. Perl lets us do this using the overload module. Within the Quantum::Entanglement module we say:

 use overload
        '+'  => sub { binop(@_, sub{$_[0] + $_[1]} ) },
     # more ...
        '.'  => sub { binop(@_, sub{$_[0] . $_[1]} ) },
     # yet more ...

Whenever someone applies the '.' operator to our object, a subroutine (in this case an anonymous one) is called to handle the operation, the result of this subroutine is then used as the result of the operation. Because the module provides new behaviours for all of Perl's operations, we write a generic routine to handle Binary Non-observational Operations and pass this the values to operate on along with another anonymous routine (which it will see as a code-ref) so that it knows which operation to perform. This allows us to re-use the code which works out if both operands are objects and if they are reversed and pieces together the data structures we use. binop is described below.

Data Structures with Hair

This module lives and dies on the strength of its data structures. We need to ensure that every variable (or, more correctly, object) knows about all the other superpositions it has been involved with throughout the course of the program without having any direct pointers between them.

When we create a new variable, we give it the following structure:

 sub entangle {
   my $universe = [ [ @_[0,1] ], # amp1, val1
                    [ @_[2,3] ], ...  ];
   my $offsets  = [];
   $var = [ \$universe, 1, \$offsets];
   $offsets->[0] = \ $var->[1];
   return bless $var, 'Quantum::Entanglement';
 }

there's a lot going on here, so pay attention. $universe is a list of lists (lol), essentially a two dimensional table with the first two columns holding the amplitudes and values of our superposition. $var contains a reference which points at a scalar which then points at the universe, rather like this:

 ($var->[0]) ---> (anonymous scalar) ---> $universe

The second value in $var is a number which indicates the column in the universe that we need to look at to find the values of our superposition. The last field of $var again points to a pointer to an array. This array though contains a scalar which points directly at the scalar which holds the number representing the offset of the values in the universe, something like this:

 $var[ (->X->universe), (number), (->Y->offsets[  ])  ]
                            \------<----<-------/

Now, when we want this object to interact with another object, all we need to do is make $var->[0] and $var->[1] for each object end up refering to the same universe. Easy, you might say, given that we have both objects around. But what if one had already interacted with another variable, which we cannot directly access anymore? This is where our extra level of indirection is required. Because each variable contains something which points at something else which then points at their set of values, we merely need to make sure that the 'something else' ends up pointing at the same thing for everything. So, we delve into each object's universe, choosing one which will contain the data for both objects (and thus for all those which have interacted in the past) and move all the data from the other object's universe into it. We then make our middle reference the same for each object.

Initially,


 universe1 = [[a1,av1],      [a2,av2]      ,... ]

 universe2 = [[b1,bv1,c1,cv1],[b2,bv2,c1,cv1],... ] 

 $var1[ (->X->universe1), 1,... ] # we have this object

 $var2[ (->Y->universe2), 1,... ] #  and this object

 $var3[ (->Y->universe2), 3,... ] # but not this one

then by pointing Y at universe1 the whole structure of our objects becomes


 universe1 = [[a1,av1,b1,bv1,c1,cv1],[a2,v2,b1,bv1,c1,cv1] ,... ]

 $var1[ (->X->universe1), 1,... ] # we have this object

 $var2[ (->Y->universe1), 3,... ] #  and this object

 $var3[ (->Y->universe1), 5,... ] # but not this one

To allow every possible value of one variable to interact with every possible value of our other variables, we need to follow a crossing rule so that the rows of our merged universe look like this:


 universe1   universe2            result

 a1 av1      b1 bv1 c1 cv1      a1 av9  ]

                            \------<----<-------/

Now, when we want this object to interact with another object, all we need to do is make $var->[0] and $var->[1] for each object end up refering to the same universe. Easy, you might say, given that we have both objects around. But what if one had already interacted with another variable, which we cannot directly access anymore? This is where our extra level of indirection is required. Because each variable contains something which points at something else which then points at their set of values, we merely need to make sure that the 'something else' ends up pointing at the same thing for everything. So, we delve into each object's universe, choosing one which will contain the data for both objects (and thus for all those which have interacted in the past) and move all the data from the other object's universe into it. We then make our middle reference the same for each object.

Initially,


 universe1 = [[a1,av1],      [a2,av2]      ,... ]

 universe2 = [[b1,bv1,c1,cv1],[b2,bv2,c1,cv1],... ] 

 $var1[ (->X->universe1), 1,... ] # we have this object

 $var2[ (->Y->universe2), 1,... ] #  and this object

 $var3[ (->Y->universe2), 3,... ] # but not this one

then by pointing Y at universe1 the whole structure of our objects becomes


 universe1 = [[a1,av1,b1,bv1,c1,cv1],[a2,v2,b1,bv1,c1,cv1] ,... ]

 $var1[ (->X->universe1), 1,... ] # we have this object

 $var2[ (->Y->universe1), 3,... ] #  and this object

 $var3[ (->Y->universe1), 5,... ] # but not this one

To allow every possible value of one variable to interact with every possible value of our other variables, we need to follow a crossing rule so that the rows of our merged universe look like this:


 universe1   universe2            result

 a1 av1      b1 bv1 c1 cv1      a1 av1  b1 bv1  c1 cv1

 a2 av2    * b1 bv1 c2 cv2  ==> a1 av1  b1 bv1  c2 cv2

                                a2 av2  b1 bv1  c1 cv1

                                a2 av2  b1 bv1  c2 cv2

so that every row in the first universe is paired with every row of the second. We then need to update the offsets for each variable which has had data moved from one universe to another. As the offsets array contains pointers back to these values, it is easy to increase each one by the correct amount. So, given two entanglements in @_, and a bit of cheating with map, we can say


  my $offsets1 = ${$_[0]->[2]}; # middle-man reference

  my $offsets2 = ${$_[1]->[2]};

  my $extra = scalar(@{ ${$_[0]->[0]} });

  push @$offsets1, map {$$_+=$extra; $_} @$offsets2;

  ${$_[1]->[2]} = $offsets1;

and you can't get clearer than that.

So binop is written like so (assuming that we can only be given two entangled variables in the correct order, for the full story, read the source):


 sub binop {

    my ($obj1,$obj2,$r,$code) = @_;

    _join($obj1,$obj2);   # ensure universes shared

    my ($os1, $os2) = ($obj1->[1],$obj2->[1]);

    my $new = $obj1->_add(); # new var also shares universe

    foreach my $state (@{${$obj1->[0]}}) {

       push( @$state, $state->[$os1-1]*$state->[$os2-1],

                      &$code( $state->[$os1], $state->[$os2] );

    }

    return $new;

 }

or, in English: make sure each variable is in the same universe then create a new variable in the same universe. For every row of the universe: add two extra values, the first is the product of the two input amplitudes, the second is the result of our operation on our two input values. Here you see the tremendous value of code reuse, no sane man would write such a routine more than once. Or, more correctly, no man would remain sane if they tried.

London Bridge is Falling Down

How do we collapse our superpositions so that every entangled variable is affected even though we can only access one of them at once? When we perform an observational operation (if ($var){...}, say) we simply need to split our universe (table of values) into two groups, those which lead to our operator returning a true value and those that do not. We add up the probability amplitudes for each value in each group, square these to get two numbers and use these to decide which group to keep. To cause our collapse we merely need to delete all the rows of the universe which form the other group which will remove any value of any variable in that row.


Getting the Module

The module distribution, like all good things, is available from the CPAN and includes a few short demonstrations of what the module can do, along with plenty of explanation (including Shor's algorithm and the square root of NOT gate outlined above). The source of this, and any other module on the CPAN, is available for inspection. If you have a burning desire to find out how the mystical wheel was first invented, Perl, and its community, will gladly show you.