roszcz commited on
Commit
33f466b
·
1 Parent(s): 9008481

Add code :rocket:

Browse files
Files changed (5) hide show
  1. .isort.cfg +5 -0
  2. .pre-commit-config.yaml +30 -0
  3. README.md +22 -4
  4. main.py +216 -0
  5. physics/particles.py +87 -0
.isort.cfg ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [settings]
2
+ length_sort=true
3
+ multi_line_output=3
4
+ include_trailing_comma=true
5
+ force_sort_within_sections=true
.pre-commit-config.yaml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_language_version:
2
+ python: python3.12
3
+ repos:
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v4.4.0
6
+ hooks:
7
+ - id: check-merge-conflict
8
+ - id: end-of-file-fixer
9
+ - id: trailing-whitespace
10
+ - id: debug-statements
11
+ - id: check-yaml
12
+ - id: check-docstring-first
13
+ - id: requirements-txt-fixer
14
+ - repo: https://github.com/pycqa/flake8
15
+ rev: 6.0.0
16
+ hooks:
17
+ - id: flake8
18
+ args: ["--max-line-length=120","--extend-ignore=E203","--per-file-ignores=.github/scripts/bump_version.py:E402"]
19
+ - repo: https://github.com/ambv/black
20
+ rev: 23.7.0
21
+ hooks:
22
+ - id: black
23
+ args: [--line-length=100]
24
+ additional_dependencies: ['click==8.0.4']
25
+ - repo: https://github.com/pycqa/isort
26
+ rev: 5.12.0
27
+ hooks:
28
+ - id: isort
29
+ name: isort
30
+ args: [--line-length=100]
README.md CHANGED
@@ -1,14 +1,32 @@
1
  ---
2
  title: Bell Violation
3
- emoji: 👁
4
- colorFrom: gray
5
  colorTo: indigo
6
  sdk: streamlit
7
  sdk_version: 1.39.0
8
- app_file: app.py
9
  pinned: false
10
  license: mit
11
  short_description: Data Science Internship Challenge 2024
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Bell Violation
3
+ emoji: 🚀
4
+ colorFrom: purple
5
  colorTo: indigo
6
  sdk: streamlit
7
  sdk_version: 1.39.0
8
+ app_file: main.py
9
  pinned: false
10
  license: mit
11
  short_description: Data Science Internship Challenge 2024
12
  ---
13
 
