Documentation

Simulateur d’ondes optiques

Documentation scientifique, choix de modélisation, limites du modèle et références associées au simulateur.

Retour au simulateur
Sommaire de la documentation

Documentation complète du simulateur d’ondes électromagnétiques

1. Objet du projet

Ce document présente de manière complète le simulateur d’ondes électromagnétiques développé autour de l’optique des ondes, de la polarisation, de la propagation dans les milieux, et de la modélisation spectrale de la lumière. Il constitue désormais la base de documentation scientifique et pédagogique de la version web du projet.

L’objectif du simulateur est double :

  • proposer un outil visuel et interactif pour explorer des phénomènes d’optique physique ;
  • fournir un cadre de modélisation suffisamment sérieux pour relier l’animation, les choix numériques et les formalismes théoriques utilisés.

Le projet se situe volontairement à l’interface entre pédagogie et modélisation. Il ne cherche pas seulement à montrer de belles ondes, mais à expliciter ce qui est représenté, ce qui est calculé, ce qui est approximé, et pourquoi certains choix ont été retenus.

2. Philosophie générale du simulateur

Le simulateur repose sur une idée simple : représenter visuellement une onde électromagnétique qui se propage le long d’un axe optique, tout en la faisant interagir avec une chaîne de dispositifs optiques et des milieux matériels.

Cependant, dès que l’on dépasse le cas monochromatique parfaitement cohérent, une difficulté fondamentale apparaît : une animation simple d’une courbe 3D ne peut pas représenter à elle seule toute la richesse physique d’une lumière spectrale, partiellement polarisée ou statistique.

Le projet repose donc sur une séparation claire entre plusieurs niveaux :

  1. niveau visuel : ce qui est animé dans la vue principale ;
  2. niveau calculatoire : ce que le moteur mathématique évalue réellement ;
  3. niveau interprétatif : ce que l’utilisateur doit comprendre de la représentation.

C’est cette séparation qui permet au simulateur de rester à la fois lisible et scientifiquement défendable.

3. Domaines physiques couverts

Le simulateur couvre principalement les thèmes suivants :

  • propagation d’ondes électromagnétiques planes ;
  • polarisation linéaire, circulaire et elliptique ;
  • action de polariseurs et de lames retardatrices ;
  • propagation dans des milieux d’indice constant ou dispersif ;
  • lumière monochromatique ;
  • lumière spectrale continue ou à raies ;
  • description cohérente de type Jones ;
  • description réaliste statistique de type Stokes / Mueller ;
  • matrice de cohérence ;
  • degré de polarisation ;
  • longueur d’onde effective et couleur perçue ;
  • longueur de cohérence estimée ;
  • étude qualitative de cannelures et d’effets spectraux à travers des dispositifs.

Ce périmètre est déjà très riche. Il permet d’aborder aussi bien des manipulations pédagogiques classiques que des questions plus avancées sur la nature physique de la lumière polarisée et partiellement cohérente.

4. Architecture conceptuelle

Le simulateur peut être vu comme la superposition de cinq modules conceptuels.

4.1. Le module onde

Il représente une onde incidente caractérisée, dans le cas monochromatique, par :

  • une amplitude sur l’axe x ;
  • une amplitude sur l’axe y ;
  • un déphasage relatif ;
  • une longueur d’onde ;
  • une vitesse d’animation.

Ce module correspond à la description la plus directe de l’onde plane vectorielle.

4.2. Le module chaîne optique

Il représente une succession ordonnée de dispositifs :

  • polariseurs ;
  • lames quart d’onde ;
  • lames demi-onde ;
  • lames retardatrices générales.

Chaque dispositif agit sur l’état de polarisation, soit par matrice de Jones, soit par matrice de Mueller selon le mode choisi.

4.3. Le module milieux optiques

Il permet d’insérer des zones matérielles le long de l’axe de propagation. Chaque milieu possède :

  • un intervalle spatial ;
  • un nom de matériau ;
  • un indice constant ou dispersif ;
  • éventuellement une loi d’indice dépendant de la longueur d’onde.

Le rôle des milieux est physique, ils modifient la phase spatiale, la longueur d’onde locale et le comportement de la propagation selon le modèle retenu.

4.4. Le module spectral

Il remplace la source monochromatique unique par une distribution d’intensité en longueur d’onde. Plusieurs types de sources sont proposés :

  • monochromatique ;
  • lumière blanche de type D65 ;
  • corps noir ;
  • LED ;
  • lampes à raies (mercure, sodium, hydrogène, néon, hélium) ;
  • spectre personnalisé.

Ce module permet de distinguer la couleur affichée, la structure spectrale réelle, et les effets dépendants de la longueur d’onde dans les dispositifs et les milieux.

4.5. Le module polarisation réaliste

Il introduit une distinction entre deux descriptions :

  • Jones cohérent : adapté à une onde monochromatique cohérente ou à une représentation effective d’une composante dominante ;
  • Stokes / Mueller réaliste : adapté aux sources spectrales partiellement polarisées, non polarisées ou statistiquement décrites.

Cette distinction est un point central du projet : elle permet de ne pas traiter toutes les lumières comme si elles étaient des ondes parfaitement cohérentes, ce qui serait faux physiquement.

5. Représentation de l’onde monochromatique

Dans le cas monochromatique, l’onde est modélisée comme une onde plane se propageant selon z. Les composantes transverses du champ électrique s’écrivent sous la forme générale

Ex(z,t)=Axcos(kzωt),Ey(z,t)=Aycos(kzωt+ϕ)E_x(z,t)=A_x\cos(kz-\omega t),\qquad E_y(z,t)=A_y\cos(kz-\omega t+\phi)

Cette écriture permet d’obtenir :

  • polarisation linéaire si les composantes sont en phase ou en opposition ;
  • polarisation circulaire si les amplitudes sont égales et le déphasage vaut ±90\pm 90^\circ ;
  • polarisation elliptique dans le cas général.

Dans la vue 3D, l’onde affichée correspond directement à cette structure. C’est le régime où l’animation a la signification la plus immédiate.

6. Formalisme de Jones

Le formalisme de Jones est utilisé pour représenter les états de polarisation cohérents. Il manipule directement les amplitudes complexes transverses du champ électrique. Dans ce cadre, l’état d’entrée s’écrit

J=(ExEy)\mathbf{J}=\begin{pmatrix}E_x\\E_y\end{pmatrix}

ExE_x et EyE_y contiennent à la fois l’amplitude et la phase des deux composantes transverses. Un dispositif optique cohérent est alors représenté par une matrice complexe 2×22\times2.

Pour une chaîne de dispositifs A1,A2,,AnA_1,A_2,\ldots,A_n, l’état de sortie vaut

Jout=AnA2A1Jin.\mathbf{J}_{\mathrm{out}}=A_n\cdots A_2A_1\mathbf{J}_{\mathrm{in}}.

Cette convention correspond à l’ordre physique de traversée : le premier dispositif rencontré agit le premier sur le vecteur d’entrée. Elle montre immédiatement que l’ordre des composants est important. Deux lames ou deux polariseurs ne sont pas nécessairement interchangeables.

Dans le simulateur, Jones sert à calculer :

  • l’état cohérent incident ;
  • les états intermédiaires après chaque dispositif ;
  • la matrice de Jones totale ;
  • les amplitudes affichées dans la vue principale ;
  • les grandeurs montrées dans les détails mathématiques ;
  • les expériences cohérentes avec polariseurs, analyseurs et lames retardatrices.

6.1. États de Jones usuels

Quelques états d’entrée représentatifs sont utilisés directement dans l’interface sous forme de préréglages.

Une polarisation linéaire horizontale peut être écrite

Jx=(10),\mathbf{J}_x=\begin{pmatrix}1\\0\end{pmatrix},

et une polarisation linéaire verticale

Jy=(01).\mathbf{J}_y=\begin{pmatrix}0\\1\end{pmatrix}.

Une polarisation linéaire à 4545^\circ s’écrit, à une normalisation près,

J45=12(11).\mathbf{J}_{45}=\frac{1}{\sqrt2} \begin{pmatrix}1\\1\end{pmatrix}.

Une polarisation circulaire est obtenue lorsque les deux amplitudes sont égales et déphasées de ±π/2\pm \pi/2 :

Jcirc,±=12(1e±iπ/2).\mathbf{J}_{\mathrm{circ},\pm}=\frac{1}{\sqrt2} \begin{pmatrix}1\\e^{\pm i\pi/2}\end{pmatrix}.

Une polarisation elliptique correspond au cas général où les amplitudes et le déphasage relatif ne donnent ni une droite ni un cercle.

6.2. Matrices de Jones usuelles

Les matrices de Jones ci-dessous constituent le cœur des dispositifs cohérents du simulateur.

Le polariseur linéaire horizontal idéal est

Px=(1000).P_x=\begin{pmatrix}1&0\\0&0\end{pmatrix}.

Il transmet la composante xx et supprime la composante yy. Un polariseur vertical idéal est

Py=(0001).P_y=\begin{pmatrix}0&0\\0&1\end{pmatrix}.

Pour orienter un dispositif d’un angle θ\theta, on utilise une matrice de rotation Jones

R(θ)=(cosθsinθsinθcosθ).R(\theta)= \begin{pmatrix} \cos\theta&-\sin\theta\\ \sin\theta&\cos\theta \end{pmatrix}.

Dans la convention du simulateur, une matrice alignée avec les axes propres est tournée par

A(θ)=R(θ)A0R(θ).A(\theta)=R(-\theta)A_0R(\theta).

Une lame retardatrice idéale d’axes propres xx et yy introduisant un retard δ\delta est modélisée par

L(δ)=(100eiδ).L(\delta)= \begin{pmatrix} 1&0\\ 0&e^{i\delta} \end{pmatrix}.

La lame quart d’onde correspond à δ=π/2\delta=\pi/2 et la lame demi-onde à δ=π\delta=\pi. La lame retardatrice générale peut soit recevoir directement un retard choisi par l’utilisateur, soit le calculer en mode physique par

δ(λ)=2πΔneλ,\delta(\lambda)=2\pi\frac{\Delta n\,e}{\lambda},

ou, en degrés,

δdeg(λ)=360Δneλ.\delta_{\deg}(\lambda)=360^\circ\frac{\Delta n\,e}{\lambda}.

Ici ee est l’épaisseur, Δn\Delta n la biréfringence et λ\lambda la longueur d’onde. Cette dépendance est importante : en mode physique, une même lame n’a pas exactement le même retard à toutes les longueurs d’onde.

6.3. Exemple physique simple : polariseur puis analyseur

Supposons qu’une onde linéairement polarisée traverse un second polariseur tourné d’un angle θ\theta relativement au premier. Le formalisme de Jones donne une intensité transmise proportionnelle à

Iout=Iincos2θ.I_{\mathrm{out}}=I_{\mathrm{in}}\cos^2\theta.

C’est la loi de Malus. Dans le simulateur, cette relation se voit directement lorsque l’on fait varier l’angle de l’analyseur : l’intensité diminue continûment jusqu’à l’extinction idéale pour deux polariseurs croisés.

6.4. Exemple physique simple : lame quart d’onde

Une lame quart d’onde placée avec ses axes à 4545^\circ d’une polarisation linéaire peut transformer cette polarisation en polarisation circulaire. Le point essentiel est que la lame ne sélectionne pas une composante comme le ferait un polariseur : elle modifie la phase relative entre les deux composantes.

Le simulateur rend cette différence visible : un polariseur agit sur l’intensité et la direction de polarisation, tandis qu’une lame retardatrice agit d’abord sur la forme de l’ellipse de polarisation.

6.5. Comment interpréter l’impact d’une matrice

Une matrice de Jones peut :

  • sélectionner une direction de polarisation ;
  • éteindre partiellement ou totalement certaines composantes ;
  • introduire un déphasage relatif ;
  • transformer une polarisation linéaire en circulaire ou elliptique ;
  • composer des transformations dont l’ordre est physiquement significatif.

Limite essentielle

Le formalisme de Jones ne décrit pas correctement une lumière non polarisée ou partiellement polarisée au sens statistique. Un vecteur de Jones représente un état pur cohérent ; il ne contient pas l’information nécessaire pour décrire un mélange d’états ou une perte de degré de polarisation.

Dans le simulateur, cela justifie la séparation entre :

  • Jones cohérent, pour les ondes cohérentes et les visualisations effectives ;
  • Stokes / Mueller réaliste, pour les sources spectrales, non polarisées ou partiellement polarisées.

7. Formalisme de Stokes / Mueller

Pour traiter les sources réalistes, le simulateur utilise le formalisme de Stokes / Mueller. Il ne manipule plus directement une unique amplitude complexe, mais des grandeurs réelles liées aux intensités et aux corrélations moyennes.

Le vecteur de Stokes s’écrit

S=(S0S1S2S3).\mathbf{S}=\begin{pmatrix}S_0\\S_1\\S_2\\S_3\end{pmatrix}.

Les composantes ont l’interprétation suivante :

  • S0S_0 : intensité totale ;
  • S1S_1 : préférence horizontale / verticale ;
  • S2S_2 : préférence à 4545^\circ / 45-45^\circ ;
  • S3S_3 : composante circulaire.

Le degré de polarisation vaut

DOP=S12+S22+S32S0,\mathrm{DOP}=\frac{\sqrt{S_1^2+S_2^2+S_3^2}}{S_0},

avec une valeur comprise entre 0 et 1 pour un état physique.

Les dispositifs agissent par matrices de Mueller :

Sout=MSin.\mathbf{S}_{\mathrm{out}}=M\mathbf{S}_{\mathrm{in}}.

7.1. Pourquoi Mueller est nécessaire

Une lumière blanche réelle, une lumière non polarisée ou une lumière partiellement polarisée ne peut pas être résumée proprement par un unique vecteur de Jones. Il faut décrire des intensités moyennes, des corrélations et éventuellement un mélange entre partie polarisée et partie non polarisée.

Le formalisme de Mueller répond à ce besoin : il permet de représenter des transformations qui n’ont pas d’équivalent Jones simple, notamment la dépolarisation.

7.2. Matrices de Mueller usuelles

Le polariseur linéaire horizontal idéal est représenté par

Mpol,x=12(1100110000000000).M_{\mathrm{pol},x}=\frac12 \begin{pmatrix} 1&1&0&0\\ 1&1&0&0\\ 0&0&0&0\\ 0&0&0&0 \end{pmatrix}.

Un polariseur tourné d’un angle θ\theta est obtenu avec une rotation Mueller. Comme les paramètres S1S_1 et S2S_2 décrivent des directions de polarisation, la rotation fait intervenir 2θ2\theta :

RM(θ)=(10000cos2θsin2θ00sin2θcos2θ00001).R_M(\theta)= \begin{pmatrix} 1&0&0&0\\ 0&\cos 2\theta&\sin 2\theta&0\\ 0&-\sin 2\theta&\cos 2\theta&0\\ 0&0&0&1 \end{pmatrix}.

