# -*- coding: utf-8 -*-
"""
Experimentul 3 — "Ghost Interference" (grupul Shih, 1995)
Descris in documentul DIRD (DIA-08-1003-016), sectiunile IV-V, Fig. 2-4 si 6.

O pereche de fotoni intricati in impuls (SPDC in BBO): fotonul "e" trece printr-o
fanta dubla (sau simpla) si este detectat de D1 (punctual, fix); fotonul "o" merge
direct si este scanat in pozitie de D2. Modelul foloseste propagatoare Fresnel de
la un punct comun de nastere x_s in cristal (imaginea "desfasurata" Klyshko, Fig. 4),
integrand numeric peste volumul sursei (efectul de "sursa groasa", Fig. 6):

  A(y1, x2) = suma_fante integrala dxs G(xs) *
              exp[ik((x_f - x_s)^2/2L1 + (y1 - x_f)^2/2Ld + (x2 - x_s)^2/2L2)]

 - IN COINCIDENTA cu D1 fix la y1: p(x2) = |A(y1, x2)|^2  -> franje "fantoma"
 - FARA coincidente (singles): suma peste toate y1 + fotonii ale caror perechi
   au fost absorbite de partile opace ale fantelor -> distributie neteda, fara franje.

Reproduce Fig. 3 (pieptene / cocoasa) si complementaritatea coerenta-intricare
(sectiunea V): franjele fantoma cer o sursa larga (intricare in impuls); o sursa
ingusta (coerenta) le distruge — niciun regim nu pune franjele in "singles".
"""
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          # mm
K = 2 * np.pi / LAM
D_F = 0.4             # mm separare fante
A_F = 0.08            # mm latime fanta
LD = 400.0            # mm fante -> D1
L2 = 600.0            # mm cristal -> D2
X2 = np.linspace(-4, 4, 401)
Y1G = np.linspace(-4, 4, 61)   # pozitii D1 pentru integrarea "singles"
FRAC_BLOCAT = 0.6     # fractiunea perechilor pierdute pe partile opace (calitativ)


def calculeaza(nr_fante, w_s, L1):
    xs = np.linspace(-3 * w_s, 3 * w_s, 401)
    G = np.exp(-xs ** 2 / (2 * w_s ** 2))
    G /= G.sum()
    centre = [0.0] if nr_fante == 1 else [-D_F / 2, D_F / 2]
    xf = np.concatenate([c + np.linspace(-A_F / 2, A_F / 2, 9) for c in centre])
    F2 = np.exp(1j * K * (xf[:, None] - xs[None, :]) ** 2 / (2 * L1))      # [xf, xs]
    F1 = np.exp(1j * K * (Y1G[:, None] - xf[None, :]) ** 2 / (2 * LD))     # [y1, xf]
    F3 = np.exp(1j * K * (X2[:, None] - xs[None, :]) ** 2 / (2 * L2))      # [x2, xs]
    C = F1 @ F2                                                            # [y1, xs]
    A = (C * G[None, :]) @ F3.T                                            # [y1, x2]
    p_coinc = np.abs(A) ** 2                       # p(x2 | D1 la y1), pe linii
    singles = p_coinc.sum(axis=0)
    singles /= singles.max() + 1e-30
    # perechile idler absorbite de partile opace: twinul lor ajunge oricum la D2
    # (distributie neteda ~ propagare libera a sursei) — pedestal fara franje
    sigma = max(w_s, LAM * L2 / (2 * np.pi * max(w_s, 1e-3))) + 0.5
    fundal = np.exp(-X2 ** 2 / (2 * sigma ** 2))
    fundal /= fundal.max()
    singles = (1 - FRAC_BLOCAT) * singles + FRAC_BLOCAT * fundal
    return p_coinc, singles / singles.max()


def perioada_ghost(L1):
    """Perioada franjelor fantoma la D2: in imaginea Klyshko desfasurata,
    fantele sunt la distanta L1 + L2 de planul lui D2."""
    return LAM * (L1 + L2) / D_F


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


