Expérience de Michelson-Morley

Code source

Web VPython 3.2

###############################################################################
#  PhysiqueUdeM / REL_referentiels
#  Michelson v1.0 
#  2026-03-05
#  https://physiqueudem.github.io/rel_referentiels/
#  DOI: 10.71781/34279
#  Licence CC-BY-4.0 
###############################################################################

# === Paramètres globaux ===

scene.width = 600
scene.height = 600
scene.align = 'left'

refEther = False
modifEther = 1

orientation = 0
lamb = 20e-6
c = 1
longV = 20
longH = 20
deltaL = 0

vEther = 0.0
particules = []

posSource = vector(-10, 0, 0)
posDetec = vector(0, -10, 0)

frequence = 0.25
longOnde = 1 / frequence
amplitude = 1

dt = 0.01
dx = 0.05

couleurs_fig = [color.black, color.gray(0.5), color.orange, color.green, color.magenta, color.cyan, color.red]
compteur_fig = 0

suiveurs = []
ondes = []

# === Fonctions ===

class onde:
    def __init__(self, debut, direction, couleur, amplitude, frequence=5, dephasage=0):
        self.amplitude = amplitude
        self.frequence = frequence
        self.dephasage = dephasage
        self.omega = 2 * pi * frequence
        self.dir = direction
        self.vertical = False
        self.pos = vector(debut)
        self.avance = 1
        self.sphere = sphere(pos=debut, radius=0.1, color=couleur, make_trail=True, trail_type="curve", interval=1, retain=1000)
        self.k = 2 * pi * frequence / c

    def updatekomega(self):
        frequence_app = self.frequence / (1 - vEther * cos(orientation * pi))
        self.omega = 2 * pi * frequence_app
        self.k = 2 * pi * frequence_app

    def get_amplitude(self, t):
        self.updatekomega()
        axis = vector(1, 0, 0) if not self.vertical else vector(0, 1, 0)
        coord = dot(self.pos, axis)
        return self.amplitude * sin(self.k * coord - self.avance * self.omega * t + self.dephasage)

    def update(self, t):
        amp = self.get_amplitude(t)
        crete = vector(0, amp, 0) if not self.vertical else vector(amp, 0, 0)
        self.sphere.pos = self.pos + crete
        self.pos += self.dir

def majVitesse(ev):
    global vEther
    try:
        vEther = float(eInput.text)
        if vEther < 0: vEther = 0
        if vEther > 1: vEther = 1
    except:
        vEther = 0
    eTexteSlider.text = f" {vEther:.1f} c"
    #ventEther.axis = modifEther * vEther * vector(cos(orientation * pi), sin(orientation * pi), 0)
    ventEther.axis = modifEther * vector(cos(orientation * pi), sin(orientation * pi), 0)
    majInterference()
    lancer_animation_onde()

def majOrientation(ev):
    global orientation
    try:
        orientation = float(oeInput.text)
        if orientation < 0: orientation = 0
        if orientation > 2: orientation = 2
    except:
        orientation = 0
    oeTexteSlider.text = f" {orientation:.2f} Pi"
    direction = vector(cos(orientation * pi), sin(orientation * pi), 0)
    #ventEther.axis = modifEther * vEther * direction
    ventEther.axis = modifEther * direction
    majInterference()
    lancer_animation_onde()

def majDeltaL(ev):
    global deltaL, longH
    try:
        deltaL = int(bhInput.text)
        if deltaL < -5: deltaL = -5
        if deltaL > 5: deltaL = 5
    except:
        deltaL = 0
    longH = longV + deltaL * (longOnde / 2)
    bhTexteSlider.text = f" {deltaL} λ/4 "
    miroirH.pos = vector(longH / 2, 0, 0)
    majInterference()
    lancer_animation_onde()

def ajoutParticules(nombre, taille):
    global particules
    particules.clear()
    for _ in range(nombre):
        pos = vector(random() * 30 - 15, random() * 30 - 15, random() * 2 - 1)
        p = sphere(pos=pos, radius=taille, color=color.white, opacity=0.3)
        particules.append(p)

def majParticules():
    if vEther > 0:
        vitesse = 2 * ventEther.axis 
        for p in particules:
            p.pos += vitesse * dt
            if p.pos.x > 15: p.pos.x = -15
            if p.pos.x < -15: p.pos.x = 15
            if p.pos.y > 15: p.pos.y = -15
            if p.pos.y < -15: p.pos.y = 15

def temps(L, orientation):
    terme1 = 2 * L / c
    terme2 = sqrt(1 - vEther ** 2 * sin(orientation * pi) ** 2)
    terme3 = 1 - vEther ** 2
    return terme1 * (terme2 / terme3)

def majInterference():
    global courbe
    courbe.delete()
    d0 = abs(longH - longV)
    alpha = 0.1
    t1 = temps(longV, orientation + 1 / 2)
    t2 = temps(longH, orientation)
    delta_phi = (2 * pi * (t1 - t2) * c) / lamb
    for i in range(0, 100):
        courbe.plot(i, cos((2 * pi / 5) * (i * alpha + d0) + delta_phi) ** 2)


