# -*- coding: utf-8 -*-
"""
Experimentul 4 — Experimentul Dopfer (Innsbruck, 1998)
Descris in documentul DIRD (DIA-08-1003-016), sectiunea IV, Fig. 5.

Fotoni intricati in impuls (SPDC, LiIO3). Fotonul de jos trece prin doua fante si
este scanat de D1. Fotonul de sus trece printr-o lentila "Heisenberg" (focala f);
detectorul D2 poate fi mutat in spatele lentilei:
  - la distanta z = f  (planul Fourier): D2 masoara IMPULSUL -> nu exista
    informatie which-way -> in coincidenta apar franje la D1;
  - la distanta z = 2f (planul imagine, cu cristalul ca "oglinda" Klyshko si
    distanta obiect totala d_a + d_b = 2f): D2 vede PRIN care fanta a trecut
    fotonul pereche -> franjele dispar.
Fara coincidente (singles la D1) nu apar franje NICIODATA, indiferent unde sta D2
-> mutarea lui D2 nu poate transmite un semnal (teorema de ne-semnalizare).

Model: propagatoare Fresnel + faza lentilei exp(-ik x^2/2f), integrare numerica
peste sursa, apertura lentilei si fante (imagine desfasurata Klyshko).
"""
import sys
import numpy as np
import tkinter as tk
from tkinter import ttk
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

LAM = 702e-6
K = 2 * np.pi / LAM
F = 300.0      # mm focala lentilei Heisenberg
DA = 350.0     # mm cristal -> lentila
DB = 250.0     # mm cristal -> fante   (DA + DB = 2F -> imagine la z = 2F)
DD = 300.0     # mm fante -> D1
D_F = 0.35     # mm separare fante
A_F = 0.07     # mm latime fanta
W_S = 0.3      # mm largimea sursei (pompa larga -> intricare in impuls buna)
X1 = np.linspace(-4, 4, 401)
XLGRID = np.linspace(-3, 3, 41)   # pozitii D2 pentru integrarea "singles"
PER_F1 = LAM * DD / D_F           # perioada franjelor la D1 (fante la dist. DD)

xs = np.linspace(-3 * W_S, 3 * W_S, 301)
G = np.exp(-xs ** 2 / (2 * W_S ** 2))
G /= G.sum()
xl = np.linspace(-4, 4, 1601)     # apertura lentilei (esantionare fina anti-aliasing)
xf = np.concatenate([c + np.linspace(-A_F / 2, A_F / 2, 9)
                     for c in (-D_F / 2, D_F / 2)])
# bratul fantelor: M[xs, x1]
M = (np.exp(1j * K * (xf[None, :, None] - xs[:, None, None]) ** 2 / (2 * DB))
     * np.exp(1j * K * (X1[None, None, :] - xf[None, :, None]) ** 2 / (2 * DD))
     ).sum(axis=1)


def brat_lentila(z, xL):
    """Amplitudinea bratului cu lentila pentru D2 la (z, xL): vector peste xs."""
    faza = (K * (xl[None, :] - xs[:, None]) ** 2 / (2 * DA)
            - K * xl[None, :] ** 2 / (2 * F)
            + K * (xL - xl[None, :]) ** 2 / (2 * z))
    return np.exp(1j * faza).sum(axis=1)


def coincidenta(z, xL):
    A = ((G * brat_lentila(z, xL))[None, :] @ M).ravel()
    p = np.abs(A) ** 2
    return p / (p.max() + 1e-30)


def singles(z):
    tot = np.zeros_like(X1)
    for xL in XLGRID:
        A = ((G * brat_lentila(z, xL))[None, :] @ M).ravel()
        tot += np.abs(A) ** 2
    return tot / (tot.max() + 1e-30)


def contrast(p):
    """Contrastul componentei de franje (perioada PER_F1) la D1, in [0, 1]."""
    tot = p.sum()
    if tot <= 0:
        return 0.0
    z = (p * np.exp(2j * np.pi * X1 / PER_F1)).sum() / tot
    return float(min(1.0, 2 * np.abs(z)))