class AppGhost:
    def __init__(self, root):
        self.root = root
        root.title("Exp. 3 — Ghost Interference (Shih 1995, DIRD Fig. 2-4, 6)")

        ctl = ttk.Frame(root, padding=8)
        ctl.pack(side=tk.LEFT, fill=tk.Y)
        ttk.Label(ctl, text="Interferenta 'fantoma'",
                  font=("Segoe UI", 12, "bold")).pack(pady=(0, 8))

        self.v_fante = tk.IntVar(value=2)
        ttk.Label(ctl, text="Sistem de fante (inaintea lui D1):").pack(anchor="w")
        ttk.Radiobutton(ctl, text="2 fante -> 'pieptene' (Fig. 3a)",
                        variable=self.v_fante, value=2,
                        command=self.actualizeaza).pack(anchor="w")
        ttk.Radiobutton(ctl, text="1 fanta -> 'cocoasa' (Fig. 3b)",
                        variable=self.v_fante, value=1,
                        command=self.actualizeaza).pack(anchor="w")

        ttk.Label(ctl, text="Pozitia detectorului D1, y1 [mm]:").pack(anchor="w", pady=(10, 0))
        self.v_y1 = tk.DoubleVar(value=0.0)
        ttk.Scale(ctl, from_=-3, to=3, variable=self.v_y1,
                  command=lambda e: self.actualizeaza()).pack(fill=tk.X)

        ttk.Label(ctl, text="Largimea pompei w_s [mm]\n(mare = intricare in impuls,\nmica = sursa coerenta):").pack(
            anchor="w", pady=(10, 0))
        self.v_ws = tk.DoubleVar(value=0.4)
        ttk.Scale(ctl, from_=0.02, to=0.8, variable=self.v_ws,
                  command=lambda e: self.actualizeaza()).pack(fill=tk.X)

        ttk.Label(ctl, text="Distanta sursa-fante L1 [mm]:").pack(
            anchor="w", pady=(10, 0))
        self.v_L1 = tk.DoubleVar(value=200.0)
        ttk.Scale(ctl, from_=100, to=800, variable=self.v_L1,
                  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: distributia lui D2 IN COINCIDENTA cu D1 — modificand fantele "
            "din bratul lui D1, modelul de pe D2 se schimba nelocal.\n\n"
            "Jos: distributia fara coincidente (singles) — neteda, fara franje. "
            "Fara legatura clasica de coincidenta nu se transmite nimic.\n\n"
            "Complementaritatea coerenta-intricare (sectiunea V din DIRD): "
            "micsorand w_s sursa devine coerenta dar isi pierde intricarea "
            "in impuls -> franjele fantoma dispar si din coincidente; "
            "marind w_s, franjele exista DOAR in coincidente. Nu exista "
            "regim in care 'singles' sa poarte semnalul.")).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(2500, root.destroy)

    def actualizeaza(self):
        p_coinc, singles = calculeaza(self.v_fante.get(), self.v_ws.get(), self.v_L1.get())
        iy = int(np.argmin(np.abs(Y1G - self.v_y1.get())))
        pc = p_coinc[iy]
        pc = pc / (pc.max() + 1e-30)

        self.ax1.clear()
        self.ax1.plot(X2, pc, color="#1f77b4")
        self.ax1.fill_between(X2, pc, alpha=0.3, color="#1f77b4")
        self.ax1.set_title(f"D2 in coincidenta cu D1 fixat la y1 = {Y1G[iy]:+.2f} mm")
        self.ax1.set_ylabel("rata normata")

        self.ax2.clear()
        self.ax2.plot(X2, singles, color="#555")
        self.ax2.fill_between(X2, singles, alpha=0.3, color="#888")
        self.ax2.set_title("D2 fara coincidente (singles) — nu apare niciun semnal")
        self.ax2.set_xlabel("pozitia x2 pe D2 [mm]")
        self.ax2.set_ylabel("rata normata")

        per = perioada_ghost(self.v_L1.get())
        self.lbl.config(text=(f"contrast franje (coincid.): {contrast(pc, per):.2f}\n"
                              f"contrast franje (singles):  {contrast(singles, per):.2f}"))
        self.canvas.draw_idle()


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