14
+ ### Code Style
15
+
16
+ This repository uses pre-commit hooks with forced python formatting ([black](https://github.com/psf/black),
17
+ [flake8](https://flake8.pycqa.org/en/latest/), and [isort](https://pycqa.github.io/isort/)):
18
+
19
+ ```sh
20
+ pip install pre-commit
21
+ pre-commit install
22
+ ```
23
+
24
+ Whenever you execute `git commit` the files altered / added within the commit will be checked and corrected.
25
+ `black` and `isort` can modify files locally - if that happens you have to `git add` them again.
26
+ You might also be prompted to introduce some fixes manually.
27
+
28
+ To run the hooks against all files without running `git commit`:
29
+
30
+ ```sh
31
+ pre-commit run --all-files
32
+ ```
main.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ import streamlit as st
4
+ from matplotlib import pyplot as plt
5
+
6
+ from physics.particles import OneBitEntangledPhoton, LocalDeterministicPhoton
7
+
8
+
9
+ def main():
10
+ st.write("# Object Oriented Bell Violation")
11
+ st.write(
12
+ """
13
+ This is a part of the coding challenge for [EPR Labs](https://epr-labs.com) data-science internship for physicists.
14
+
15
+ The challenge is to implement a non-local model of a pair of entangled particles that recreates
16
+ the results of Bell-violating EPR experiments through a Monte Carlo simulation.
17
+ """
18
+ )
19
+
20
+ st.write(
21
+ """
22
+ Particle class should inherid `PolarizedParticleBase` – for simplicity you can assume
23
+ it's a photon, and we're interested in measuring polarization given a detector at an angle.
24
+ """
25
+ )
26
+
27
+ code = """
28
+ class PolarizedParticleBase(ABC):
29
+ @abstractmethod
30
+ def measure_polarization(self, detector_angle_rad: float) -> PolarizationMeasurementOutcome:
31
+ pass
32
+ """
33
+ st.code(code, language="python")
34
+
35
+ st.write(
36
+ """
37
+ The Monte Carlo simulation should perform at least 10k measurements of an entangled
38
+ pair with random detector angles. We're interested in tracking the information about
39
+ detector angles and measurement outputs. Here's an overview of what the simulation loop can look like:
40
+ """
41
+ )
42
+
43
+ code = """
44
+ results = []
45
+ for it in range(100_000):
46
+ a_photon = YourPhoton(...)
47
+ b_photon = YourPhoton(...)
48
+
49
+ # Your entanglement mechanism
50
+ a_photon.entangle(b_photon)
51
+
52
+ # We are interested only in the angle difference in range [0, pi/2]
53
+ a_detector_angle = 0
54
+ b_detector_angle = np.random.random() * np.pi / 2
55
+
56
+ a_polarization_status = a_photon.measure_polarization(a_detector_angle)
57
+ b_polarization_status = b_photon.measure_polarization(b_detector_angle)
58
+ result = {
59
+ "a_outcome": a_polarization_status.value,
60
+ "b_outcome": b_polarization_status.value,
61
+ "a_detector": a_detector_angle,
62
+ "b_detector": b_detector_angle,
63
+ }
64
+ results.append(result)
65
+ """
66
+ st.code(code, language="python")
67
+
68
+ st.write(
69
+ """
70
+ Below you can see results for two different simulations.
71
+ One for a local hidden variables model, which fails to violate Bell inequalities,
72
+ and one for a model with 1-bit non-local communication, that violates Bell inequalities, but
73
+ fails to reproduce empirical results.
74
+ """
75
+ )
76
+
77
+ st.write("## Local Photon Model")
78
+ st.write("What Einstein hoped for 🥲")
79
+ df = run_local_experiment()
80
+ df["angle_bin"] = df.angle_diff.round(2)
81
+ # TODO: Write an experiment results wrapper class & nice chart
82
+ angle_agreement = df.groupby("angle_bin").agreement.mean().reset_index()
83
+
84
+ fig = draw_polarization_agreement_chart(agreement_df=angle_agreement)
85
+ st.pyplot(fig)
86
+
87
+ st.write("## 1-bit superluminal communication")
88
+ st.write(
89
+ "This simulation recreates a model proposed by T.Maudlin in 1992 [[1](https://www.jstor.org/stable/192771)]."
90
+ )
91
+ df = run_entangled_experiment()
92
+ df["angle_bin"] = df.angle_diff.round(2)
93
+ # TODO: Write an experiment results wrapper class & nice chart
94
+ angle_agreement = df.groupby("angle_bin").agreement.mean().reset_index()
95
+
96
+ fig = draw_polarization_agreement_chart(agreement_df=angle_agreement)
97
+ st.pyplot(fig)
98
+
99
+ show_refs()
100
+
101
+
102
+ def show_refs():
103
+ title = "The Communication Cost of Simulating Bell Correlations"
104
+ link = "https://arxiv.org/abs/quant-ph/0304076"
105
+ st.write(f"[{title}]({link})")
106
+
107
+
108
+ def draw_polarization_agreement_chart(agreement_df: pd.DataFrame):
109
+ fig, ax = plt.subplots(figsize=[9, 4])
110
+ x = agreement_df.angle_bin.values * 180 / np.pi
111
+ ax.plot(
112
+ x,
113
+ agreement_df.agreement,
114
+ ".",
115
+ ms=7,
116
+ label="this simulation",
117
+ color="indigo",
118
+ )
119
+
120
+ ax.plot(
121
+ x,
122
+ np.cos(agreement_df.angle_bin) ** 2,
123
+ label="EPR experiments",
124
+ color="teal",
125
+ )
126
+ ax.set_xlabel("Detectors angle difference [degrees]", fontsize=14)
127
+ ax.set_ylabel(
128
+ "Proportion of agreement in\nspace time separated\npolarization angle measurements",
129
+ fontsize=14,
130
+ )
131
+
132
+ ax.set_ylim(0, 1)
133
+ ax.set_xlim(0, x.max())
134
+
135
+ ax.legend()
136
+
137
+ y_down = np.zeros_like(x)
138
+ # y_up = np.ones_like(x)
139
+ y_diagonal = np.linspace(1, 0, len(x))
140
+ ax.fill_between(x, y_down, y_diagonal, color="gray", alpha=0.2)
141
+ ax.text(10.3, 0.3, "Possible with\nBell-local theory", fontsize=20, rotation=0)
142
+
143
+ # ax.fill_between(x, y_diagonal, y_up, color="gray", alpha=0.4)
144
+ ax.text(54.3, 0.50, "Not possible with\nBell-local theory", fontsize=20, rotation=0)
145
+
146
+ return fig
147
+
148
+
149
+ def run_entangled_experiment(n_steps: int = 100_000) -> pd.DataFrame:
150
+ results = []
151
+ for it in range(n_steps):
152
+ reference_angle = np.random.random() * np.pi
153
+ a_photon = OneBitEntangledPhoton(reference_angle)
154
+ b_photon = OneBitEntangledPhoton(reference_angle)
155
+
156
+ a_photon.entangle(b_photon)
157
+ b_photon.entangle(a_photon)
158
+
159
+ # We are interested in the angle difference between detectors
160
+ # and we only want to measure within range of [0 - pi/2] ...
161
+ a_detector_angle = 0
162
+ # ... so we only allow the movement of the second detector within that range
163
+ b_detector_angle = np.random.random() * np.pi / 2
164
+
165
+ a_polarization_status = a_photon.measure_polarization(a_detector_angle)
166
+ b_polarization_status = b_photon.measure_polarization(b_detector_angle)
167
+ result = {
168
+ "a_outcome": a_polarization_status.value,
169
+ "b_outcome": b_polarization_status.value,
170
+ "a_detector": a_detector_angle,
171
+ "b_detector": b_detector_angle,
172
+ }
173
+ results.append(result)
174
+
175
+ df = pd.DataFrame(results)
176
+ df["agreement"] = df.a_outcome == df.b_outcome
177
+ df["angle_diff"] = df.b_detector - df.a_detector
178
+
179
+ return df
180
+
181
+
182
+ def run_local_experiment(n_steps: int = 100_000) -> pd.DataFrame:
183
+ results = []
184
+ for it in range(n_steps):
185
+ # In the experimental setup we have photons with same polarization
186
+ polarization_angle = np.random.random() * np.pi
187
+ a_photon = LocalDeterministicPhoton(polarization_angle)
188
+ b_photon = LocalDeterministicPhoton(polarization_angle)
189
+
190
+ # We are interested in the angle difference between detectors
191
+ # and we only want to measure within range of [0 - pi/2] ...
192
+ a_detector_angle = 0
193
+ # ... so we only allow the movement of the second detector within that range
194
+ b_detector_angle = np.random.random() * np.pi / 2
195
+
196
+ # Without entanglement order of measurement doesn't matter
197
+ # (does it matter with entangled particles? :thinking:)
198
+ a_polarization_status = a_photon.measure_polarization(a_detector_angle)
199
+ b_polarization_status = b_photon.measure_polarization(b_detector_angle)
200
+ result = {
201
+ "a_outcome": a_polarization_status.value,
202
+ "b_outcome": b_polarization_status.value,
203
+ "a_detector": a_detector_angle,
204
+ "b_detector": b_detector_angle,
205
+ }
206
+ results.append(result)
207
+
208
+ df = pd.DataFrame(results)
209
+ df["agreement"] = df.a_outcome == df.b_outcome
210
+ df["angle_diff"] = df.b_detector - df.a_detector
211
+
212
+ return df
213
+
214
+
215
+ if __name__ == "__main__":
216
+ main()
physics/particles.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+ from abc import ABC, abstractmethod
3
+
4
+ import numpy as np
5
+
6
+
7
+ class PolarizationMeasurementOutcome(Enum):
8
+ PASSED = True
9
+ ABSORBED = False
10
+
11
+
12
+ class PolarizedParticleBase(ABC):
13
+ @abstractmethod
14
+ def measure_polarization(self, detector_angle_rad: float) -> PolarizationMeasurementOutcome:
15
+ pass
16
+
17
+
18
+ class EntangledPolarizedParticleBase(PolarizedParticleBase):
19
+ @abstractmethod
20
+ def superluminal_communication(self):
21
+ pass
22
+
23
+
24
+ class LocalDeterministicPhoton(PolarizedParticleBase):
25
+ def __init__(self, polarization_angle: float):
26
+ self.polarization_angle = polarization_angle
27
+
28
+ def measure_polarization(self, detector_angle_rad: float) -> PolarizationMeasurementOutcome:
29
+ angle_difference = self.polarization_angle - detector_angle_rad
30
+ # This means the difference is within [-pi/4, pi/4]
31
+ if np.cos(angle_difference) ** 2 > 0.5:
32
+ return PolarizationMeasurementOutcome.PASSED
33
+ else:
34
+ # And this means it's more than pi/4 and less than 3pi/4
35
+ return PolarizationMeasurementOutcome.ABSORBED
36
+
37
+
38
+ class OneBitEntangledPhoton(EntangledPolarizedParticleBase):
39
+ def __init__(
40
+ self,
41
+ reference_angle_rad: float,
42
+ ):
43
+ self.decided = False
44
+ self.use_strategy_b = False
45
+
46
+ # Entangled photons do not have polarization decided when created
47
+ # but they share a reference frame
48
+ self.reference_angle_rad = reference_angle_rad
49
+
50
+ def strategy_a(self, detector_angle_rad: float) -> PolarizationMeasurementOutcome:
51
+ angle_difference = self.reference_angle_rad - detector_angle_rad
52
+
53
+ # This means the difference is within [-pi/4, pi/4]
54
+ if np.cos(angle_difference) ** 2 > 0.5:
55
+ return PolarizationMeasurementOutcome.PASSED
56
+ else:
57
+ # And this means it's more than pi/4 and less than 3pi/4
58
+ return PolarizationMeasurementOutcome.ABSORBED
59
+
60
+ def strategy_b(self, detector_angle_rad: float) -> PolarizationMeasurementOutcome:
61
+ angle_difference = self.reference_angle_rad - detector_angle_rad
62
+
63
+ # This means the difference is within [-pi/4, pi/4]
64
+ if np.cos(angle_difference - np.pi / 4) ** 2 > 0.5:
65
+ return PolarizationMeasurementOutcome.PASSED
66
+ else:
67
+ # And this means it's more than pi/4 and less than 3pi/4
68
+ return PolarizationMeasurementOutcome.ABSORBED
69
+
70
+ def entangle(self, other_photon: "OneBitEntangledPhoton"):
71
+ self.other_photon = other_photon
72
+
73
+ def superluminal_communication(self, use_strategy_b: bool):
74
+ self.use_strategy_b = use_strategy_b
75
+ self.decided = True
76
+
77
+ def measure_polarization(self, detector_angle_rad: float) -> PolarizationMeasurementOutcome:
78
+ # Quantum magic
79
+ if not self.decided:
80
+ angle_difference = self.reference_angle_rad - detector_angle_rad
81
+ self.use_strategy_b = (angle_difference - np.pi / 8) % (np.pi / 2) < np.pi / 4
82
+ self.other_photon.superluminal_communication(use_strategy_b=self.use_strategy_b)
83
+
84
+ if self.use_strategy_b:
85
+ return self.strategy_b(detector_angle_rad)
86
+ else:
87
+ return self.strategy_a(detector_angle_rad)