class AppDopfer:
    def __init__(self, root):
        self.root = root
        root.title("Exp. 4 — Experimentul Dopfer 1998 (DIRD Fig. 5)")

        ctl = ttk.Frame(root, padding=8)
        ctl.pack(side=tk.LEFT, fill=tk.Y)
        ttk.Label(ctl, text="Lentila Heisenberg\n(Dopfer / Zeilinger)",
                  font=("Segoe UI", 12, "bold")).pack(pady=(0, 8))

        ttk.Label(ctl, text="Pozitia lui D2 dupa lentila, z [mm]:").pack(anchor="w")
        self.v_z = tk.DoubleVar(value=F)
        self.s_z = ttk.Scale(ctl, from_=150, to=750, variable=self.v_z,
                             command=lambda e: self.actualizeaza())
        self.s_z.pack(fill=tk.X)
        fr = ttk.Frame(ctl)
        fr.pack(fill=tk.X, pady=2)
        ttk.Button(fr, text="z = f (impuls)",
                   command=lambda: self.seteaza_z(F, 0.0)).pack(side=tk.LEFT, expand=True, fill=tk.X)
        ttk.Button(fr, text="z = 2f (imagine)",
                   command=lambda: self.seteaza_z(2 * F, D_F / 2)).pack(side=tk.LEFT, expand=True, fill=tk.X)

        ttk.Label(ctl, text="Pozitia transversala a lui D2 [mm]:").pack(anchor="w", pady=(10, 0))
        self.v_xL = tk.DoubleVar(value=0.0)
        ttk.Scale(ctl, from_=-2, to=2, variable=self.v_xL,
                  command=lambda e: self.actualizeaza()).pack(fill=tk.X)

        self.lbl = ttk.Label(ctl, text="", font=("Consolas", 10))
        self.lbl.pack(anchor="w", pady=10)

        ttk.Label(ctl, wraplength=230, justify="left", foreground="#555", text=(
            "Sus: rata D1 in coincidenta cu D2.\n"
            "z = f: D2 in planul Fourier -> impuls, fara which-way -> franje.\n"
            "z = 2f: D2 in planul imagine al fantelor -> which-way -> fara franje.\n\n"
            "Jos: singles la D1 — mereu fara franje, oriunde ar fi D2. "
            "'Emitatorul' care muta D2 nu poate trimite nimic fara canalul "
            "clasic de coincidenta.")).pack(anchor="w")

        self.fig = Figure(figsize=(8.5, 6.5), dpi=100)
        self.ax1 = self.fig.add_subplot(211)
        self.ax2 = self.fig.add_subplot(212)
        self.fig.subplots_adjust(hspace=0.4)
        self.canvas = FigureCanvasTkAgg(self.fig, master=root)
        self.canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        self.actualizeaza()
        if "--test" in sys.argv:
            root.after(3000, root.destroy)

    def seteaza_z(self, z, xL=None):
        self.v_z.set(z)
        if xL is not None:
            self.v_xL.set(xL)   # la z=2f, D2 se pune pe imaginea unei fante
        self.actualizeaza()

    def actualizeaza(self):
        z, xL = self.v_z.get(), self.v_xL.get()
        pc = coincidenta(z, xL)
        ps = singles(z)

        self.ax1.clear()
        self.ax1.plot(X1, pc, color="#d62728")
        self.ax1.fill_between(X1, pc, alpha=0.3, color="#d62728")
        self.ax1.set_title(f"D1 in coincidenta cu D2 (z = {z:.0f} mm, x_D2 = {xL:+.2f} mm)")
        self.ax1.set_ylabel("rata normata")

        self.ax2.clear()
        self.ax2.plot(X1, ps, color="#555")
        self.ax2.fill_between(X1, ps, alpha=0.3, color="#888")
        self.ax2.set_title("D1 fara coincidente (singles)")
        self.ax2.set_xlabel("pozitia x1 pe D1 [mm]")
        self.ax2.set_ylabel("rata normata")

        self.lbl.config(text=(f"z/f = {z / F:.2f}\n"
                              f"contrast franje (coincid.): {contrast(pc):.2f}\n"
                              f"contrast franje (singles):  {contrast(ps):.2f}"))
        self.canvas.draw_idle()


if __name__ == "__main__":
    root = tk.Tk()
    AppDopfer(root)
    root.mainloop()