Dans la convention du simulateur, un dispositif Mueller orienté est écrit

M(θ)=RM(θ)M0RM(θ).M(\theta)=R_M(-\theta)M_0R_M(\theta).

Une lame retardatrice idéale alignée avec les axes propres peut s’écrire

Mret(δ)=(1000010000cosδsinδ00sinδcosδ).M_{\mathrm{ret}}(\delta)= \begin{pmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&\cos\delta&\sin\delta\\ 0&0&-\sin\delta&\cos\delta \end{pmatrix}.

Elle conserve S0S_0 et fait tourner les composantes de polarisation dans l’espace de Stokes.

Un dépolariseur isotrope simple est

Mdepol(α)=(10000α0000α0000α).M_{\mathrm{depol}}(\alpha)= \begin{pmatrix} 1&0&0&0\\ 0&\alpha&0&0\\ 0&0&\alpha&0\\ 0&0&0&\alpha \end{pmatrix}.

Il conserve l’intensité totale mais réduit la partie polarisée.

Un diatténuateur linéaire d’axes propres \parallel et \perp est modélisé par

Mdia=12(T+TTT00TTT+T00002TT00002TT).M_{\mathrm{dia}}=\frac12 \begin{pmatrix} T_\parallel+T_\perp&T_\parallel-T_\perp&0&0\\ T_\parallel-T_\perp&T_\parallel+T_\perp&0&0\\ 0&0&2\sqrt{T_\parallel T_\perp}&0\\ 0&0&0&2\sqrt{T_\parallel T_\perp} \end{pmatrix}.

Cette matrice inclut le polariseur idéal comme cas limite T=0T_\perp=0.

7.3. États sources réalistes

Dans le mode réaliste, la source peut être :

  • non polarisée ;
  • linéairement polarisée ;
  • circulairement polarisée ;
  • partiellement polarisée, via un degré imposé.

Une lumière non polarisée est représentée par

Sunpol=(I000).\mathbf{S}_{\mathrm{unpol}}= \begin{pmatrix} I\\ 0\\ 0\\ 0 \end{pmatrix}.

Une lumière linéairement polarisée selon un angle θ\theta est représentée par

Slin=(IIcos2θIsin2θ0).\mathbf{S}_{\mathrm{lin}}= \begin{pmatrix} I\\ I\cos 2\theta\\ I\sin 2\theta\\ 0 \end{pmatrix}.

Une polarisation circulaire idéale est représentée par

Scirc=(I00±I).\mathbf{S}_{\mathrm{circ}}= \begin{pmatrix} I\\ 0\\ 0\\ \pm I \end{pmatrix}.

Pour une source partiellement polarisée, le simulateur mélange une partie polarisée et une partie non polarisée afin d’obtenir le degré demandé. Ce choix est important : on ne réduit pas seulement l’intensité, on change la structure statistique de l’état.

7.4. Exemple physique simple : lumière non polarisée sur un polariseur

Si une lumière non polarisée traverse un polariseur idéal, l’intensité transmise est divisée par deux et la lumière de sortie devient polarisée. C’est un cas typiquement Mueller : le dispositif transforme une distribution statistique en état ordonné.

7.5. Exemple physique simple : transformation par une lame

Une lame retardatrice idéale ne réduit pas l’intensité d’une lumière totalement polarisée. Elle modifie la répartition entre S1S_1, S2S_2 et S3S_3. Elle peut donc transformer une polarisation linéaire en polarisation circulaire ou inversement, sans agir comme un filtre d’intensité.

7.6. Comment lire physiquement une matrice de Mueller

Une matrice de Mueller permet de savoir comment un composant agit sur :

  • l’intensité totale ;
  • la préférence linéaire horizontale / verticale ;
  • la préférence linéaire diagonale ;
  • la composante circulaire ;
  • le degré de polarisation.

Le formalisme est particulièrement adapté à une lecture expérimentale, car les paramètres de Stokes sont des grandeurs réelles reliées à des mesures d’intensité.

7.7. Lien avec le simulateur

Dans le simulateur, le choix Jones / Mueller n’est pas un simple choix d’affichage. Il change la signification physique de ce qui est représenté :

  • Jones représente une onde cohérente ;
  • Mueller représente une lumière statistique, potentiellement spectrale et partiellement polarisée.

C’est pourquoi certains dispositifs n’existent que dans le mode Mueller : ils décrivent des phénomènes qui ne peuvent pas être appliqués proprement à un seul vecteur de Jones.

8. Matrice de cohérence

Pour donner une base plus profonde à la transition entre Jones et Stokes, le projet introduit aussi la matrice de cohérence.

Elle relie :

  • la description cohérente par vecteur de Jones ;
  • la description statistique par Stokes ;
  • la notion de degré de polarisation.

Elle constitue une interface conceptuelle importante :

  • un état de Jones pur correspond à une matrice de cohérence de rang 1 ;
  • un état non polarisé correspond à une matrice isotrope ;
  • un état partiellement polarisé est intermédiaire.

Cette couche enrichit la cohérence théorique du simulateur, même si toute sa richesse n’est pas toujours visible directement dans la vue principale.

8.1. Définition mathématique

Si l’on note ExE_x et EyE_y les deux composantes complexes transverses du champ, la matrice de cohérence s’écrit

C=(ExExExEy EyExEyEy)\mathbf{C}= \begin{pmatrix} \langle E_x E_x^* \rangle & \langle E_x E_y^* \rangle \\\ \langle E_y E_x^* \rangle & \langle E_y E_y^* \rangle \end{pmatrix}

Les crochets désignent ici une moyenne temporelle, statistique ou d’ensemble selon le contexte physique choisi. Cette matrice contient donc à la fois :

  • l’intensité de chaque composante ;
  • leur corrélation mutuelle ;
  • les informations nécessaires pour reconstruire le vecteur de Stokes.

8.2. Propriétés importantes

La matrice de cohérence possède plusieurs propriétés structurantes :

  • elle est hermitienne ;
  • elle est positive ;
  • sa trace est reliée à l’intensité totale ;
  • son déterminant renseigne sur le caractère plus ou moins pur de l’état de polarisation.

Dans le cas d’un état parfaitement cohérent décrit par un vecteur de Jones J\mathbf{J}, on a simplement

C=JJ\mathbf{C} = \mathbf{J}\mathbf{J}^\dagger

On retrouve alors un objet de rang 1. À l’inverse, un état complètement non polarisé est représenté par une matrice proportionnelle à l’identité.

8.3. Lien avec les paramètres de Stokes

La matrice de cohérence permet de reconstruire les paramètres de Stokes. Elle constitue en ce sens un pont entre la description microscopique complexe et la description expérimentale réelle fondée sur des intensités mesurées.

On peut voir les choses ainsi :

  • le vecteur de Jones est le plus fin mais ne décrit que les états cohérents ;
  • la matrice de cohérence généralise Jones aux états partiellement polarisés ;
  • le vecteur de Stokes est la forme réelle et mesurable extraite de cette information.

8.4. Intérêt dans le simulateur

On peut tout de même trouver un intérêt concret à cette matrice . Elle permet de justifier rigoureusement la transition entre :

  • une onde monochromatique cohérente représentée par une courbe 3D unique ;
  • une lumière réaliste décrite statistiquement, pour laquelle il faut distinguer partie polarisée et partie non polarisée, donc écrire deux modes physiques différents dans une seule onde.

Autrement dit, la matrice de cohérence sert de charnière mathématique entre le monde des amplitudes complexes et le monde des observables statistiques.

9. Sources lumineuses

Le simulateur propose plusieurs familles de sources. Toutes sont ramenées à un spectre discret sur le visible, typiquement entre 380 nm et 780 nm, afin de pouvoir calculer la couleur, la transmission, la longueur d’onde effective et les effets dépendant de λ\lambda.

La grandeur stockée est une intensité spectrale relative. Les spectres sont normalisés pour l’affichage et pour la comparaison entre sources. Cette normalisation ne doit pas être interprétée comme une puissance absolue radiométrique.

9.1. Source monochromatique

La source monochromatique correspond au cas idéal d’une seule longueur d’onde. Dans le moteur spectral, elle est représentée par une gaussienne très étroite centrée sur λ0\lambda_0 :

I(λ)=exp[(λλ0)22σ2],I(\lambda)=\exp\left[-\frac{(\lambda-\lambda_0)^2}{2\sigma^2}\right],

avec une largeur numérique faible. Cette approximation permet de rester compatible avec les routines spectrales tout en gardant le sens physique d’une source quasi monochromatique.

Dans le mode strictement monochromatique de l’interface, la longueur d’onde affichée est la longueur d’onde réelle choisie par l’utilisateur. C’est le cas le plus direct pour étudier les lames, les polariseurs et la propagation dans les milieux.

9.2. Lumière blanche D65

La lumière blanche de référence est l’illuminant D65. Le simulateur utilise une table spectrale interpolée sur la grille de calcul. Le choix de D65 évite d’inventer un blanc artificiel : il s’agit d’un illuminant standard de colorimétrie, représentatif d’une lumière de jour moyenne.

Ce choix a plusieurs conséquences :

  • le blanc possède une structure spectrale réelle, non plate ;
  • les filtres colorés agissent sur une distribution plausible ;
  • deux sources perçues comme blanches peuvent rester différentes spectralement ;
  • la fenêtre spectrale permet de distinguer couleur perçue et contenu spectral.

9.3. Lampe tungstène et corps noir

La lampe tungstène 2850 K est modélisée comme une source thermique proche d’un corps noir chaud. Les sources “corps noir” utilisent la loi de Planck :

Bλ(T)=2hc2λ51exp(hcλkBT)1.B_\lambda(T)=\frac{2hc^2}{\lambda^5} \frac{1}{\exp\left(\frac{hc}{\lambda k_B T}\right)-1}.

Dans le simulateur, le spectre est ensuite normalisé. L’objectif n’est donc pas de prédire une puissance absolue, mais de conserver la forme relative du spectre et le déplacement du maximum avec la température.

Les sources disponibles incluent notamment :

  • lampe tungstène 2850 K ;
  • corps noir 3000 K ;
  • corps noir 6500 K ;
  • corps noir personnalisé, avec température choisie par l’utilisateur.

Ce groupe de sources permet de comparer un blanc chaud, riche en rouge, à un blanc plus froid, plus riche en bleu.

9.4. LEDs blanches

Les LEDs blanches ne sont pas modélisées comme de simples corps noirs. Le simulateur utilise des profils LED tabulés de type CIE, interpolés sur la grille visible. Les profils retenus sont :

SourceProfil utiliséInterprétation
LED blanche chaude 3000 KLED-B2blanc chaud, plus riche en grandes longueurs d’onde
LED blanche neutre 4000 KLED-B3blanc intermédiaire
LED blanche froide 6500 KLED-B5blanc froid, plus riche en bleu

Une LED blanche peut donc produire une perception globale de blanc sans avoir le spectre continu lisse d’un corps noir. Le simulateur peut donc montrer que la couleur perçue ne suffit pas à connaître la composition spectrale.

9.5. Lampes à raies

Les lampes à raies sont modélisées comme des sommes de gaussiennes étroites :

I(λ)=iaiexp[(λλi)22σi2].I(\lambda)=\sum_i a_i\exp\left[-\frac{(\lambda-\lambda_i)^2}{2\sigma_i^2}\right].

Les largeurs utilisées ne sont pas des largeurs instrumentales exactes. Elles servent à rendre les raies visibles sur la grille numérique et dans les graphes, tout en conservant leur caractère spectralement discret.

Mercure

Raie (nm)Amplitude relativeRôle visuel
404.70.28violet
435.81.00bleu marqué
491.60.18bleu-vert faible
546.11.08vert dominant
577.00.34jaune
579.10.30jaune

Sodium

Raie (nm)Amplitude relativeCommentaire
588.9951.00doublet jaune principal
589.5920.95doublet jaune principal
568.80.10raie secondaire
615.40.08raie secondaire rouge-orangé

Hydrogène

Raie (nm)Amplitude relativeSérie visible
656.31.00H-alpha
486.10.55H-beta
434.00.35H-gamma
410.20.22H-delta

Néon

Raie (nm)Amplitude relative
540.10.12
585.20.70
588.20.45
594.50.55
603.00.65
607.40.55
614.30.85
621.70.75
626.60.95
633.40.90
638.31.00
640.20.80
650.70.65
659.90.50
692.90.35
703.20.32

Hélium

Raie (nm)Amplitude relative
388.90.20
447.10.45
471.30.18
492.20.22
501.60.35
587.61.00
667.80.50
706.50.35

9.6. Spectre personnalisé

Le spectre personnalisé permet d’ajouter des raies choisies par l’utilisateur. Chaque raie est définie par :

  • une longueur d’onde centrale ;
  • une intensité relative ;
  • une largeur spectrale.

Ce mode sert à tester des cas limites, à construire une source simplifiée ou à isoler l’effet d’une composante particulière sur la couleur et la propagation.

9.7. Échantillonnage et normalisation

Toutes les sources sont évaluées sur une grille discrète. À chaque longueur d’onde :

  • le spectre source donne une intensité relative ;
  • les filtres modifient cette intensité ;
  • les dispositifs Mueller peuvent agir sur le vecteur de Stokes local ;
  • les milieux dispersifs modifient l’indice et la phase ;
  • les contributions sont agrégées pour produire les grandeurs globales.

La normalisation par le maximum rend les spectres comparables visuellement, mais elle n’exprime pas une calibration photométrique absolue.

9.8. Limites des sources implémentées

Les sources à raies ne prétendent pas reproduire un spectre expérimental complet. Les raies choisies sont représentatives et pédagogiquement utiles, mais elles ne contiennent pas toutes les raies faibles possibles, ni les effets de pression, de température ou de résolution instrumentale.

Les LED sont basées sur des profils tabulés, mais le simulateur ne modélise pas chaque technologie de phosphore ni les variations entre fabricants. Les corps noirs sont calculés par une loi idéale, sans tenir compte d’émissivités réelles.

Ces limites sont assumées : le but est de disposer de sources suffisamment crédibles pour étudier les effets optiques principaux, sans transformer le simulateur en base de données spectroscopique exhaustive.

10. Couleur affichée et longueur d’onde effective

Un point délicat du simulateur est la différence entre :

  • la réalité spectrale de la source ;
  • la couleur affichée à l’écran ;
  • la longueur d’onde utilisée pour l’animation.

Dans une source spectrale, il n’existe pas en général une unique longueur d’onde physique qui résume toute la source. Pourtant, pour animer une onde dans la vue 3D, il faut une échelle spatiale visible. Le simulateur utilise donc une longueur d’onde effective d’affichage, choisie à partir du spectre de sortie.

Cela signifie :

  • en mode monochromatique, la longueur d’onde affichée est la vraie longueur d’onde de la source ;
  • en mode spectral Jones, la longueur d’onde de l’animation est une longueur d’onde effective pédagogique ;
  • en mode Mueller réaliste, l’animation est une somme de composantes spectrales et non une unique onde physique.

11. Chaîne optique et dispositifs

Les dispositifs proposés modifient l’état de polarisation de manière contrôlée.

11.1. Polariseur

Il projette l’état de polarisation sur une direction donnée. Physiquement, cela réduit l’intensité et sélectionne une composante privilégiée.

11.2. Lame quart d’onde

Elle introduit un retard de phase de π/2\pi/2 entre deux axes propres. Elle permet notamment de convertir une polarisation linéaire en polarisation circulaire dans des conditions appropriées.

11.3. Lame demi-onde

Elle introduit un retard de phase de π\pi, ce qui revient à tourner l’état de polarisation dans l’espace des états.

11.4. Lame retardatrice générale

Elle peut être utilisée en mode manuel ou physique, via épaisseur et biréfringence. C’est un bon compromis entre contrôle direct et réalisme expérimental.

12. Filtres colorés et filtrage spectral

Les filtres colorés constituent une famille importante de dispositifs. Leur rôle est différent de celui d’un polariseur ou d’une lame : ils n’agissent pas d’abord sur l’état de polarisation, mais sur la distribution spectrale de la lumière.

Un filtre rouge, vert ou bleu ne transforme pas directement une polarisation linéaire en polarisation circulaire. Dans le modèle retenu, il sélectionne certaines longueurs d’onde et atténue les autres.

Cette séparation est volontaire :

  • les dispositifs de polarisation modifient l’état Jones, Stokes ou Mueller ;
  • les filtres spectraux modifient l’intensité disponible en fonction de la longueur d’onde.

12.1. Grandeur simulée par un filtre

Un filtre coloré est représenté par une transmission spectrale scalaire

T(λ)[0,1].T(\lambda)\in[0,1].

Si le spectre incident vaut Imin(λ)I_{ m in}(\lambda), le spectre transmis est

Imout(λ)=T(λ)Imin(λ).I_{ m out}(\lambda)=T(\lambda)I_{ m in}(\lambda).

Dans un calcul Stokes / Mueller, l’action scalaire correspond à

Smout(λ)=T(λ)Smin(λ).\mathbf{S}_{ m out}(\lambda)=T(\lambda)\mathbf{S}_{ m in}(\lambda).

12.2. Modèle spectral gaussien

Pour les sources spectrales étendues, les filtres sont décrits par une transmission gaussienne :

T(λ)=Tmaxexp[(λλc)22σ2].T(\lambda)=T_{\max}\exp\left[-\frac{(\lambda-\lambda_c)^2}{2\sigma^2}\right].

Les paramètres sont :

  • λc\lambda_c : longueur d’onde centrale ;
  • σ\sigma : largeur spectrale du filtre ;
  • TmaxT_{\max} : transmission maximale.

Ce choix donne une bande de transmission lisse, facile à comprendre et stable numériquement. Il ne prétend pas représenter la fiche technique exacte d’un filtre réel.

12.3. Cas particulier du mode monochromatique

En mode strictement monochromatique, l’utilisateur raisonne souvent comme avec une onde unique. Pour éviter une situation peu intuitive où une onde très éloignée du centre d’un filtre resterait faiblement visible à cause de la queue gaussienne, le simulateur utilise un comportement effectif plus net :

Tmmono(λ)={Tmax,λλcΔλ,0,λλc>Δλ.T_{ m mono}(\lambda)= \begin{cases} T_{\max},& |\lambda-\lambda_c|\leq \Delta\lambda,\\ 0,& |\lambda-\lambda_c|>\Delta\lambda. \end{cases}

Ici Δλ\Delta\lambda est interprété comme une demi-largeur passante effective. Ainsi, une onde monochromatique à 550 nm traversant un filtre rouge centré à 640 nm avec une demi-largeur de 45 nm est éteinte, car 550 nm est hors de la bande [595,685][595,685] nm.

Ce comportement est réservé au mode monochromatique. Les sources spectrales étendues conservent le modèle gaussien, qui est plus adapté à une distribution continue.

12.4. Filtres prédéfinis et filtre personnalisé

Les filtres prédéfinis utilisent des centres typiques :

FiltreCentre λc\lambda_cRôle
Rouge640 nmtransmission préférentielle du rouge
Vert530 nmtransmission préférentielle du vert
Bleu460 nmtransmission préférentielle du bleu

Le filtre personnalisé permet au contraire de régler :

  • le centre spectral ;
  • la largeur ;
  • la transmission maximale.

12.5. Effet sur la couleur affichée

La couleur après un filtre est reconstruite à partir du spectre transmis. Le simulateur ne se contente pas d’attribuer une couleur fixe au nom du filtre.

Cela permet de rendre visibles plusieurs cas importants :

  • une lumière blanche filtrée en rouge devient rouge ;
  • une lampe à raies peut être presque éteinte si aucune raie ne tombe dans la bande du filtre ;
  • une source déjà rouge reste peu affectée par un filtre rouge ;
  • deux sources de même couleur perçue peuvent réagir différemment à un filtre.

12.6. Effet sur l’animation en mode Jones spectral

En mode Jones appliqué à une source spectrale, le spectre complet existe dans le calcul, mais l’animation principale reste une représentation effective. Le filtre modifie :

  • l’intensité transmise ;
  • la couleur globale ;
  • la longueur d’onde effective ;
  • les informations affichées dans la fenêtre spectrale.

Il faut donc interpréter l’onde affichée comme une synthèse pédagogique, et non comme toutes les composantes du spectre affichées simultanément.

12.7. Effet en mode Stokes / Mueller réaliste

En mode réaliste, les filtres agissent directement sur les composantes spectrales affichées. Après un filtre rouge, les composantes bleues ou vertes fortement atténuées ne doivent pas continuer à apparaître comme si elles traversaient le dispositif.

Le rendu conserve cependant une continuité géométrique suffisante pour éviter des coupures visuelles artificielles. Les composantes sont découpées par région lorsque leur intensité ou leur degré de polarisation change, mais les raccords sont construits de manière à éviter les trous ou les surbrillances aux interfaces.

12.8. Ce que le modèle de filtre ne décrit pas

Le modèle est volontairement scalaire et simplifié. Il ne prend pas en compte :

  • les courbes de transmission expérimentales de filtres commerciaux ;
  • les fuites hors bande réelles ;
  • les flancs asymétriques ;
  • la dépendance à l’angle d’incidence ;
  • les réflexions multiples dans un substrat ;
  • la dépendance éventuelle à la polarisation ;
  • l’absorption thermique ;
  • la fluorescence ou la conversion de longueur d’onde ;
  • la réponse spectrale d’un capteur expérimental.

Un vrai filtre peut avoir une transmission différente pour deux polarisations, un bord de bande non gaussien, ou des oscillations fines dues à des interférences de couches minces. Ces effets ne sont pas modélisés ici.

12.9. Justification du choix

Le filtre gaussien offre un bon compromis pour un simulateur interactif :

  • il dépend de peu de paramètres ;
  • il reste lisible pour l’utilisateur ;
  • il évite d’intégrer une base lourde de données expérimentales ;
  • il permet de tester facilement la sélectivité spectrale ;
  • il se combine naturellement avec les sources continues et les lampes à raies.

Le modèle pourra être enrichi plus tard avec des courbes expérimentales tabulées. Pour la version actuelle, son rôle est de rendre les effets spectraux compréhensibles et contrôlables.

13. Dispositifs Mueller avancés

L’ajout des dispositifs Mueller avancés marque une étape importante dans le simulateur. Jusque-là, les dispositifs principaux étaient essentiellement des polariseurs idéaux, des lames retardatrices idéales ou des filtres spectraux scalaires. Ces éléments suffisent pour beaucoup de situations pédagogiques, mais ils ne montrent pas toute la puissance du formalisme de Mueller.

Le formalisme de Jones décrit un état cohérent pur. Une matrice de Jones appliquée à un vecteur de Jones transforme un état pur en un autre état pur, sauf extinction totale. Elle ne peut donc pas représenter correctement une vraie dépolarisation statistique d’un faisceau avec un seul champ déterministe.

Les dispositifs Mueller avancés répondent précisément à cette limite. Ils permettent de modéliser des phénomènes comme :

  • une perte partielle du degré de polarisation ;
  • une transmission différente selon deux axes sans extinction parfaite ;
  • un retardateur qui n’est pas parfaitement idéal ;
  • une dépolarisation dépendante de la longueur d’onde.

13.1. Dépolariseur isotrope

Le dépolariseur isotrope est le dispositif Mueller le plus fondamental ajouté au simulateur. Il conserve l’intensité totale mais réduit les composantes polarisées du vecteur de Stokes.

Son modèle est :

(S0,S1,S2,S3)(S0,αS1,αS2,αS3)(S_0,S_1,S_2,S_3)\longmapsto(S_0,\alpha S_1,\alpha S_2,\alpha S_3)

La matrice associée est :

Mdepol=(1000 0α00 00α0 000α)M_{\rm depol}= \begin{pmatrix} 1&0&0&0\\\ 0&\alpha&0&0\\\ 0&0&\alpha&0\\\ 0&0&0&\alpha \end{pmatrix}

Le paramètre α\alpha représente la polarisation résiduelle :

  • α=1\alpha=1 : aucun effet dépolarisant ;
  • α=0\alpha=0 : dépolarisation totale ;
  • 0<α<10<\alpha<1 : dépolarisation partielle.

Si l’intensité S0S_0 est conservée, le degré de polarisation vérifie idéalement :

DOPout=αDOPin\mathrm{DOP}_{\rm out}=\alpha\,\mathrm{DOP}_{\rm in}

13.2. Polariseur imparfait

Un polariseur idéal transmet un axe et bloque complètement l’axe orthogonal. Dans la réalité, aucun polariseur n’est parfaitement idéal : il existe toujours une fuite, même faible, sur l’axe supposé éteint.

Le polariseur imparfait est modélisé comme un diatténuateur très contrasté. Dans ses axes propres, on peut noter :

T=1,T=αT_\parallel=1,\qquad T_\perp=\alpha

Le paramètre α\alpha représente alors la fuite de l’axe bloqué :

  • α=0\alpha=0 : polariseur idéal ;
  • α1\alpha\ll1 : polariseur très bon mais non parfait ;
  • α1\alpha\to1 : l’élément ne sélectionne presque plus la polarisation.

La matrice de Mueller d’un diatténuateur linéaire aligné avec les axes peut s’écrire sous la forme :

Mdia=12(T+TTT00 TTT+T00 002TT0 0002TT)M_{\rm dia}=\frac12 \begin{pmatrix} T_\parallel+T_\perp&T_\parallel-T_\perp&0&0\\\ T_\parallel-T_\perp&T_\parallel+T_\perp&0&0\\\ 0&0&2\sqrt{T_\parallel T_\perp}&0\\\ 0&0&0&2\sqrt{T_\parallel T_\perp} \end{pmatrix}

Un angle de rotation permet ensuite d’orienter l’élément dans le banc.

13.3. Diatténuateur linéaire

Le diatténuateur linéaire généralise le polariseur imparfait. Il ne cherche pas nécessairement à bloquer un axe : il transmet simplement deux axes propres avec des intensités différentes.

Dans le simulateur, on conserve un modèle simple :

T=1,T=αT_\parallel=1,\qquad T_\perp=\alpha

mais l’interprétation est différente de celle du polariseur imparfait. Le diatténuateur est un composant qui introduit des pertes dépendantes de la polarisation, pas un polariseur "raté".

Il peut donc :

  • modifier l’intensité totale ;
  • favoriser une direction de polarisation ;
  • transformer partiellement l’état de Stokes ;
  • rendre une lumière partiellement plus ordonnée selon un axe.

Ce dispositif est particulièrement utile pour comprendre que l’absorption ou la transmission peuvent être anisotropes sans être nécessairement idéales.

13.4. Retardateur imparfait

Une lame retardatrice idéale modifie la phase relative entre deux composantes sans diminuer le degré de polarisation. Dans un composant réel, ce comportement peut être perturbé par des défauts, de l’inhomogénéité, de la diffusion ou des effets non modélisés en Jones pur.

Le retardateur imparfait du simulateur est représenté comme une combinaison simple :

Mimparfait=Mdepol(α)MretardM_{\rm imparfait}=M_{\rm depol}(\alpha)\,M_{\rm retard}

Le retardateur agit d’abord sur l’état de polarisation, puis le dépolariseur réduit la partie polarisée.

Ce modèle permet de distinguer deux effets :

  • le retard, qui modifie l’ellipse de polarisation ;
  • la dépolarisation, qui réduit la pureté de l’état.

Deux états peuvent avoir une ellipse similaire en apparence mais des degrés de polarisation différents.

13.5. Dépolariseur spectral

Le dépolariseur spectral introduit une dépendance en longueur d’onde. Certaines composantes spectrales peuvent être fortement dépolarisées tandis que d’autres restent presque intactes.

Le modèle utilisé est :

α(λ)=1(1αmin)exp[(λλc)22σ2]\alpha(\lambda)=1-(1-\alpha_{\min}) \exp\left[-\frac{(\lambda-\lambda_c)^2}{2\sigma^2}\right]

avec :

  • αmin\alpha_{\min} la polarisation résiduelle minimale au centre de la bande ;
  • λc\lambda_c la longueur d’onde où la dépolarisation est maximale ;
  • σ\sigma la largeur spectrale de l’effet.

Autour de λc\lambda_c, le facteur α(λ)\alpha(\lambda) devient proche de αmin\alpha_{\min}, donc la dépolarisation est forte. Loin de cette bande, α(λ)\alpha(\lambda) tend vers 1, donc le dispositif agit beaucoup moins.

Ce dispositif est particulièrement utile avec une source blanche. Il montre que la DOP de sortie peut dépendre du spectre, et qu’une seule longueur d’onde effective ne suffit pas toujours à résumer la situation.

13.6. Pourquoi ces dispositifs sont réservés au mode Mueller réaliste

Ces dispositifs sont volontairement masqués en mode Jones. La raison n’est pas qu'une contrainte d’interface mais bien également un choix physique.

Un vecteur de Jones représente un champ cohérent pur. Il ne contient pas l’information nécessaire pour représenter un mélange statistique de polarisations. Or les dispositifs comme le dépolariseur ou le retardateur imparfait agissent précisément sur cette dimension statistique.

Les rendre accessibles en mode Jones conduirait à deux mauvaises options :

  • soit les laisser sans effet, ce qui serait frustrant ;
  • soit leur inventer une matrice de Jones approximative, ce qui serait trompeur.

Le simulateur choisit donc la cohérence conceptuelle : les dispositifs qui nécessitent Mueller apparaissent en mode Stokes / Mueller réaliste, et disparaissent en mode Jones.

13.7. Limites des dispositifs Mueller avancés

Les modèles ajoutés restent volontairement phénoménologiques. Ils ne décrivent pas une microphysique détaillée du composant. Par exemple :

  • le dépolariseur isotrope ne précise pas le mécanisme matériel de dépolarisation ;
  • le polariseur imparfait est décrit par une fuite scalaire simple ;
  • le diatténuateur ne modélise pas des spectres de transmission expérimentaux distincts sur les deux axes ;
  • le retardateur imparfait regroupe plusieurs défauts possibles en un seul paramètre α\alpha ;
  • le dépolariseur spectral utilise une loi gaussienne simple et non une mesure réelle.

13.8. Apport pédagogique

Ces dispositifs enrichissent fortement le simulateur, car ils permettent de montrer que :

  • tous les composants optiques ne sont pas idéaux ;
  • une lumière peut perdre son degré de polarisation sans perdre toute son intensité ;
  • la description Jones est insuffisante pour certains phénomènes ;
  • le formalisme de Mueller est parfois indispensable ;
  • une grandeur comme la DOP peut varier avec la longueur d’onde.

Ils donnent donc une justification concrète à la coexistence des deux modes du simulateur : Jones pour les états cohérents purs, Mueller pour les descriptions statistiques réalistes.

14. Milieux optiques et dispersion

Les milieux représentent des zones matérielles insérées le long du banc optique. Ils modifient la propagation en changeant l’indice local, donc la phase et la longueur d’onde dans le milieu.

Ils servent à montrer :

  • la différence entre longueur géométrique et chemin optique ;
  • l’effet d’un indice fixe ;
  • l’effet d’un indice dépendant de la longueur d’onde ;
  • l’impact de la dispersion sur les composantes spectrales.

14.1. Indice fixe

Dans le modèle le plus simple, le milieu possède un indice constant nn. Si la longueur d’onde dans le vide vaut λ0\lambda_0, la longueur d’onde dans le milieu vaut

λm=λ0n.\lambda_m=\frac{\lambda_0}{n}.

La phase accumulée sur une distance LL dépend du chemin optique :

Φ=2πλ0nL.\Phi=\frac{2\pi}{\lambda_0}nL.

Ce modèle permet d’isoler l’effet principal d’un milieu : la phase avance plus vite en chemin optique lorsque nn augmente.

14.2. Indice dispersif

Dans un milieu dispersif, l’indice dépend de la longueur d’onde :

n=n(λ).n=n(\lambda).

Chaque composante spectrale accumule alors une phase différente. C’est essentiel pour les sources spectrales : le milieu ne traite plus la lumière comme un bloc unique, mais comme une somme de composantes ayant chacune son propre indice.

14.3. Loi de Sellmeier

Pour les matériaux transparents usuels, le simulateur utilise une loi de Sellmeier :

n2(λ)=1+iBiλ2λ2Ci.n^2(\lambda)=1+\sum_i\frac{B_i\lambda^2}{\lambda^2-C_i}.

La longueur d’onde est exprimée en micromètres dans les coefficients utilisés. Les coefficients BiB_i et CiC_i dépendent du matériau.

Cette loi est standard pour décrire la dispersion de nombreux verres dans le visible. Elle est suffisamment réaliste pour donner une variation crédible de l’indice, tout en restant simple et rapide à évaluer.

14.4. Matériaux implémentés

Les matériaux disponibles sont les suivants.

MatériauIndice fixe par défautModèle dispersif
Air1.00non dispersif dans le simulateur
Eau1.33Sellmeier approché
Verre1.50assimilé à BK7
BK71.5168Sellmeier standard BK7
Silice1.4585Sellmeier silice fondue
Flint F21.6200Sellmeier flint F2
Personnaliséchoisi par l’utilisateurnon dispersif par défaut

Air

L’air est pris comme référence quasi vide, avec un indice fixé à 1.00. Les faibles variations réelles de l’indice de l’air avec la pression, la température, l’humidité ou la longueur d’onde ne sont pas modélisées. Ce choix évite de rendre visible un effet très faible dans un simulateur pédagogique.

Eau

L’eau reçoit un indice fixe par défaut n=1.33n=1.33. En mode dispersif, une loi de Sellmeier approchée est utilisée afin d’obtenir une variation plausible dans le visible. L’objectif est une représentation qualitative correcte d’un liquide transparent faiblement dispersif.

Coefficients utilisés :

CoefficientValeurs
BiB_i0.568909 ; 0.173847 ; 0.020593
CiC_i0.005101 ; 0.018251 ; 0.026241

Verre et BK7

Le matériau “Verre” est volontairement générique. Pour lui donner un comportement dispersif défendable, il est assimilé au BK7, un verre optique courant. Le matériau “BK7” utilise explicitement les mêmes coefficients.

CoefficientValeurs
BiB_i1.03961212 ; 0.231792344 ; 1.01046945
CiC_i0.00600069867 ; 0.0200179144 ; 103.560653

Ce choix permet d’obtenir une dispersion réaliste pour un verre optique standard, sans multiplier les matériaux.

Silice

La silice représente un matériau transparent plus faiblement dispersif que certains verres denses. Elle est utile pour comparer des milieux aux indices proches mais aux dispersions différentes.

CoefficientValeurs
BiB_i0.6961663 ; 0.4079426 ; 0.8974794
CiC_i0.00467914826 ; 0.0135120631 ; 97.9340025

Flint F2

Le Flint F2 représente un verre plus dispersif et d’indice plus élevé. Il est utile pour rendre visible l’effet d’un matériau plus marqué sur les composantes spectrales.

CoefficientValeurs
BiB_i1.34533359 ; 0.209073176 ; 0.937357162
CiC_i0.00997743871 ; 0.0470450767 ; 111.886764

Milieu personnalisé

Le milieu personnalisé permet à l’utilisateur de choisir un indice fixe. Il ne possède pas de loi dispersive inventée automatiquement, car il serait scientifiquement arbitraire d’attribuer une dispersion sans données supplémentaires.

14.5. Comment le milieu agit dans le calcul

Le simulateur évalue le chemin optique accumulé le long de l’axe de propagation. Dans une région de milieu, la coordonnée optique progresse plus vite que la coordonnée géométrique lorsque n>1n>1.

Pour une source spectrale, cette opération est faite longueur d’onde par longueur d’onde lorsque la dispersion est activée. La phase d’une composante bleue et celle d’une composante rouge peuvent donc évoluer différemment dans le même milieu.

14.6. Ce que le modèle de milieu ne décrit pas

Le modèle actuel ne décrit pas :

  • l’absorption spectrale des matériaux ;
  • les indices complexes ;
  • la diffusion ;
  • les réflexions aux interfaces ;
  • les interférences dues aux faces d’une lame ;
  • la dépendance à l’angle d’incidence ;
  • la biréfringence des milieux ordinaires, sauf dans les lames retardatrices ;
  • les variations thermiques ou mécaniques de l’indice.

Les interfaces entre milieux sont donc des frontières de phase et d’indice pour l’affichage, pas des surfaces qui réfléchissent ou réfractent réellement un faisceau oblique.

14.7. Pourquoi ce choix reste cohérent

Le but est de montrer l’effet du chemin optique et de la dispersion sans transformer le simulateur en solveur complet d’électromagnétisme. La loi de Sellmeier conserve une base matérielle identifiable, tandis que l’absence de réflexions et d’absorption garde l’interface lisible et interactive.

15. Degré de polarisation

Le degré de polarisation (DOP) est une grandeur centrale en mode réaliste. Il mesure la part ordonnée de la lumière.

  • DOP = 1 : état totalement polarisé ;
  • DOP = 0 : état non polarisé ;
  • valeurs intermédiaires : états partiellement polarisés.

Dans le simulateur, le DOP intervient :

  • dans les détails mathématiques ;
  • dans les graphes d’analyse ;
  • dans le rendu visuel, en séparant partie polarisée et halo statistique.

16. Cohérence temporelle

La cohérence temporelle intervient dès qu’on parle de spectres non monochromatiques.

Une source très étroite spectralement possède une longueur de cohérence plus grande ; une source large spectralement possède une longueur de cohérence plus courte.

Le simulateur utilise cette idée pour :

  • estimer une longueur de cohérence ;
  • relier structure spectrale et comportement statistique ;
  • enrichir les analyses en mode Mueller ;
  • préparer des extensions futures vers l’interférométrie analytique.

Il faut cependant bien distinguer deux usages :

  • cohérence comme grandeur théorique ;
  • cohérence comme effet visible dans la texture de l’animation.

Le projet prend soin de ne pas confondre ces deux niveaux.

16.1. Comment la lumière non polarisée est simulée

C’est ici que se concentrent une grande partie des choix de simulation les plus délicats.

Le simulateur ne représente pas la lumière non polarisée comme une unique onde désordonnée. Il procède autrement :

  • le spectre est discrétisé en plusieurs composantes représentatives ;
  • chaque composante traverse toute la chaîne optique ;
  • à chaque zone, cette composante possède un poids, une longueur d’onde, un état de Stokes local et un degré de polarisation local ;
  • l’animation finale superpose ces composantes sous forme de courbes continues.

Autrement dit, une lumière non polarisée ou partiellement polarisée n’est pas simulée comme un champ brut mais comme une somme de composantes statistiques rendues visuellement.

16.2. Partie polarisée et partie non polarisée

Le rôle du degré de polarisation est central. Pour chaque composante affichée, on sépare conceptuellement :

  • une partie ordonnée, liée à la polarisation locale ;
  • une partie désordonnée, liée au caractère non polarisé.

Dans la vue principale, cela se traduit par :

  • une composante principale plus structurée ;
  • un halo ou une texture plus diffuse pour la partie non polarisée.

Cette séparation matérialise la différence entre une lumière décrite par un état polarisé bien défini et une lumière statistique dont les orientations et phases ne sont pas parfaitement corrélées.

16.3. Gestion des phases statistiques

Un point très important du simulateur est la manière dont les phases sont gérées en mode réaliste.

Le projet ne redéfinit pas entièrement une nouvelle phase aléatoire à chaque zone. Ce serait visuellement brutal et physiquement peu satisfaisant. À la place :

  • chaque composante spectrale reçoit une phase globale de départ stable ;
  • cette phase reste continue tout au long de la propagation ;
  • des fluctuations supplémentaires sont ajoutées sous forme de bruit de phase contrôlé ;
  • l’intensité et le caractère de ces fluctuations dépendent de la cohérence estimée.

C’est ce choix qui évite les coupures artificielles aux interfaces et qui permet de conserver une continuité visuelle crédible même pour des lumières statistiques.

16.4. Fonction de cohérence et traduction dans le simulateur

Dans un cadre plus théorique, la cohérence temporelle peut être reliée à une fonction de cohérence qui mesure à quel point deux valeurs du champ restent corrélées lorsqu’on introduit un retard.

Dans le projet, cette idée n’est pas traitée sous la forme la plus générale possible, c'est une modélisation raisonnable :

  • une longueur de cohérence est estimée à partir de la largeur spectrale de la source ;
  • cette longueur règle le niveau de fluctuations introduites dans la phase ;
  • plus la source est large spectralement, plus la cohérence est faible ;
  • plus la cohérence est faible, plus la texture de l’onde doit apparaître instable ou diffuse.

La fonction de cohérence utilisée dans l’esprit du modèle est de type gaussien, ce qui est cohérent avec une approximation fréquentielle simple :

γ(ΔL)=exp[(ΔLLc)2]\gamma(\Delta L) = \exp\left[-\left(\frac{\Delta L}{L_c}\right)^2\right]

Ici :

  • ΔL représente une différence de chemin optique ;
  • Lc représente la longueur de cohérence.

Cette écriture justifie directement la manière dont le simulateur relie largeur spectrale, stabilité de phase et rendu visuel.

16.5. Ce que cela change visuellement

Ces choix se traduisent de manière concrète dans l’animation :

  • une source très cohérente garde une structure plus nette ;
  • une source large spectralement présente une texture plus fluctuante ;
  • une lumière non polarisée montre davantage de halo et moins d’ordre géométrique ;
  • un milieu modifie aussi le chemin optique local, donc la phase cumulée de chaque composante.

Ainsi, la vue réaliste cherche à traduire visuellement la nature statistique de la lumière simulée.

16.6. Limites de ce choix

Il faut toutefois être clair : cette simulation n’est pas une réalisation Monte-Carlo complète d’un champ électromagnétique statistique. C’est une représentation intermédiaire, qui vise à conserver les bonnes idées physiques :

  • continuité des composantes ;
  • dépendance aux milieux ;
  • dépendance à la cohérence ;
  • distinction entre partie polarisée et non polarisée ;
  • interprétation correcte des formalismes de Stokes et de Mueller.

La finalité n’est pas de prétendre simuler toute la théorie de la cohérence optique, mais de proposer une représentation physiquement argumentée et exploitable pédagogiquement.

17. Vue principale 3D

La vue principale a pour but de montrer la propagation et les effets des dispositifs dans un espace lisible.

Elle affiche selon les cas :

  • les composantes ExE_x, EyE_y et le champ total en mode cohérent ;
  • les milieux ;
  • les dispositifs ;
  • les trajectoires d’entrée et de sortie ;
  • les composantes spectrales réalistes en mode Mueller.

Important

La vue principale n’est pas une reproduction brute d’un champ électromagnétique complet dans l’espace-temps. C’est une représentation choisie, conçue pour rendre perceptibles des phénomènes qui, autrement, resteraient invisibles.

18. Fenêtre spectrale

La fenêtre spectrale montre :

  • le spectre source ;
  • le spectre de sortie ;
  • une bande colorée correspondant au rendu visuel du spectre de sortie.

C’est une interface clé entre la physique et la perception. Elle permet de comprendre qu’une même couleur visible peut provenir de structures spectrales différentes, et qu’un dispositif peut modifier le spectre transmis de manière non triviale.

19. Détails mathématiques

Cette fenêtre joue un rôle central dans le caractère sérieux du projet.

Elle ne sert pas seulement à rajouter des formules, mais à montrer ce qui est réellement calculé. Elle peut contenir selon le mode :

  • équation d’onde plane ;
  • séquence de Jones ;
  • matrice de Jones totale ;
  • intensités intermédiaires ;
  • vecteurs de Stokes ;
  • matrices de Mueller ;
  • DOP ;
  • longueur de cohérence ;
  • intégrales spectrales.

Cette séparation entre vue et détails mathématiques permet au simulateur de parler à deux publics :

  • un utilisateur qui veut surtout voir et manipuler ;
  • un utilisateur qui veut comprendre précisément les calculs.

20. Analyse de polarisation

La fenêtre d’analyse de polarisation a pour vocation d’être complémentaire des détails mathématiques.

Elle permet de mettre en évidence:

  • l’évolution de l’intensité le long de la chaîne ;
  • l’évolution du degré de polarisation ;
  • l’évolution des coefficients de Stokes ;
  • la structure spectrale de la transmission et du DOP.

21. Choix de modélisation : ce qui est exact, ce qui est approché

Pour qu’un simulateur soit crédible, il faut expliciter ses approximations.

21.1. Aspects bien modélisés

  • structure de polarisation en Jones ;
  • transformations par polariseurs et lames ;
  • description Stokes / Mueller ;
  • propagation spectrale par échantillonnage en longueur d’onde ;
  • influence des milieux via l’indice et la dispersion ;
  • couleur de sortie obtenue à partir du spectre.

21.2. Aspects représentés de manière pédagogique

  • longueur d’onde effective d’affichage ;
  • visualisation des composantes spectrales réalistes ;
  • halo de la partie non polarisée ;
  • fluctuations de phase utilisées pour rendre visible la nature statistique.

Ces choix ne sont pas des tricheries arbitraires : ce sont des choix de représentation. Ils servent à rendre visible un contenu physique qui, dans une simulation brute, serait soit trop complexe, soit illisible, soit beaucoup trop coûteux à calculer en temps réel.

21.3. Aspects non modélisés complètement

  • propagation vectorielle complète dans l’espace 3D réel ;
  • cohérence spatiale détaillée ;
  • interférences multi-bras complètes dans la vue principale ;
  • électrodynamique complète des matériaux ;
  • réponses instrumentales expérimentales détaillées.

Le projet assume ces limites. Il ne prétend pas être un solveur complet de Maxwell, mais un simulateur physiquement informé et pédagogiquement solide.

21.4. Pourquoi ces choix restent pertinents

Le cœur de la démarche consiste à conserver les bonnes structures physiques tout en simplifiant leur représentation :

  • on garde les bons formalismes selon les régimes ;
  • on garde la dépendance spectrale ;
  • on garde la dépendance aux milieux ;
  • on garde la distinction entre partie cohérente et description statistique ;
  • mais on simplifie la manière de montrer ces objets à l’écran.

On ne cherche pas (et on n'a pas la prétention de le faire) un cours complet mais on doit rester assez rigoureux pour qu’un utilisateur curieux puisse réellement y apprendre quelque chose de juste.

22. Public visé

Le simulateur s’adresse à plusieurs niveaux.

22.1. Niveau découverte

Un utilisateur débutant peut :

  • voir ce qu’est une polarisation ;
  • comprendre l’effet d’un polariseur ;
  • comparer des sources ;
  • voir qu’un milieu change la propagation ;
  • distinguer lumière monochromatique et lumière spectrale.

22.2. Niveau intermédiaire

Un étudiant peut :

  • relier la vue aux vecteurs de Jones ;
  • interpréter les matrices de Mueller ;
  • étudier les effets de la dispersion ;
  • comparer différents montages optiques.

22.3. Niveau avancé

Un utilisateur expérimenté peut :

  • analyser les hypothèses numériques ;
  • suivre les matrices intermédiaires ;
  • relier la matrice de cohérence aux états de Stokes ;
  • réfléchir aux extensions possibles vers la cohérence et l’interférométrie.

23. Cohérence entre interface et théorie

Un enjeu majeur du projet est la cohérence globale entre :

  • ce que l’utilisateur manipule ;
  • ce que le programme calcule ;
  • ce que la fenêtre d’analyse explique ;
  • ce que les détails mathématiques affichent.

Autrement dit, il ne suffit pas qu’une animation soit belle ou qu’une formule soit correcte isolément. L’important est que tout l’ensemble raconte la même physique.

Cette exigence a guidé plusieurs choix :

  • différencier Jones et Mueller ;
  • introduire une longueur d’onde effective seulement comme outil d’affichage ;
  • ne pas représenter une source blanche comme une unique onde monochromatique blanche ;
  • faire apparaître le rôle du DOP ;
  • intégrer les milieux dans la phase des composantes réalistes.

24. Fonctionnalités complémentaires de la version web

La version web du simulateur ne se limite pas à la scène principale. Plusieurs fenêtres et outils permettent de relier la visualisation aux grandeurs calculées.

24.1. Analyse de cohérence

L’analyse de cohérence est disponible en mode Stokes / Mueller réaliste. Elle affiche notamment :

  • une estimation de la largeur spectrale ;
  • une longueur de cohérence ;
  • une courbe de cohérence gaussienne ;
  • une figure de franges analytiques amorties ;
  • l’influence qualitative de la source sur la visibilité.

La figure de franges ne prétend pas simuler un interféromètre complet dans la scène 3D. Elle fournit une représentation analytique séparée pour comprendre le lien entre largeur spectrale et extinction progressive des interférences.

24.2. Sonde locale

La sonde locale permet d’interroger l’état lumineux en un point du banc. Elle calcule les grandeurs correspondant à la position choisie :

  • région optique locale ;
  • dispositifs déjà traversés et dispositifs restants ;
  • spectre local ;
  • transmission locale ;
  • couleur locale ;
  • vecteur de Stokes local ;
  • degré de polarisation ;
  • ellipse ou état dominant selon le mode.

24.3. Expériences préétablies et expériences guidées

Les expériences préétablies permettent de charger rapidement des montages représentatifs : polariseur-analyseur, lames retardatrices, sources spectrales, dispositifs Mueller ou filtres.

Les expériences guidées ajoutent une couche pédagogique : elles proposent un objectif, des étapes et les outils pertinents à ouvrir. Elles ne changent pas la physique calculée ; elles organisent seulement l’exploration.

24.4. Import et export de configuration

L’import/export enregistre l’état complet du montage :

  • source ;
  • mode de polarisation ;
  • paramètres d’onde ;
  • dispositifs et positions ;
  • milieux ;
  • options d’affichage ;
  • paramètres d’analyse.

Cette fonctionnalité permet de partager un montage, de revenir à un état de travail ou de conserver une expérience précise pour une comparaison ultérieure.

24.5. Structure logicielle de la version web

La version web sépare plusieurs niveaux :

  • une interface React / Next.js pour les contrôles, fenêtres et pages du site ;
  • une scène 3D WebGL pour la visualisation ;
  • un backend FastAPI pour les calculs physiques ;
  • des modules de cœur numérique pour l’optique, les spectres, la polarisation et la cohérence.

Cette séparation évite de mélanger l’interface et les modèles. Elle facilite aussi l’évolution du projet : les calculs peuvent être enrichis sans réécrire toute l’interface, et l’interface peut être améliorée sans changer les équations physiques.

24.6. Choix d’interface et de visualisation

Plusieurs choix visuels sont explicitement des choix de représentation :

  • les composantes ExE_x et EyE_y peuvent être affichées séparément en mode Jones ;
  • les trajectoires d’entrée et de sortie montrent la forme locale de la polarisation ;
  • les composantes spectrales en mode Mueller représentent des échantillons visuels du spectre ;
  • le halo représente la partie non polarisée ou statistiquement désordonnée ;
  • les dispositifs Mueller non compatibles avec Jones sont masqués en mode Jones ;
  • les contrôles sans signification physique dans un contexte donné sont désactivés ou masqués.

Le but est de ne pas donner à l’utilisateur des commandes qui produiraient une interprétation trompeuse. L’interface fait donc partie de la modélisation : elle encode aussi ce que le simulateur considère comme physiquement pertinent.

25. Perspectives d’évolution

La version actuelle couvre déjà la propagation, la polarisation, les sources spectrales, les filtres, les milieux dispersifs, l’analyse de polarisation, la cohérence, la sonde locale et l’import/export de configurations. Les perspectives suivantes ne sont donc pas des éléments manquants au fonctionnement principal, mais des pistes d’enrichissement.

25.1. Interférométrie analytique

Une extension naturelle serait une fenêtre dédiée à des montages interférométriques analytiques, par exemple Michelson ou Mach-Zehnder. L’objectif ne serait pas nécessairement d’ajouter un banc 3D complet, mais de relier :

  • différence de marche ;
  • largeur spectrale ;
  • visibilité ;
  • dispersion ;
  • source utilisée.

Cette extension compléterait l’analyse de cohérence actuelle.

25.2. Courbes expérimentales de filtres et de matériaux

Les filtres et certains matériaux pourraient être enrichis par des courbes tabulées expérimentales : transmission réelle de filtres, absorption spectrale, indices complexes, ou données fabricants supplémentaires.

Cela permettrait de passer d’un modèle pédagogique paramétrique à un modèle plus proche d’un composant réel précis.

25.3. Extensions de sources et de capteurs

Le simulateur pourrait intégrer d’autres sources : lasers à largeur finie, spectres mesurés, sources astronomiques simplifiées ou fichiers importés. Une autre extension possible serait d’ajouter une réponse de capteur ou de caméra, afin de distinguer spectre physique, perception visuelle et mesure instrumentale.

26. Conclusion

En conclusion, le projet constitue une tentative de représentation cohérente de plusieurs couches de la physique de la lumière :

  • la propagation ;
  • la polarisation ;
  • les transformations optiques ;
  • la nature spectrale des sources ;
  • la distinction entre lumière cohérente et lumière statistique ;
  • l’influence des milieux ;
  • les outils mathématiques adaptés à chaque régime.

Sa valeur tient précisément au fait qu’il ne se contente pas de juxtaposer des fonctionnalités. Il cherche à maintenir une continuité entre intuition visuelle et calcul/affichage numérique .

Cette documentation a pour but de rendre ce travail explicite, exploitable et transmissible dans le cadre de la version web du projet.

Il faut toutefois rappeler la philosophie exacte de l’ensemble : le simulateur n’a pas vocation à remplacer un traité complet d’optique physique ni un solveur général des équations de Maxwell. Son rôle est plutôt de proposer une représentation physiquement argumentée, suffisamment rigoureuse pour être justifiée, suffisamment visuelle pour être manipulable, et suffisamment explicite pour que l’utilisateur puisse comprendre ce que le logiciel montre réellement.

C’est cette position intermédiaire qui fait l’intérêt du projet :

  • pour un débutant, il offre une entrée visuelle dans des notions souvent abstraites ;
  • pour un étudiant, il relie calcul, représentation et interprétation ;
  • pour un utilisateur plus avancé, il donne accès à une structuration cohérente entre Jones, Stokes, Mueller, cohérence, dispersion et visualisation numérique.

La cohérence d’ensemble du projet repose donc sur un principe simple : ne jamais séparer complètement l’animation de la théorie, ni la théorie de ses limites.

27. Références et ressources complémentaires

Cette section concentre les sources techniques utilisées pour certaines des courbes et lois utilisées par le simulateur.

27.1. Colorimétrie et illuminant D65

27.2. Dispersion et verres optiques

28. Sélection de ressources pour aller plus loin

Je donne ici trois titres d'ouvrages qui couvrent le sujet abordé par ce simulateur, et permettent d'aller plus loin sur l'optique anisotrope, les matrices de Mueller et les systèmes optiques plus complexes.

28.1. Optique (Édition Française) — Eugene Hecht

  • Éditeur : De Boeck Supérieur
  • Niveau : L3 / M1
  • Pourquoi le consulter : C'est une des références internationales sur le sujet. Il est très complet sur la polarisation, donne beaucoup d'explications et de schémas, le tout à un niveau abordable. Il pose aussi clairement le formalisme de Jones. Toutefois attention la forme en double colonne par page peut être un peu indigeste, il n'est pas forcément évident de prendre des notes avec ce support.

28.2. Optique physique : Propagation de la lumière — Richard Taillet

  • Éditeur : De Boeck Supérieur
  • Niveau : L2/L3
  • Pourquoi le consulter : Grande rigueur mathématique et clarté d'exposition. Il est idéal pour comprendre les équations de propagation sous-jacentes au moteur du simulateur, le comportement des paquets d'ondes, ainsi que le traitement des déphasages et des états de polarisation. Moins complet que le Hecht mais aussi clairement plus digeste.

28.3. Polarized Light (En Anglais) — Dennis H. Goldstein

  • Éditeur : CRC Press
  • Niveau : Plus avancé
  • Pourquoi le consulter : C'est la référence absolue et incontournable sur le sujet. Si vous devez pousser le simulateur dans ses retranchements ou vérifier l'implémentation du formalisme de Mueller (4×44 \times 4) et des paramètres de Stokes, c'est l'ouvrage à ouvrir. Il traite de manière exhaustive la transition entre la lumière totalement cohérente (Jones) et partiellement dépolarisée (Mueller), avec une approche très orientée ingénierie et calcul de systèmes optiques.

29. Annexe — Architecture du moteur physique et logique de code

Cette annexe décrit plus en détail la structure du code qui porte la physique du simulateur.

On peut résumer l’organisation générale ainsi :

  • le frontend décrit un état de montage choisi par l’utilisateur ;
  • le backend convertit cet état en objets physiques ;
  • les cœurs de calcul appliquent les modèles Jones, Stokes/Mueller, spectraux et dispersifs ;
  • le backend renvoie des grandeurs numériques déjà organisées pour la scène 3D et les fenêtres d’analyse ;
  • la scène web transforme ces grandeurs en rendu visuel, sans réinventer la physique.

Cette séparation est volontaire : elle évite que la visualisation devienne le lieu du calcul physique principal. Le frontend peut choisir quoi afficher, mais il ne doit pas décider seul de la propagation, de la polarisation ou de la transmission spectrale.

29.1. Vue d’ensemble des modules

Le moteur physique est structuré autour de plusieurs modules spécialisés.

ModuleRôle principalType de logique contenue
optics_core.pyObjets de base du banc optiqueonde, dispositifs, milieux, indices, phase, Jones simple, filtres, sauvegarde
spectral_core.pyCalcul spectralsources, grille visible, spectres de sortie, RGB, cannelures, régions spectrales
polarization_core.pyPolarisationJones, Stokes, Mueller, diatténuateurs, retardateurs, dépolariseurs
coherence_core.pyCohérence temporelle simplifiéeestimation de longueur de cohérence, fonction de cohérence utilisée par l’analyse
coherence_matrix.pyMatrice de cohérenceconversion Jones/Stokes/cohérence, projection physique, séparation polarisée/non polarisée
main.pyInterface API du moteurconversion frontend/backend, orchestration, endpoints /simulate, /probe, /math-details
WaveScene.tsxVisualisation 3Dtubes, halos, dispositifs, milieux, trajectoires, options d’affichage

La séparation la plus importante est celle entre les modules de calcul et main.py. Les modules de calcul ne devraient pas dépendre de l’interface web : ils manipulent des objets physiques et des tableaux numériques. main.py joue le rôle de couche d’adaptation : il reçoit les structures envoyées par le frontend, construit l’état physique, appelle les fonctions nécessaires, puis reformate le résultat.

29.2. Objets physiques centraux

Le module optics_core.py définit les objets fondamentaux du banc. Le choix est de représenter les éléments optiques par des structures simples, explicites et sérialisables.

Extrait représentatif :

@dataclass
class Wave:
    Ex0: float = 1.0
    Ey0: float = 1.0
    phi_deg: float = 90.0
    wavelength_nm: float = 550.0
    wavelength_display: float = 2.0
    n: float = 1.0
    speed: float = 1.0

@dataclass
class OpticalDevice:
    name: str = "Aucun"
    z: float = 0.0
    angle: float = 0.0
    retard: float = 90.0
    physical_mode: bool = False
    thickness_nm: float = 137500.0
    birefringence: float = 0.001
    filter_center_nm: float = 550.0
    filter_width_nm: float = 45.0
    filter_peak_transmission: float = 1.0
    depolarization_factor: float = 0.25

@dataclass
class OpticalMedium:
    name: str = "Air"
    z_start: float = 0.0
    z_end: float = 16.0
    n: float = 1.0
    use_dispersion: bool = False

Le point important est que OpticalDevice est volontairement générique. Un polariseur n’utilise pas les paramètres spectraux ; un filtre n’utilise pas le retard ; un dépolariseur ne lit pas les paramètres Jones. Ce choix simplifie l’interface : tous les dispositifs ont une structure commune, et les fonctions physiques décident quels paramètres sont pertinents.

Cette approche évite de multiplier des classes spécialisées pour chaque dispositif. Pour un simulateur interactif, c’est un compromis utile : l’état est facile à sauvegarder, transmettre, modifier et reconstruire. La contrepartie est qu’il faut être rigoureux dans les fonctions de dispatch, afin qu’un paramètre inutilisé ne soit pas interprété au mauvais endroit.

29.3. État complet du banc

L’état complet du banc est regroupé dans SimulationState. Il contient l’onde, la chaîne de dispositifs, les milieux, les bornes spatiales et quelques informations sur la source.

Extrait représentatif :

@dataclass
class SimulationState:
    wave: Wave = field(default_factory=Wave)
    devices: list[OpticalDevice] = field(default_factory=list)
    media: list[OpticalMedium] = field(default_factory=list)

    z_min: float = 0.0
    z_max: float = 16.0
    base_spacing: float = 4.0
    auto_spacing: bool = True

    source_type: str = "Monochromatique"
    blackbody_temperature_K: float = 4500.0
    custom_spectral_lines: list = field(default_factory=list)

    polarization_model: str = "Jones cohérent"
    realistic_polarization: str = "Non polarisée"
    partial_polarization_degree: float = 1.0

Cet objet joue le rôle de photographie du montage. Il ne contient pas seulement les paramètres visibles dans l’interface ; il contient aussi les informations nécessaires pour calculer la propagation. Les méthodes associées à l’état se chargent de maintenir des positions cohérentes lorsque l’espacement automatique est activé ou lorsque les milieux doivent rester dans les bornes du banc.

Le simulateur sépare cependant deux niveaux :

  • l’état utilisateur, envoyé par le frontend ;
  • l’état physique reconstruit par le backend.

Cette séparation est importante parce que le frontend utilise des noms et conventions JavaScript (filterCenterNm, physicalMode, zStart) alors que le backend utilise des objets Python (filter_center_nm, physical_mode, z_start).

29.4. Adaptation frontend → backend

La conversion est concentrée dans main.py. Elle est volontairement explicite : chaque champ utile est recopié dans l’objet Python correspondant. Cela rend le passage d’un monde à l’autre lisible et limite les ambiguïtés.

Extrait représentatif :

def frontend_device_to_python(device):
    return OpticalDevice(
        name=device.name,
        z=device.z,
        angle=device.angle,
        retard=device.retard,
        physical_mode=device.physicalMode,
        thickness_nm=device.thicknessNm,
        birefringence=device.birefringence,
        filter_center_nm=device.filterCenterNm,
        filter_width_nm=device.filterWidthNm,
        filter_peak_transmission=device.filterPeakTransmission,
        depolarization_factor=device.depolarizationFactor,
    )


def frontend_medium_to_python(medium):
    return OpticalMedium(
        name=medium.name,
        z_start=medium.zStart,
        z_end=medium.zEnd,
        n=medium.n,
        use_dispersion=medium.useDispersion,
    )

Ce choix est plus verbeux qu’une conversion automatique par dictionnaire, mais il est plus robuste. Il permet de voir immédiatement quels paramètres traversent l’API, et il évite qu’un champ ajouté côté frontend soit utilisé côté backend sans décision explicite.

La fonction de construction d’état rassemble ensuite l’onde, la source, les dispositifs et les milieux :

def build_state(payload: SimulationInput):
    state = SimulationState()
    state.z_min = payload.zMin
    state.z_max = payload.zMax

    state.wave = Wave(
        Ex0=payload.wave.Ex0,
        Ey0=payload.wave.Ey0,
        phi_deg=payload.wave.phiDeg,
        wavelength_nm=payload.wave.wavelengthNm,
    )

    state.source_type = payload.source.name
    state.blackbody_temperature_K = payload.source.blackbodyTemperatureK
    state.custom_spectral_lines = custom_lines_to_python(payload.source.customLines)
    state.devices = [frontend_device_to_python(d) for d in payload.devices]
    state.media = [frontend_medium_to_python(m) for m in payload.media]

    return state

Cette fonction est un point de contrôle. Si un jour on ajoute une nouvelle grandeur physique, elle doit être intégrée ici ou volontairement ignorée. Cela force à expliciter le lien entre interface et calcul.

29.5. Dispatch physique des dispositifs

Un choix explicite du code est de ne pas donner à tous les dispositifs le même type de matrice dans tous les modes. Un polariseur ou une lame peut être représenté en Jones et en Mueller. Un dépolariseur, lui, n’a pas de matrice de Jones simple agissant sur un unique état pur.

Le dispatch Jones ressemble à ceci :

def device_jones_matrix(device, wavelength_nm=550.0):
    name = device.name
    angle = getattr(device, "angle", 0.0)

    if name == "Aucun":
        return np.eye(2, dtype=complex)

    if name == "Polariseur x":
        return jones_linear_polarizer(angle)

    if name == "Polariseur y":
        return jones_linear_polarizer(angle + 90.0)

    if name in ["Lame quart onde", "Lame demi onde", "Lame retard"]:
        return jones_retarder(
            angle_deg=angle,
            retard_deg=device_retard_deg(device, wavelength_nm),
        )

    # Les dispositifs Mueller avancés ne sont pas représentables par une
    # matrice de Jones agissant sur un seul état pur.
    return np.eye(2, dtype=complex)

La dernière ligne est un choix de prudence : en mode Jones, les dispositifs qui demandent une description statistique ne sont pas forcés artificiellement dans un formalisme inadapté. Dans l’interface, ils sont masqués lorsque le mode choisi ne permet pas de les interpréter correctement.

Le dispatch Mueller est plus riche :

def device_mueller_matrix(device, wavelength_nm=550.0):
    name = device.name
    angle = getattr(device, "angle", 0.0)

    if name == "Polariseur x":
        return mueller_linear_polarizer(angle)

    if name == "Polariseur y":
        return mueller_linear_polarizer(angle + 90.0)

    if name in ["Lame quart onde", "Lame demi onde", "Lame retard"]:
        return mueller_retarder(
            angle_deg=angle,
            retard_deg=device_retard_deg(device, wavelength_nm),
        )

    if name == "Dépolariseur":
        return mueller_depolarizer(device.depolarization_factor)

    if name == "Polariseur imparfait":
        return mueller_linear_diattenuator(
            angle_deg=angle,
            t_parallel=1.0,
            t_perpendicular=device.depolarization_factor,
        )

    if name == "Dépolariseur spectral":
        return mueller_spectral_depolarizer(device, wavelength_nm=wavelength_nm)

    return mueller_identity()

Ce dispatch encode la signification physique de chaque dispositif. Un retardateur imparfait n’est pas seulement une lame avec un mauvais angle : il est modélisé comme une transformation retardatrice suivie d’un effet dépolarisant.

29.6. Rotations et conventions d’orientation

Les dispositifs orientés utilisent une convention commune : on écrit une matrice dans ses axes propres, puis on la tourne. En Jones, la rotation agit sur les deux composantes transverses ; en Mueller, elle agit sur les paramètres S1S_1 et S2S_2, d’où l’apparition de 2θ2\theta.

Extrait Jones :

def rotation_jones(angle_deg):
    theta = np.deg2rad(angle_deg)
    c = np.cos(theta)
    s = np.sin(theta)
    return np.array([[c, -s], [s, c]], dtype=complex)


def jones_retarder(angle_deg=0.0, retard_deg=90.0):
    delta = np.deg2rad(retard_deg)
    J0 = np.array([[1.0, 0.0], [0.0, np.exp(1j * delta)]], dtype=complex)
    R = rotation_jones(angle_deg)
    R_inv = rotation_jones(-angle_deg)
    return R_inv @ J0 @ R

Extrait Mueller :

def rotation_mueller(angle_deg):
    theta = np.deg2rad(angle_deg)
    c = np.cos(2.0 * theta)
    s = np.sin(2.0 * theta)
    return np.array(
        [[1.0, 0.0, 0.0, 0.0],
         [0.0, c, s, 0.0],
         [0.0, -s, c, 0.0],
         [0.0, 0.0, 0.0, 1.0]],
        dtype=float,
    )

Le facteur 2 en Mueller est un point important du modèle : une polarisation linéaire orientée à θ\theta correspond à un point de Stokes faisant intervenir cos2θ\cos 2\theta et sin2θ\sin 2\theta. Le code reflète donc directement la géométrie de la sphère de Poincaré.

29.7. Propagation Jones cohérente

En mode Jones, le code doit produire deux choses différentes :

  • l’état complexe local utilisé pour la physique ;
  • les tableaux réels Ex(z)E_x(z) et Ey(z)E_y(z) utilisés pour l’affichage instantané.

La chaîne de dispositifs est appliquée dans l’ordre spatial. Les filtres sont traités à part, car ils sont scalaires et spectraux : ils ne sont pas des matrices Jones de polarisation, mais des transmissions d’intensité. Pour appliquer une transmission d’intensité à une amplitude complexe, on multiplie l’amplitude par la racine de la transmission.

Extrait représentatif du calcul spectral Jones :

def compute_jones_output_for_wavelength(state, wavelength_nm):
    A_in = get_jones_vector(state.wave)
    J_total = np.eye(2, dtype=complex)
    scalar_transmission = 1.0

    for device in sorted(state.devices, key=lambda d: d.z):
        if is_filter_device(device):
            scalar_transmission *= filter_transmission(device, wavelength_nm)
        else:
            J_total = jones_matrix(device, wavelength_nm) @ J_total

    A_out = J_total @ A_in
    A_out = np.sqrt(max(scalar_transmission, 0.0)) * A_out
    return A_in, A_out, J_total

La même logique est utilisée pour les champs locaux le long du banc : on segmente la propagation en régions séparées par les dispositifs, puis on attribue à chaque région l’amplitude complexe correspondant aux éléments déjà traversés.

Le choix de garder les amplitudes complexes Ax, Ay dans la sortie API est important. Le frontend peut ainsi reconstruire l’animation temporelle sans redemander un calcul complet au backend à chaque frame. Le backend renvoie la structure physique ; l’animation web applique ensuite la phase temporelle.

29.8. Phase, milieux et coordonnée optique

Le calcul de phase ne se limite pas à une formule kzkz avec un indice constant. Le code échantillonne la propagation et accumule localement la phase en fonction de l’indice du milieu traversé.

Extrait :

def medium_index_at_z_lambda(state, z_value, wavelength_nm):
    for medium in state.media:
        if medium.z_start <= z_value <= medium.z_end:
            if medium.use_dispersion and medium.name != "Personnalisé":
                return sellmeier_index(medium.name, wavelength_nm)
            return medium.n
    return state.wave.n


def compute_phase_array(state, z_array, wavelength_nm=None):
    phase = np.zeros_like(z_array, dtype=float)

    for i in range(1, len(z_array)):
        z_mid = 0.5 * (z_array[i] + z_array[i - 1])
        n_mid = medium_index_at_z_lambda(state, z_mid, wavelength_nm)
        k_mid = 2 * np.pi * n_mid / wavelength_display
        dz = z_array[i] - z_array[i - 1]
        phase[i] = phase[i - 1] + k_mid * dz

    return phase

La méthode est une intégration discrète du chemin optique. Elle est plus robuste qu’un simple découpage analytique par milieu, car elle reste valable même lorsque plusieurs zones sont ajoutées, déplacées ou modifiées. Elle donne aussi un comportement cohérent pour les sources spectrales : chaque longueur d’onde peut voir un indice différent si la dispersion est activée.

Le choix de z_mid est un compromis numérique classique : l’indice est évalué au milieu du pas, ce qui évite certains artefacts de bord lorsque l’échantillonnage tombe exactement sur une interface.

29.9. Dispersion et matériaux

La dispersion est codée dans une base de coefficients de Sellmeier. Les matériaux qui disposent d’une loi dispersive peuvent être évalués longueur d’onde par longueur d’onde ; les autres reviennent à un indice fixe.

Extrait simplifié :

SELLMEIER_DATABASE = {
    "Air": None,
    "Eau": {"B": [...], "C": [...]},
    "BK7": {"B": [...], "C": [...]},
    "Silice": {"B": [...], "C": [...]},
    "Flint F2": {"B": [...], "C": [...]},
}


def sellmeier_index(material_name, wavelength_nm):
    coeffs = SELLMEIER_DATABASE[material_name]
    wavelength_um = float(wavelength_nm) * 1e-3
    lambda2 = wavelength_um ** 2

    n2 = 1.0
    for Bi, Ci in zip(coeffs["B"], coeffs["C"]):
        n2 += Bi * lambda2 / (lambda2 - Ci)

    return float(np.sqrt(max(n2, 1.0)))

Le code convertit explicitement les nanomètres en micromètres, car les coefficients de Sellmeier usuels sont généralement donnés avec λ\lambda en micromètres.

Le choix max(n2, 1.0) est une protection numérique. Dans le domaine visible et pour les matériaux prévus, la loi donne des valeurs physiques. Mais cette borne évite qu’une situation limite, une longueur d’onde hors domaine ou une singularité proche ne fasse apparaître un indice imaginaire dans un simulateur qui ne gère pas les constantes optiques complexes.

29.10. Filtres spectraux : deux conventions selon le régime

Les filtres colorés sont scalaires : ils multiplient l’intensité spectrale sans agir directement sur la polarisation. Dans le cœur spectral, ils sont modélisés par une gaussienne lisse.

def filter_transmission(device, wavelength_nm):
    center = device.filter_center_nm
    width = max(device.filter_width_nm, 1e-6)
    peak = np.clip(device.filter_peak_transmission, 0.0, 1.0)
    transmission = peak * np.exp(-0.5 * ((wavelength_nm - center) / width) ** 2)
    return float(np.clip(transmission, 0.0, 1.0))

Cependant, pour une source strictement monochromatique, l’interface utilise une convention plus intuitive : un filtre est traité comme un passe-bande net. Si la longueur d’onde unique est hors de la bande, l’onde est éteinte. Cette règle est appliquée dans la couche d’orchestration, sans modifier le modèle spectral général.

def monochromatic_filter_transmission(device, wavelength_nm):
    center = device.filter_center_nm
    half_width = max(device.filter_width_nm, 1e-6)

    if abs(wavelength_nm - center) > half_width:
        return 0.0

    return device.filter_peak_transmission


def filter_transmission_for_source(device, wavelength_nm, source_name):
    if source_name == "Monochromatique":
        return monochromatic_filter_transmission(device, wavelength_nm)

    return filter_transmission(device, wavelength_nm)

Ce découplage évite une erreur de modélisation : il ne fallait pas remplacer le filtre gaussien partout, car les sources spectrales doivent conserver une transmission continue. Le comportement passe-bande net est une convention d’affichage et de manipulation pour le cas d’une onde unique.

29.11. Sources spectrales et grille visible

Le module spectral travaille sur une grille visible, typiquement de 380 nm à 780 nm. Chaque source est une fonction qui renvoie une intensité relative sur cette grille.

Extrait :

def make_wavelength_grid(lambda_min=380.0, lambda_max=780.0, n_points=801):
    return np.linspace(lambda_min, lambda_max, n_points)


def source_custom(wavelengths_nm, lines=None, background=0.0):
    spectrum = np.full_like(wavelengths_nm, float(background), dtype=float)

    for lambda0, amp, width in lines:
        spectrum += gaussian(wavelengths_nm, lambda0, width, amplitude=amp)

    return normalize_spectrum(spectrum)

Le choix d’une grille discrète est évidemment un compromis. Une grille de quelques centaines de points est suffisante pour visualiser les sources, appliquer des filtres, repérer des raies et calculer une couleur approximative.

La normalisation est appliquée à la plupart des sources. Elle signifie que les spectres sont utilisés comme profils relatifs, pas comme puissances absolues. Cette convention est cohérente avec l’objectif pédagogique du simulateur : comparer des formes spectrales, des transmissions et des couleurs plutôt que prédire des flux photométriques calibrés.

29.12. Calcul spectral Jones

Le calcul spectral Jones consiste à répéter un calcul monochromatique pour chaque longueur d’onde de la grille.

def compute_transmission_spectrum(state, wavelengths_nm):
    transmissions = np.zeros_like(wavelengths_nm, dtype=float)

    for i, wavelength_nm in enumerate(wavelengths_nm):
        transmissions[i] = compute_transmission_for_wavelength(state, wavelength_nm)

    return np.clip(transmissions, 0.0, None)


def compute_output_spectrum(state, source_name, wavelengths_nm=None, ...):
    source = get_source_spectrum(source_name, wavelengths_nm, ...)
    transmission = compute_transmission_spectrum(state, wavelengths_nm)
    output = source * transmission

    return {
        "wavelengths_nm": wavelengths_nm,
        "source": source,
        "transmission": transmission,
        "output": output,
    }

Cette structure est volontairement directe : source, transmission, sortie. Elle permet de vérifier facilement la logique dans la fenêtre de spectre. Si un filtre rouge est ajouté, la transmission spectrale doit changer ; si un polariseur éteint une polarisation cohérente, la transmission doit également refléter l’action de la matrice Jones.

29.13. Calcul spectral Mueller

En mode Mueller, le calcul spectral doit suivre non seulement l’intensité, mais aussi l’état de Stokes à chaque longueur d’onde.

def compute_mueller_output_for_wavelength(
    state,
    source_intensity,
    wavelength_nm,
    polarization_mode,
    degree,
):
    S_in = source_stokes_vector(
        intensity=source_intensity,
        polarization_mode=polarization_mode,
        degree=degree,
    )

    non_filter_devices = [dev for dev in state.devices if not is_filter_device(dev)]
    S_out, M_total = propagate_stokes_through_devices(
        S_in,
        non_filter_devices,
        wavelength_nm=wavelength_nm,
    )

    filt_T = spectral_filter_chain_transmission(state, wavelength_nm)
    S_out = np.asarray(S_out, dtype=float).copy()
    S_out *= filt_T

    return S_in, S_out, M_total

Le code sépare les filtres des dispositifs Mueller. C’est cohérent avec le modèle physique : les filtres colorés sont scalaires, tandis que les dispositifs Mueller agissent sur le vecteur de Stokes. La multiplication finale par filt_T applique la transmission spectrale à toutes les composantes de Stokes. Elle modifie l’intensité et les contributions spectrales, mais ne crée pas à elle seule de polarisation.

Le calcul complet stocke ensuite pour chaque longueur d’onde :

  • le spectre source ;
  • le spectre de sortie ;
  • la transmission ;
  • les vecteurs de Stokes d’entrée et de sortie ;
  • le degré de polarisation d’entrée et de sortie ;
  • la longueur de cohérence estimée associée à la source.
for i, wavelength_nm in enumerate(wavelengths_nm):
    S_in, S_out, _ = compute_mueller_output_for_wavelength(...)

    stokes_in[i, :] = S_in
    stokes_out[i, :] = S_out
    output[i] = max(S_out[0], 0.0)

    if S_in[0] > 1e-12:
        transmission[i] = output[i] / S_in[0]

    dop_in[i] = degree_of_polarization(S_in)
    dop_out[i] = degree_of_polarization(S_out)

Ce choix rend possible la dernière figure de l’analyse de polarisation : source, sortie, transmission et DOP sont calculées sur la même grille de longueurs d’onde.

29.14. Matrice de cohérence comme couche intermédiaire

Le module coherence_matrix.py sert de pont entre Jones et Stokes. Il permet de représenter les états purs, partiellement polarisés et non polarisés avec le même objet mathématique : une matrice hermitienne positive.

Extraits représentatifs :

def stokes_to_coherency(S):
    S0, S1, S2, S3 = np.asarray(S, dtype=float)
    return 0.5 * np.array(
        [[S0 + S1, S2 + 1j * S3],
         [S2 - 1j * S3, S0 - S1]],
        dtype=complex,
    )


def coherency_to_stokes(J):
    J = ensure_hermitian(J)
    S0 = np.real(J[0, 0] + J[1, 1])
    S1 = np.real(J[0, 0] - J[1, 1])
    S2 = np.real(J[0, 1] + J[1, 0])
    S3 = np.real(-1j * (J[0, 1] - J[1, 0]))
    return np.array([S0, S1, S2, S3], dtype=float)

Cette couche est utile pour deux raisons. D’abord, elle permet de convertir proprement entre les formalismes. Ensuite, elle offre un endroit naturel pour imposer des contraintes physiques : hermitianité, positivité, degré de polarisation borné.

Le code peut ainsi appliquer un dispositif Mueller à une matrice de cohérence en passant par Stokes, puis revenir à une matrice de cohérence :

def apply_mueller_matrix_to_coherency(J, M):
    S = coherency_to_stokes(J)
    S_out = np.asarray(M, dtype=float) @ S
    return stokes_to_coherency(normalize_stokes(S_out))

Ce choix reflète la logique physique : les matrices de Mueller transforment des observables réelles de Stokes, tandis que la matrice de cohérence garde une représentation compacte de l’état statistique.

29.15. Normalisation et projection physique

Plusieurs fonctions empêchent le moteur de produire des états non physiques lors des manipulations interactives. La plus simple est la normalisation du vecteur de Stokes.

def normalize_stokes(S):
    S = np.asarray(S, dtype=float)

    if S[0] <= 1e-12:
        return np.zeros(4, dtype=float)

    out = S.copy()
    dop = np.sqrt(out[1]**2 + out[2]**2 + out[3]**2)

    if dop > out[0]:
        out[1:] *= out[0] / dop

    return out

Cette fonction encode directement la contrainte physique

S12+S22+S32S0.\sqrt{S_1^2+S_2^2+S_3^2}\leq S_0.

Elle est nécessaire car certains enchaînements numériques, arrondis ou paramètres extrêmes peuvent produire un vecteur légèrement hors domaine. Plutôt que de laisser cette erreur contaminer la visualisation, le code projette l’état vers un domaine admissible.

La même philosophie se retrouve dans les fonctions de cohérence : hermitianiser une matrice, clipper ses valeurs propres, éviter les divisions par zéro, borner les transmissions entre 0 et 1. Ces protections ne changent pas le modèle physique voulu ; elles rendent le simulateur manipulable en temps réel.

29.16. Orchestration de /simulate

L’endpoint /simulate est le point d’entrée principal. Il ne contient pas toute la physique, mais il orchestre les appels importants : état, spectre, longueur d’onde effective, champ complexe, Stokes, phase et sortie formatée.

Extrait simplifié :

@app.post("/simulate", response_model=SimulationOutput)
def simulate(payload: SimulationInput):
    state = build_state(payload)
    source_data = compute_source_data(state, payload)

    effective_wavelength_nm = source_data["effective_wavelength_nm"]
    state.wave.wavelength_nm = effective_wavelength_nm

    z = np.linspace(payload.zMin, payload.zMax, payload.nPoints)

    Ax, Ay, A_in, A_out, _ = compute_chain_complex_amplitudes(
        state,
        z,
        effective_wavelength_nm,
        payload.source.name,
    )

    phase = compute_phase_array(state, z, effective_wavelength_nm)
    Ex = np.real(Ax * np.exp(1j * phase))
    Ey = np.real(Ay * np.exp(1j * phase))

    return SimulationOutput(...)

La longueur d’onde effective est un élément clé. Pour une source monochromatique, elle est la longueur d’onde réelle. Pour une source spectrale, elle sert à construire une onde affichable dans la scène Jones ou à choisir une échelle représentative. Les spectres, eux, restent calculés sur toute la grille visible.

Le backend renvoie à la fois :

  • des tableaux pour la scène principale ;
  • des grandeurs scalaires pour les badges et métriques ;
  • les spectres complets pour la fenêtre spectrale ;
  • les régions de couleur ;
  • les composantes réalistes en mode Mueller ;
  • les données nécessaires aux fenêtres d’analyse.

Le frontend n’a donc pas besoin de recalculer la physique principale. Il reçoit une structure riche et l’utilise pour afficher différents points de vue sur le même calcul.

29.17. Calcul des données de source et de sortie

La fonction compute_source_data est l’un des nœuds principaux du backend. Elle choisit entre le calcul Jones spectral et le calcul Mueller spectral, applique les conventions particulières, résume le spectre et prépare les données visuelles.

Extrait simplifié :

def compute_source_data(state, payload):
    wavelengths_nm = make_wavelength_grid(380.0, 780.0, 801)

    if is_mueller_mode(payload):
        spectral_result = compute_output_spectrum_mueller(...)
    else:
        spectral_result = compute_output_spectrum(...)

    if payload.source.name == "Monochromatique":
        # correction passe-bande net pour les filtres monochromatiques
        spectral_result["transmission"] *= correction
        spectral_result["output"] *= correction

    summary = summarize_spectrum_result(spectral_result)
    dominant_wavelength_nm = choose_dominant_wavelength(...)

    if payload.source.name == "Monochromatique":
        effective_wavelength_nm = payload.wave.wavelengthNm
    else:
        effective_wavelength_nm = dominant_wavelength_nm

    return {
        "wavelengths_nm": wavelengths_nm,
        "source": source,
        "output": output,
        "transmission": transmission,
        "region_colors": region_colors,
        "realistic_components": realistic_components,
        "effective_wavelength_nm": effective_wavelength_nm,
    }

Cette fonction matérialise la séparation entre trois longueurs d’onde possibles :

  • la longueur d’onde du slider, pertinente en monochromatique ;
  • les longueurs d’onde de la grille spectrale, pertinentes pour le calcul ;
  • la longueur d’onde effective, pertinente pour certaines représentations visuelles.

La distinction est essentielle pour éviter de faire dire à l’animation plus qu’elle ne peut dire.

29.18. Couleur, RGB et bande spectrale

La conversion spectre → RGB est gérée dans spectral_core.py. Le code ne cherche pas une colorimétrie instrumentale parfaite : il produit une couleur d’affichage cohérente, stable et utile à l’interprétation.

La logique générale est :

def spectrum_to_rgb(wavelengths_nm, spectrum, normalize_brightness=True):
    rgb = np.zeros(3, dtype=float)

    for wavelength_nm, intensity in zip(wavelengths_nm, spectrum):
        rgb += intensity * eye_response_rgb(wavelength_nm)

    if normalize_brightness and np.max(rgb) > 1e-12:
        rgb /= np.max(rgb)

    return np.clip(rgb, 0.0, 1.0)

La fonction spectrum_to_display_rgb_map sert à construire une bande colorée point par point. Elle est utilisée dans la fenêtre spectrale pour montrer non seulement la courbe d’intensité, mais aussi la couleur associée à chaque région du spectre.

Le choix d’un gamma d’affichage est aussi visuel : il rend les faibles intensités plus perceptibles sans changer la physique calculée. La documentation doit donc distinguer la couleur comme outil d’affichage de la transmission comme grandeur calculée.

29.19. Cannelures, contraste et résumé spectral

La fenêtre spectrale affiche aussi des grandeurs synthétiques : transmission globale, contraste et cannelures. Elles sont calculées à partir du spectre de sortie normalisé.

Extrait :

def spectral_contrast(wavelengths_nm, spectrum):
    spectrum = normalize_spectrum(spectrum)
    positive = spectrum[spectrum > 1e-6]

    i_high = np.percentile(positive, 95)
    i_low = np.percentile(positive, 5)

    return float((i_high - i_low) / (i_high + i_low))

Ce contraste n’est pas une définition universelle de laboratoire ; c’est un indicateur robuste pour une interface. L’usage de percentiles évite qu’un seul point aberrant domine la mesure.

Les bandes sombres sont détectées en repérant des zones où le spectre normalisé descend sous un seuil. Le code limite leur largeur pour éviter de qualifier toute une extinction globale de “cannelure”. Là encore, l’objectif est interprétatif : donner à l’utilisateur une information lisible sur la structure du spectre.

29.20. Régions spectrales et couleurs locales

Pour que la scène change correctement après un filtre ou un dispositif, le backend calcule des régions le long du banc. Une région correspond à un intervalle spatial dans lequel les dispositifs déjà traversés sont les mêmes.

La logique est de reconstruire un état partiel :

def compute_region_colors_mueller(state, source_name, wavelengths_nm, ...):
    devices_sorted = sorted(state.devices, key=lambda d: d.z)
    z_boundaries = [state.z_min] + [device.z for device in devices_sorted] + [state.z_max]

    original_devices = state.devices
    regions = []

    try:
        for region_index in range(len(z_boundaries) - 1):
            state.devices = devices_sorted[:region_index]
            result = compute_output_spectrum_mueller(state, source_name, ...)
            rgb = spectrum_to_rgb(result["wavelengths_nm"], result["output"])
            regions.append({"z_start": ..., "z_end": ..., "rgb": rgb})
    finally:
        state.devices = original_devices

    return regions

Le point délicat est la modification temporaire de state.devices. Le code sauvegarde l’état original et le restaure dans un bloc finally. C’est important : sans cette précaution, un calcul régional pourrait modifier l’état global et fausser les calculs suivants.

Cette stratégie est simple mais efficace : elle permet d’utiliser les mêmes fonctions spectrales que pour la sortie globale, en changeant seulement la portion de chaîne optique déjà traversée.

29.21. Sélection des composantes réalistes affichées

En mode Mueller réaliste, afficher les 801 longueurs d’onde de la grille serait illisible. Le backend choisit donc quelques composantes représentatives. Ces composantes ne remplacent pas le spectre complet ; elles sont une projection visuelle.

Chaque composante réaliste contient typiquement :

{
    "wavelength_nm": 550.0,
    "rgb": [0.7, 1.0, 0.2],
    "weight": 0.64,
    "q": 0.1,
    "u": 0.0,
    "v": 0.0,
    "dop": 0.25,
    "z_start": 4.0,
    "z_end": 8.0,
    "coherence_length_um": 12.0,
}

Ces données ne sont pas seulement esthétiques. Le poids règle l’importance visuelle ; le DOP sépare partie ordonnée et halo ; les paramètres de Stokes orientent la composante ; la longueur de cohérence influence les fluctuations visuelles ; les bornes spatiales indiquent dans quelle région la composante est valide.

Le frontend reçoit donc des composantes déjà physiquement annotées. Il n’a pas à deviner si une composante doit être visible après un filtre : cette décision vient du backend.

29.22. Rendu 3D des composantes réalistes

Dans WaveScene.tsx, la fonction de rendu réaliste transforme les composantes en tubes et halos. Elle ne recalcule pas le spectre ; elle utilise les régions et poids transmis par le backend.

Le code choisit d’abord s’il faut afficher les composantes de manière continue ou région par région :

function buildDisplayRealisticComponents(rawComponents, devices) {
  const hasFilterDevice = devices.some((device) => isFilterDeviceName(device.name));

  if (hasFilterDevice) {
    return buildRegionwiseRealisticComponents(rawComponents);
  }

  return buildContinuousRealisticComponents(rawComponents);
}

Ce choix vient d’un compromis visuel : sans filtre, une composante peut rester continue pour éviter un découpage inutile. Avec filtre, il faut respecter les modifications spectrales locales, donc les régions redeviennent essentielles.

Les paramètres de matériau visuel sont calculés à partir du poids, du DOP et de la cohérence :

function materialParamsForRegion(region) {
  const weight = Math.max(0, region.weight);
  const dop = clamp01(region.dop || 0);
  const lc = region.coherenceLengthUm || 5;
  const incoh = 1 / (1 + lc / 10);

  return {
    lineOpacity: 0.22 + 0.62 * weight * Math.max(0.18, dop),
    haloOpacity: 0.025 + 0.34 * weight * (1 - dop) * (0.35 + 0.65 * incoh),
    lineRadius: 0.012 + 0.021 * dop,
    haloRadius: 0.085,
  };
}

Le rayon du halo est volontairement stabilisé. Si le rayon variait fortement d’une région à l’autre, les tubes transparents révéleraient les interfaces. L’intensité du halo varie donc surtout par l’opacité, pas par une géométrie instable.

29.23. Raccord des régions dans la scène 3D

Le découpage par région est nécessaire pour les filtres, mais il peut créer des artefacts visuels aux interfaces. La solution retenue est d’ajouter explicitement les points de frontière par interpolation, plutôt que de couper approximativement sur l’échantillonnage.

Extrait représentatif :

function pointAtZ(zPhysical) {
  const ratio = (zPhysical - Z_MIN) / Math.max(zMax - Z_MIN, 1e-9);
  const floatIndex = ratio * Math.max(N_POINTS - 1, 1);
  const lowerIndex = Math.floor(floatIndex);
  const upperIndex = Math.ceil(floatIndex);

  if (lowerIndex === upperIndex) {
    return points[lowerIndex].clone();
  }

  const localT = floatIndex - lowerIndex;
  return points[lowerIndex].clone().lerp(points[upperIndex], localT);
}

function pointsForRegion(region) {
  const regionPoints = [pointAtZ(region.zStart)];

  points.forEach((point, pointIndex) => {
    const zPhysical = Z_MIN + pointIndex / (N_POINTS - 1) * (zMax - Z_MIN);
    if (zPhysical > region.zStart && zPhysical < region.zEnd) {
      regionPoints.push(point);
    }
  });

  regionPoints.push(pointAtZ(region.zEnd));
  return regionPoints;
}

Cette logique évite deux défauts opposés :

  • un trou si deux tubes s’arrêtent avant de se rejoindre ;
  • une surbrillance si deux tubes transparents se chevauchent trop.

Le raccord exact aux frontières est un choix de rendu, mais il préserve la logique physique : chaque région garde ses propres paramètres sans créer de fausse double intensité.

29.24. Halo non polarisé

Le halo est l’un des éléments les plus délicats du rendu. Il doit représenter une partie statistiquement désordonnée, sans prétendre être un champ électromagnétique aléatoire complet.

La formule de rendu est volontairement phénoménologique :

  • plus le DOP est faible, plus le halo est visible ;
  • plus la cohérence est faible, plus le halo peut être marqué ;
  • le poids spectral module l’opacité ;
  • la couleur reste celle de la composante spectrale.

Le halo ne doit pas devenir un simple brouillard global. Il doit rester localement modifiable, notamment après un filtre spectral ou un dépolariseur spectral. C’est pourquoi la version correcte du rendu conserve le découpage par région et ne dessine pas un halo continu unique sur tout le banc lorsque les paramètres changent localement.

29.25. Animation temporelle et synchronisation

Le frontend anime la scène à partir des données numériques reçues. Il ne redemande pas une simulation complète à chaque frame. Cette séparation permet d’avoir une animation fluide : le backend calcule l’état, le frontend fait évoluer le temps.

La synchronisation a été un point sensible. Certaines options d’affichage ne doivent pas réinitialiser l’horloge interne, sinon les composantes réaffichées deviennent déphasées par rapport à l’onde totale. La logique correcte est donc :

  • recalculer la géométrie si les données physiques changent ;
  • ne pas remettre le temps à zéro pour une simple option d’affichage ;
  • conserver une horloge commune pour l’onde totale, les composantes et les trajectoires.

Ce choix explique pourquoi le code distingue soigneusement les dépendances physiques des dépendances purement visuelles.

29.26. Sonde locale

La sonde locale demande au backend d’évaluer l’état du système à une position donnée. Elle n’est pas une lecture directe de la scène 3D ; c’est un calcul physique local.

La logique est :

  1. séparer les dispositifs déjà traversés et les dispositifs restants ;
  2. déterminer le milieu local ;
  3. calculer l’état Jones ou Mueller correspondant ;
  4. reconstruire les spectres locaux ;
  5. renvoyer une description exploitable par la fenêtre de sonde.

Extrait simplifié :

def optical_state_at_probe(payload, z_probe):
    state = build_state(payload)
    local_devices = [d for d in state.devices if d.z <= z_probe]
    remaining_devices = [d for d in state.devices if d.z > z_probe]

    if is_mueller_mode(payload):
        return local_mueller_stokes_at_probe(payload, local_devices, lambda_eff_nm)

    return local_jones_stokes_at_probe(state, payload, local_devices, lambda_eff_nm)

La sonde est donc utile pour vérifier la cohérence du rendu. Si la scène montre une modification après un dispositif, la sonde doit retrouver cette modification dans les grandeurs locales.

29.27. Détails mathématiques

La fenêtre des détails mathématiques est construite côté backend pour éviter de dupliquer les calculs dans le frontend. Elle utilise les mêmes matrices et vecteurs que la simulation principale, puis les formate en texte structuré.

Cette fenêtre remplit plusieurs fonctions :

  • vérifier la matrice Jones ou Mueller totale ;
  • afficher les états intermédiaires ;
  • expliciter les paramètres spectraux ;
  • rappeler la longueur de cohérence estimée ;
  • rendre visible la différence entre un calcul cohérent et un calcul statistique.

Les fonctions comme matrix_to_text, stokes_to_text ou coherence_matrix_to_text ne sont pas physiquement centrales, mais elles sont importantes pour la traçabilité. Elles évitent que les fenêtres d’analyse présentent des résultats décoratifs sans lien clair avec les objets numériques réels.

29.28. Analyse de polarisation

L’analyse de polarisation est construite à partir de la même chaîne de dispositifs. En mode Jones, elle suit les amplitudes et la phase relative. En mode Mueller, elle suit les intensités, les vecteurs de Stokes et le DOP.

Le code normalise les intensités par rapport au maximum observé dans la chaîne, afin de rendre les graphes comparables :

def normalize_stage_intensities(stages):
    max_intensity = max(abs(stage["stokes"][0]) for stage in stages)

    return [
        PolarizationStageOutput(
            label=stage["label"],
            intensityRelative=stage["stokes"][0] / max_intensity,
            dop=degree_of_polarization(stage["stokes"]),
            stokes=list(stage["stokes"]),
        )
        for stage in stages
    ]

Cette normalisation est un choix d’affichage. Elle ne remplace pas les intensités physiques utilisées dans le calcul spectral, mais elle rend les barres et courbes lisibles dans une fenêtre d’analyse.

29.29. Expériences préétablies, expériences guidées et configuration

Les expériences préétablies sont des états complets du simulateur : source, polarisation, dispositifs, milieux, options d’affichage. Les expériences guidées ajoutent un texte d’accompagnement : objectif, étapes, observations attendues et outils utiles.

Le principe est important : une expérience guidée ne doit pas introduire une physique parallèle. Elle doit charger une configuration standard, puis orienter l’utilisateur vers les bons phénomènes.

L’import/export suit la même logique. Le fichier sauvegardé contient les paramètres nécessaires pour reconstruire le montage, pas une capture visuelle. Il inclut notamment :

type SimulatorConfigFile = {
  version: 1;
  wave: { Ex0: number; Ey0: number; phiDeg: number; wavelengthNm: number };
  source: SourceState;
  polarization: PolarizationState;
  devices: Device[];
  media: Medium[];
  displayOptions: DisplayOptions;
};

Cette structure donne une base de reproductibilité. Une configuration peut être partagée, rechargée et analysée avec les mêmes outils.

29.30. Gestion des modes dans l’interface

L’interface ne montre pas tous les contrôles en permanence. Ce n’est pas seulement une question d’ergonomie ; c’est aussi un choix de cohérence physique.

Exemples :

  • le choix Jones/Mueller n’est pas pertinent pour une source strictement monochromatique dans la logique actuelle ;
  • les dispositifs Mueller avancés disparaissent en mode Jones ;
  • le slider de longueur d’onde disparaît pour les sources spectrales ;
  • certains angles sont masqués pour les dispositifs isotropes ;
  • les outils de cohérence sont réservés au mode réaliste lorsque leur interprétation est pertinente.

Cette logique évite de proposer des combinaisons qui seraient calculables informatiquement mais trompeuses physiquement.

29.31. API et séparation des endpoints

Le backend expose plusieurs endpoints, chacun associé à un niveau d’analyse différent :

  • /simulate : état global de la scène et grandeurs principales ;
  • /probe : état local à une position donnée ;
  • /math-details : texte mathématique structuré ;
  • /health : vérification simple du backend.

Cette séparation évite que l’endpoint principal devienne surchargé. La scène principale n’a pas besoin de recevoir tout le texte mathématique détaillé à chaque recalcul, et la sonde locale n’a pas besoin de recalculer toute la scène visuelle.

C’est aussi un choix de performance : chaque fenêtre d’outil peut demander seulement les données dont elle a besoin.

29.32. Choix de performance

Plusieurs décisions sont prises pour maintenir une interaction fluide :

  • grille spectrale discrète mais raisonnable ;
  • nombre limité de composantes réalistes affichées ;
  • calcul backend déclenché sur changement d’état, pas à chaque frame ;
  • animation temporelle côté frontend ;
  • géométries Three.js reconstruites avec un taux limité ;
  • rendu des composantes réalistes sous forme de tubes plutôt que de surfaces complexes ;
  • séparation entre onde totale, composantes, halo et trajectoires.

Ces choix sont liés. Si l’on augmentait fortement le nombre de composantes affichées, il faudrait réduire autre chose : résolution des tubes, fréquence de reconstruction ou complexité des halos. Le simulateur est donc conçu comme un équilibre entre lisibilité physique et coût graphique.

29.33. Limites structurelles du moteur actuel

L’architecture actuelle est adaptée à un simulateur pédagogique interactif, mais elle n’est pas celle d’un solveur électromagnétique général.

Limites principales :

  • les champs sont propagés selon un axe optique privilégié ;
  • les dispositifs sont modélisés par matrices ou transmissions phénoménologiques ;
  • les matériaux n’ont pas d’indice complexe complet ;
  • les effets de réflexion multiple ne sont pas modélisés ;
  • la cohérence spatiale complète n’est pas suivie ;
  • la scène réaliste est une représentation interprétative de grandeurs statistiques.

Ces limites ne sont pas des défauts accidentels : elles définissent le domaine du programme. Le code garde les structures physiques essentielles tout en restant assez léger pour une interface web interactive.

29.34. Pourquoi cette architecture reste extensible

L’intérêt de cette organisation est que l’on peut ajouter des fonctionnalités sans réécrire tout le simulateur.

Quelques exemples :

  • une nouvelle source se place dans spectral_core.py, puis dans le sélecteur frontend ;
  • un nouveau matériau se place dans la base Sellmeier et dans la liste des milieux ;
  • un nouveau dispositif Jones se définit par une matrice Jones et un cas de dispatch ;
  • un nouveau dispositif Mueller se définit par une matrice Mueller et un cas de dispatch ;
  • un nouvel outil d’analyse peut appeler les mêmes données que /simulate ou demander un endpoint spécialisé ;
  • une courbe expérimentale de filtre pourrait remplacer la gaussienne sans changer la logique générale de la chaîne.

Cette extensibilité repose sur une règle : les ajouts doivent respecter la séparation entre état, calcul, analyse et visualisation. Lorsque cette séparation est conservée, le simulateur peut évoluer sans que chaque nouvelle fonction casse l’ensemble.