Advertisement
chemoelectric

EPR-B simulation

Aug 10th, 2023
1,010
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.49 KB | Source Code | 0 0
  1. #!/bin/env -S python3
  2.  
  3. #
  4. # Reference:
  5. #
  6. #   A. F. Kracklauer, ‘EPR-B correlations: non-locality or geometry?’,
  7. #   J. Nonlinear Math. Phys. 11 (Supp.) 104–109 (2004).
  8. #   https://doi.org/10.2991/jnmp.2004.11.s1.13 (Open access, CC BY-NC)
  9. #
  10.  
  11. import sys
  12. import multiprocessing as mp
  13. import time as tm
  14. from math import atan2, cos, floor, pi, radians, sin, sqrt
  15. from random import randint, seed, uniform
  16.  
  17. def modulo(a, p):
  18.     '''This is like the MODULO function of Fortran.'''
  19.     return (a - (floor(a / p) * p));
  20.  
  21. class Vector:
  22.     '''A simple implementation of Gibbs vectors, suited to our
  23.    purpose.'''
  24.  
  25.     def __init__(self, magnitude, angle):
  26.         '''A vector is stored in polar form, with the angle in
  27.        degrees between 0 (inclusive) and 360 (exclusive).'''
  28.         self.magnitude = magnitude
  29.         self.angle = modulo(angle, 360)
  30.  
  31.     def __repr__(self):
  32.         return ("Vector(" + str(self.magnitude)
  33.                 + "," + str(self.angle) + ")")
  34.  
  35.     @staticmethod
  36.     def from_rect(x, y):
  37.         '''Return a vector for the given rectangular coordinates.'''
  38.         return Vector(sqrt(x**2 + y**2),
  39.                       modulo (180 * pi * atan2 (y, x), 360))
  40.  
  41.     def to_rect(self):
  42.         '''Return the x and y coordinates of the vector.'''
  43.         x = self.magnitude * cos ((pi / 180) * self.angle)
  44.         y = self.magnitude * sin ((pi / 180) * self.angle)
  45.         return (x, y)
  46.  
  47.     @staticmethod
  48.     def scalar_product(vector, scalar):
  49.         '''Multiply a vector by a scalar, returning a new vector.'''
  50.         return Vector(vector.magnitude * scalar, vector.angle)
  51.  
  52.     @staticmethod
  53.     def dot_product(u, v):
  54.         '''Return the dot product of two vectors.'''
  55.         return (u.magnitude * v.magnitude
  56.                 * cos ((pi / 180) * (u.angle - v.angle)))
  57.  
  58.     @staticmethod
  59.     def difference(u, v):
  60.         '''Return the difference of two vectors.'''
  61.         (xu, yu) = u.to_rect()
  62.         (xv, yv) = v.to_rect()
  63.         return Vector.from_rect(xu - xv, yu - yv)
  64.  
  65.     @staticmethod
  66.     def projection(u, v):
  67.         '''Return the projection of vector u onto vector v.'''
  68.         scalar = Vector.dot_product(u, v) / Vector.dot_product(v, v)
  69.         return Vector.scalar_product(v, scalar)
  70.  
  71. class Mechanism:
  72.     '''A Mechanism represents a part of the experimental apparatus.'''
  73.  
  74.     def __call__(self):
  75.         '''Run the mechanism.'''
  76.         while True:
  77.             self.run()
  78.             # A small pause to try not to overtax the computer.
  79.             tm.sleep(0.001)
  80.  
  81.     def run(self):
  82.         '''Fill this in with what the mechanism does.'''
  83.         raise NotImplementedError()
  84.  
  85. class LightSource(Mechanism):
  86.     '''A LightSource occasionally emits oppositely plane-polarized
  87.    light pulses, of fixed amplitude, polarized 90° with respect to
  88.    each other. The pulses are represented by the vectors (1,0°) and
  89.    (1,90°), respectively. One is emitted to the left, the other to
  90.    the right. The probability is 1/2 that the (1,0°) pulse is emitted
  91.    to the left.
  92.  
  93.    The LightSource records which pulse it emitted in which direction.
  94.  
  95.    '''
  96.  
  97.     def __init__(self, L, R, log):
  98.         Mechanism.__init__(self)
  99.         self.L = L         # Queue gets (1,0°) or (1,90°)
  100.         self.R = R         # Queue gets (1,90°) or (1,0°)
  101.         self.log = log     # Queue gets 0 if (1,0°) sent left, else 1.
  102.  
  103.     def run(self):
  104.         '''Emit a light pulse.'''
  105.         n = randint(0, 1)
  106.         self.L.put(Vector(1, n * 90))
  107.         self.R.put(Vector(1, 90 - (n * 90)))
  108.         self.log.put(n)
  109.  
  110. class PolarizingBeamSplitter(Mechanism):
  111.     '''A polarizing beam splitter takes a plane-polarized light pulse
  112.    and splits it into two plane-polarized pulses. The directions of
  113.    polarization of the two output pulses are determined solely by the
  114.    angular setting of the beam splitter—NOT by the angle of the
  115.    original pulse. However, the amplitudes of the output pulses
  116.    depend on the angular difference between the impinging light pulse
  117.    and the beam splitter.
  118.  
  119.    Each beam splitter is designed to select randomly between one of
  120.    two angle settings. It records which setting it selected (by
  121.    putting that information into one of its output queues).
  122.  
  123.    '''
  124.  
  125.     def __init__(self, S, S1, S2, log, angles):
  126.         Mechanism.__init__(self)
  127.         self.S = S              # Vector queue to read from.
  128.         self.S1 = S1            # One vector queue out.
  129.         self.S2 = S2            # The other vector queue out.
  130.         self.log = log          # Which angle setting was used.
  131.         self.angles = angles
  132.  
  133.     def run(self):
  134.         '''Split a light pulse into two pulses. One of output pulses
  135.        may be visualized as the vector projection of the input pulse
  136.        onto the direction vector of the beam splitter. The other
  137.        output pulse is the difference between the input pulse and the
  138.        first output pulse: in other words, the orthogonal component.'''
  139.  
  140.         angle_setting = randint(0, 1)
  141.         self.log.put(angle_setting)
  142.  
  143.         angle = self.angles[angle_setting]
  144.         assert (0 <= angle and angle < 90)
  145.  
  146.         v = self.S.get()
  147.         v1 = Vector.projection(v, Vector(1, angle))
  148.         v2 = Vector.difference(v, v1)
  149.  
  150.         self.S1.put(v1)
  151.         self.S2.put(v2)
  152.  
  153. class LightDetector(Mechanism):
  154.     '''Our light detector is assumed to work as follows: if a
  155.    uniformly distributed random number between 0 and 1 is less than
  156.    or equal to the intensity (square of the amplitude) of an
  157.    impinging light pulse, then the detector outputs a 1, meaning the
  158.    pulse was detected. Otherwise it outputs a 0, representing the
  159.    quiescent state of the detector.
  160.  
  161.    '''
  162.  
  163.     def __init__(self, In, Out):
  164.         Mechanism.__init__(self)
  165.         self.In = In
  166.         self.Out = Out
  167.  
  168.     def run(self):
  169.         '''When a light pulse comes in, either detect it or do not.'''
  170.         pulse = self.In.get()
  171.         intensity = pulse.magnitude**2
  172.         self.Out.put(1 if uniform(0, 1) <= intensity else 0)
  173.  
  174. class DataSynchronizer(Mechanism):
  175.     '''The data synchronizer combines the raw data from the logs and
  176.    the detector outputs, putting them into dictionaries of
  177.    corresponding data.
  178.  
  179.    '''
  180.  
  181.     def __init__(self, logS, logL, logR,
  182.                  detectedL1, detectedL2,
  183.                  detectedR1, detectedR2,
  184.                  dataout):
  185.         Mechanism.__init__(self)
  186.         self.logS = logS
  187.         self.logL = logL
  188.         self.logR = logR
  189.         self.detectedL1 = detectedL1
  190.         self.detectedL2 = detectedL2
  191.         self.detectedR1 = detectedR1
  192.         self.detectedR2 = detectedR2
  193.         self.dataout = dataout
  194.  
  195.     def run(self):
  196.         '''This method does the synchronizing.'''
  197.         self.dataout.put({"logS" : self.logS.get(),
  198.                           "logL" : self.logL.get(),
  199.                           "logR" : self.logR.get(),
  200.                           "detectedL1" : self.detectedL1.get(),
  201.                           "detectedL2" : self.detectedL2.get(),
  202.                           "detectedR1" : self.detectedR1.get(),
  203.                           "detectedR2" : self.detectedR2.get()})
  204.  
  205. def save_raw_data(filename, data):
  206.     def save_data(f):
  207.         f.write(str(len(data)))
  208.         f.write("\n")
  209.         for entry in data:
  210.             f.write(str(entry["logS"]))
  211.             f.write(" ")
  212.             f.write(str(entry["logL"]))
  213.             f.write(" ")
  214.             f.write(str(entry["logR"]))
  215.             f.write(" ")
  216.             f.write(str(entry["detectedL1"]))
  217.             f.write(" ")
  218.             f.write(str(entry["detectedL2"]))
  219.             f.write(" ")
  220.             f.write(str(entry["detectedR1"]))
  221.             f.write(" ")
  222.             f.write(str(entry["detectedR2"]))
  223.             f.write("\n")
  224.     if filename != "-":
  225.         with open(filename, "w") as f:
  226.             save_data(f)
  227.     else:
  228.         save_data(sys.stdout)
  229.  
  230. if __name__ == '__main__':
  231.  
  232.     if len(sys.argv) != 2 and len(sys.argv) != 3:
  233.         print("Usage: " + sys.argv[0] + " NUM_PULSES [RAW_DATA_FILE]")
  234.         sys.exit(1)
  235.     num_pulses = int(sys.argv[1])
  236.     raw_data_filename = (sys.argv[2] if len(sys.argv) == 3 else "-")
  237.  
  238.     seed()             # Initialize random numbers with a random seed.
  239.  
  240.     # Angles commonly used in actual experiments. Whatever angles you
  241.     # use have to be zero degrees or placed in Quadrant 1. This
  242.     # constraint comes with no loss of generality, because a
  243.     # polarizing beam splitter is actually a sort of rotated
  244.     # "X". Therefore its orientation can be specified by any one of
  245.     # the arms of the X. Using the Quadrant 1 arm simplifies data
  246.     # analysis.
  247.     anglesL = (0.0, 45.0)
  248.     anglesR = (22.5, 67.5)
  249.     assert (all(0 <= x and x < 90 for x in anglesL + anglesR))
  250.  
  251.     # Queues used for communications between the processes. (Note that
  252.     # the direction of communication is always forwards in time. This
  253.     # forwards-in-time behavior is guaranteed by using queues for the
  254.     # communications, instead of Python's two-way pipes.)
  255.     max_size = 100000
  256.     logS = mp.Queue(max_size)
  257.     logL = mp.Queue(max_size)
  258.     logR = mp.Queue(max_size)
  259.     L = mp.Queue(max_size)
  260.     R = mp.Queue(max_size)
  261.     L1 = mp.Queue(max_size)
  262.     L2 = mp.Queue(max_size)
  263.     R1 = mp.Queue(max_size)
  264.     R2 = mp.Queue(max_size)
  265.     detectedL1 = mp.Queue(max_size)
  266.     detectedL2 = mp.Queue(max_size)
  267.     detectedR1 = mp.Queue(max_size)
  268.     detectedR2 = mp.Queue(max_size)
  269.     dataout = mp.Queue(max_size)
  270.  
  271.     # Objects that will run in the various processes.
  272.     lightsource = LightSource(L, R, logS)
  273.     splitterL = PolarizingBeamSplitter(L, L1, L2, logL, anglesL)
  274.     splitterR = PolarizingBeamSplitter(R, R1, R2, logR, anglesR)
  275.     detectorL1 = LightDetector(L1, detectedL1)
  276.     detectorL2 = LightDetector(L2, detectedL2)
  277.     detectorR1 = LightDetector(R1, detectedR1)
  278.     detectorR2 = LightDetector(R2, detectedR2)
  279.     sync = DataSynchronizer(logS, logL, logR, detectedL1, detectedL2,
  280.                             detectedR1, detectedR2, dataout)
  281.  
  282.     # Processes.
  283.     lightsource_process = mp.Process(target=lightsource)
  284.     splitterL_process = mp.Process(target=splitterL)
  285.     splitterR_process = mp.Process(target=splitterR)
  286.     detectorL1_process = mp.Process(target=detectorL1)
  287.     detectorL2_process = mp.Process(target=detectorL2)
  288.     detectorR1_process = mp.Process(target=detectorR1)
  289.     detectorR2_process = mp.Process(target=detectorR2)
  290.     sync_process = mp.Process(target=sync)
  291.  
  292.     # Start the processes.
  293.     sync_process.start()
  294.     detectorL1_process.start()
  295.     detectorL2_process.start()
  296.     detectorR1_process.start()
  297.     detectorR2_process.start()
  298.     splitterL_process.start()
  299.     splitterR_process.start()
  300.     lightsource_process.start()
  301.  
  302.     data = []
  303.     for i in range(num_pulses):
  304.         data.append(dataout.get())
  305.     save_raw_data(raw_data_filename, data)
  306.  
  307.     # Shut down the apparatus.
  308.     logS.close()
  309.     logL.close()
  310.     logR.close()
  311.     L.close()
  312.     R.close()
  313.     L1.close()
  314.     L2.close()
  315.     R1.close()
  316.     R2.close()
  317.     detectedL1.close()
  318.     detectedL2.close()
  319.     detectedR1.close()
  320.     detectedR2.close()
  321.     dataout.close()
  322.     lightsource_process.terminate()
  323.     splitterL_process.terminate()
  324.     splitterR_process.terminate()
  325.     detectorL1_process.terminate()
  326.     detectorL2_process.terminate()
  327.     detectorR1_process.terminate()
  328.     detectorR2_process.terminate()
  329.     sync_process.terminate()
  330.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement