Spaces:
Running
Running
Add code :rocket:
Browse files- .isort.cfg +5 -0
- .pre-commit-config.yaml +30 -0
- README.md +22 -4
- main.py +216 -0
- 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:
|
5 |
colorTo: indigo
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.39.0
|
8 |
-
app_file:
|
9 |
pinned: false
|
10 |
license: mit
|
11 |
short_description: Data Science Internship Challenge 2024
|
12 |
---
|
13 |
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|