def dupliquerCourbe():
    global compteur_fig
    couleur = couleurs_fig[compteur_fig % len(couleurs_fig)]
    compteur_fig += 1
    courbe_snapshot = gcurve(color=couleur)
    d0 = abs(longH - longV)
    alpha = 0.1
    t1 = temps(longV, orientation + 1 / 2)
    t2 = temps(longH, orientation)
    delta_phi = (2 * pi * (t1 - t2) * c) / lamb
    for i in range(0, 100):
        y = cos((2 * pi / 5) * (i * alpha + d0) + delta_phi) ** 2
        courbe_snapshot.plot(i, y)

def animer_onde(onde1, onde2, onde3):
    t = 0
    while not ((onde1.pos - posDetec).mag < 1 and (onde2.pos - posDetec).mag < 1):# and (onde3.pos - posDetec).mag < 1):
        if abs(dot(onde1.pos, vector(1, 0, 0)) - longH / 2) <= dx:
            onde1.dir = -onde1.dir
            onde1.avance = -onde1.avance
        if abs(dot(onde3.pos, vector(1, 0, 0)) - longH / 2) <= dx:
            onde3.dir = -onde3.dir
            onde3.avance = -onde3.avance
        if abs(dot(onde1.pos, vector(1, 0, 0))) <= dx and onde1.avance == -1:
            onde1.vertical = True
            onde1.dir = vector(0, -dx, 0)
        if abs(dot(onde2.pos, vector(1, 0, 0))) <= dx and not onde2.vertical:
            onde2.vertical = True
            onde2.dir = vector(0, dx, 0)
        if abs(dot(onde2.pos, vector(0, 1, 0)) - longV / 2) <= dx:
            onde2.dir = vector(0, -dx, 0)
            onde2.avance = -onde2.avance
        t += dt
        if (onde1.pos - posDetec).mag > 1:
            onde1.update(t)
        if (onde3.pos).mag > 1:
            onde3.update(t)

        if (onde2.pos - posDetec).mag > 1:
            onde2.update(t)
        

def lancer_animation_onde():
    for o in ondes:
        o.sphere.clear_trail()
        o.sphere.visible = False
    ondes.clear()
    onde1 = onde(posSource, vector(dx, 0, 0), color.blue, amplitude, frequence)
    onde2 = onde(posSource, vector(dx, 0, 0), color.red, amplitude, frequence)
    onde3 = onde(posSource, vector(dx, 0, 0), color.magenta, amplitude, frequence)
    ondes.append(onde1)
    ondes.append(onde2)
    ondes.append(onde3)
    animer_onde(onde1, onde2, onde3)

def boucle_particules():
    while True:
        rate(100)
        majParticules()

# === Interface ===

# === Interface (Sliders au-dessus de la scène) ===

wtext(text="<b>Vitesse par rapport à l'ether (entre 0 et 1)</b><br>", pos=scene.caption_anchor )
eInput = winput(bind=majVitesse, text="0.0", type="numeric", pos=scene.caption_anchor )
eTexteSlider = wtext(text=" c", pos=scene.caption_anchor )
wtext(text="<br><br>", pos=scene.pos=scene.caption_anchor )

wtext(text="<b>Orientation de l'ether (entre 0 et 2)</b><br>", pos=scene.caption_anchor )
oeInput = winput(bind=majOrientation, text="0.0", type="numeric", pos=scene.caption_anchor )
oeTexteSlider = wtext(text=" Pi", pos=scene.caption_anchor )
wtext(text="<br><br>", pos=scene.caption_anchor )

wtext(text="<b>Différence de longueur (entre -5 et 5)</b><br>", pos=scene.caption_anchor )
bhInput = winput(bind=majDeltaL, text="0", type="numeric", pos=scene.caption_anchor )
bhTexteSlider = wtext(text=" λ/4 ", pos=scene.caption_anchor )
wtext(text="<br><br>", pos=scene.caption_anchor )


# === Objets de la scène ===

ventEther = arrow(pos=vector(-longH / 2, longV / 2, 0),
                  axis=vector(1, 0, 0),
                  color=color.cyan,
                  shaftwidth=0.3)
source = sphere(pos=posSource, color=color.magenta)
miroirH = box(pos=vector(longH / 2, 0, 0), size=vector(0.1, 4, 4), axis=vector(1, 0, 0), opacity=0.8)
miroirV = box(pos=vector(0, longV / 2, 0), size=vector(0.1, 4, 4), axis=vector(0, 1, 0), opacity=0.8)
splitteur = box(size=vector(0.1, 4, 4), axis=vector(1, -1, 0), opacity=0.8)
detecteur = box(pos=posDetec, size=vector(3, 2, 2), axis=vector(0, 1, 0), color=color.white)

# === Franges d'interférence ===

graph1 = graph(title="Intensité normalisée selon la distance sur le détecteur",
               xtitle="Position sur l'écran (m)", ytitle="I/I_max",
               width=600, height=400)

graph1.align='right'

courbe = gcurve(color=color.blue)
button(bind=dupliquerCourbe, text="Garder en mémoire",pos=scene.caption_anchor)

# === Lancement initial ===

ajoutParticules(20, 0.5)
majInterference()
lancer_animation_onde()
boucle_particules()