Näherungsverfahren

Bearbeiten

Meist ist die Schrödinger-Gleichung nicht in geschlossener Form lösbar, wenn Energien von gebundenen Zuständen bei Molekülen usw. gesucht sind. Dann kommen Approximationsverfahren zum Einsatz, die mehr oder weniger mit der Brechstange sich von bekannten Lösungen mit einfachen Hamilton-Operatoren vorarbeiten zu den ungefähren Lösungen mit komplizierteren Operatoren. Kurze Einführung zur Störungsrechnung und Variationsrechnung. (Nur viel weitergehende Abhandlungen könnten sich mit dem Namen „Störungstheorie“ schmücken.)

Algorithmus Störungsrechnung

Bearbeiten

Angefangen sei mit einem Hamilton-Operator   und dessem orthonormalen vollständigen System von Eigenfunktionen  . Dann wird mit dem Faktor t eine Störung dazu geschaltet  . Polynome in t dienen hier zum Sortieren nach der Ordnung. Die Eigenvektoren und Eigenwerte variieren bis zur zweiten Ordnung.

 

Die Terme von Ordnung t liefern:  .

Das Skalarprodukt mit   macht ein   mit dem hermiteschen  . Es bleibt übrig   weil die   normiert sind. Ergebnis: In erster Ordnung ist der korrigierte Energie-Eigenwert gleich dem alten plus dem Erwartungswert des Stör-Operators   mit dem alten Eigenvektor.

Das Skalarprodukt mit einem anderen ungestörten   bringt:

 

Bei Entartung   ist das grundfalsch! Denn die Matrixelemente von   verschwinden nicht. Um doch die Konsistenz der Näherung zu sichern, macht man erst die hermitesche Matrix   aus all den  , die den entarteten Teilraum zum selben Energie-Eigenwert   aufspannen. Man diagonalisiert diese Matrix, was eine neue orthonormale Teilbasis mit   hervorbringt. Mit den   wird jede gefährliche Gleichung weggeschaltet und man rechnet mit diesen weiter.

Ohne Entartung gilt:  . Nun soll die Bedingung ausgewertet werden, dass   normiert sei:

 

woraus folgt:  . Jetzt nutzt man aus, dass die komplexe Phase von   beliebig wählbar ist, ohne die Orthonormalität und die erste Energiekorrektur zu verändern. Die Phase sei so gedreht, dass auch   gilt. Mit   ist dann erreicht, dass solche Paare orthogonal sind. Mit dem Orthonormalsystem zu   folgt dann die Näherungsformel für  :

 

Dies ist die Entwicklung der Vektor-Störung in der ungestörten Basis.

Die Korrekturen der Ordnung  , genannt   und  , sind nun fällig.

 

Hierin nach erster Ordnung:

 

Das Skalarprodukt der zweiten Ordnung mit   macht  , was wegen Hermitizität mit   herausfällt. Daher folgt:

 

Der letzte Term wurde oben mittels Phasendrehung zu Null wegdiskutiert. Resultat nach Einsetzen der Entwicklung von  :

 

Während die erste Energie-Korrektur nur den Eigenzustand selbst berührt, bezieht die zweite bevorzugt alle Nachbarzustände mit ein. Wenn   der Grundzustand war,  , ist diese zweite Korrektur negativ.

In einem Zwei-Niveau-System sind die zweiten Korrekturen gegenläufig:

 
 

Die unendlichen Summen der zweiten Korrektur geben nicht selten Konvergenz-Probleme auf, so etwas wie Infrarot- oder Ultraviolett-Katastrophen.

Algorithmus Variationsrechnung

Bearbeiten

Diese Berechnung ist vor allem gebrächlich für Grundzustände von Molekülen. Die kleinste Energie ist das absolute Minimum aller Erwartungswerte des Hamiltonoperators. Beweis. Sei ein beliebiger Zustand im orthonormalen Eigensystem des Operators H entwickelt,

 

dann ist sein Erwartungswert

 

größer oder gleich   wo  .

Angemerkt sei noch, dass die Eigenwerte von H Extrema oder stationäre Punkte von Erwartungswerten   sind, wenn F beliebige Kurven F(z) beschreibt. Man setze dF/dz = f(z). Wenn H F= E F (H hermitesch), verschwindet dieses f:

 
 .

Die Strategie des Variations-Algorithmus ist, eine Familie von Test-Wellenfunktionen F(p) zu erzeugen mit n freien Parametern. Es wird das Minimum der Erwartungswerte von   im Parameter-Raum gesucht. Damit wird bei richtiger Wahl der Grundzustand gut angenähert.

In der einfachsten Variante sind die Testfunktionen auf einer Hyperebene; sie hängen linear von n Koordinaten ab. Die Berechnung des Minimums führt (mit einem Lagrange-Faktor der Normierung?) auf Eigenwert-Gleichungen. Man sucht die kürzste Achse eines Ellipsoids in n Dimensionen. Es geht auch ohne orthonormierte Vektoren, wie ein Beispiel unten zeigt.

In anderen Varianten wird sowohl ein hermitesches H wie auch Vektor F längs einer Kurve beispielsweise so variiert   dass F immer Eigenvektor bleibt:  .

Wenn auch noch die Normierungs-Bedingung   greift, dann folgt die Hellmann-Feynman-Gleichung

 
 
 

denn die Klammer =   nach Voraussetzung.

Es können auch andere als Grundzustände berechnet werden, wenn man Quantenzahlen direkt einbaut. Um zum Beispiel Zustände im Zentralpotenzial mit Drehimpuls   zu erzwingen, bekommt der Satz Testfunktionen die Winkelanteile   verpasst.

Anharmonischer Oszillator

Bearbeiten

Der Potenzial-Topf   eines Oszillators sei verformt mit einem unsymmetrischen Anteil in der dritten Potenz der Ortskordinate.

 
 

Die Störung hat drei Zutaten: den dimensionslosen Parameter s, die Referenz-Energie   und Referenz-Länge  .

Eine klassisches Pendel mit der Dynamik   beschreibt eine verformte Sinus-Schwingung, hat also Oberwellen im Frequenz-Spektrum. Auch die Grundfrequenz fällt als Funktion der Amplitude ab. Quantenmechanisch speisen wir den   Anteil in die Störungsrechnung ein und schätzen die Korrekturen an der Folge der Niveaus, die für den ungestörten harmonischen Oszillator den gleichen Abstand haben.

Formeln des Harmonischen Oszillators mit der Basis |n> und Leiter-Operatoren:

Normiert:  
 
 

Operatoren   sind dimensionslos. x hat die Referenzlänge  :

 

Mit   und  :

 

  ändert die Quantenzahl n um {-3,-1,1,3}. Er hat folgende Matrixelemente, die von den 4 Termen in ihrer Reihenfolge abstammen, sowie Energiedifferenzen:

 
 
 
 

Es kommen nur Energiekorrekturen von zweiter Ordnung   vor. Es folgt nach fleißigem Ausrechnen mit der Energie-Formel der Störungsrechnung:

 
 

Die Niveaus haben einen ungleichen Abstand, der enger wird mit steigendem n. In der Tat zeigen die Vibrations-Spektren von zweiatomigen polaren Molekülen Absorptions-Linien, die solchen Reihen von Energieniveaus entsprechen. Es gibt dafür Modelle des Potenzials vom Typ Lennard-Jones:  .

Helium-Atom Grundzustand

Bearbeiten

In der vereinfachten Schrödinger-Gleichung gibt es zwei Elektronen an Orten  , ein ruhendes Zentrum mit anziehendem Potenzial   und das Abstoßungs-Potenzial der zwei Elektronen  .

Mit  :

 

Der gesamte Hamilton-Operator ist invariant unter der Vertauschung von  . Also will man Lösungen, die auch Eigenfunktionen des hermiteschen Vertausch-Operators sind. Konkret, symmetrische und antisymmetrische Funktionen,

 .

Wie anderswo im Buch steht, benötigen Elektronenpaare antisymmetrische Wellen, aber auch Spin-Freiheitsgrade. Die Forderung ist erfüllt, wenn bei symmetrischem Orts-Anteil die Spin-Faktoren antisymmetrisch sind und umgekehrt. Im Grundzustand kombinieren sich die Spins zum antisymmetrischen Singulett-Zustand mit Spin Null. Daher nimmt dieses Beispiel ein symmetrisches Paar von Ortsfunktionen an.

In nullter Ordnung setzt man U zu Null und nimmt simple Produkte von bekannten Wasserstoff-Atom-Lösungen:

 .

Das niedrigste Niveau 1s,1s hat mit   eine symmetrische Funktion.   13,6 eV; a ist der Bohr-Radius.

Die erste Korrektur zur Energie ist dann nach dem Störungs-Algorithmus:

  = 34 eV.

Das ergibt -75 eV für die totale Ionisierung des Heliums, verglichen mit experimentell -79 eV.

Danach wird eine Variation errechnet, die eine bessere Näherung von -77,5 eV ergibt. Die Funktion g erhält einen freien Parameter s, der die Abschirmung der Zentralladung durch das Partner-Elektron simuliert.   mit Z ersetzt durch (Z-x). Der Parameter x wird eingestellt auf das Minimum des Erwartungswerts E(x) des Gesamt-H-Operators.

Ergebnis:   -77,5 eV

Einzelheiten der Rechenschritte:

Funktionen   haben das Integral

  wenn n keine ganze Zahl ist).
 .

Es folgt  

Die Funktion   hat das Volumenintegral  .

Die Funktion   hat die Quadratintegral-Norm   (setze b=a/2 in voriger Formel).

Diese Funktion g ist der Wasserstoff-Grundzustand mit a= Bohr-Radius, normiert also:  .

Es gibt eine Entwickung von   in Kugelfunktionen:

Sei   der Winkel zwischen beiden Richtungen.

 

Das Legendre-Polynom   kann weiter in   mit absoluten Winkeln zerlegt werden, wenn es sein muss:

 

Die Abstoßung zweier 1s Elektronenwolken ist also ein Matrixelement mit dem Integral vom Typ:

 

wo  . Bei der Entwicklung des Coulomb-Terms in   bleibt nur   übrig, denn die Wellen-Integranden hängen nur von Radien ab,   ist konstant 1 und alle übrigen Integrale verschwinden, da sie orthogonal zu diesem   sind. Die Winkelintegration über zwei Kugeln bringt den Faktor   mit. Die Integration über die zwei Radien sieht daher so aus:

 

Alle Integrale sind einfach aber zeitraubend für Langsamrechner, Ergebnis:  .

Im Folgenden werden die Energien bezogen auf den Wasserstoff-Grundzustand  , sowie Längen auf den Bohr-Radius  . Mit einer Kernladung (Ze) hat der Ein-Elektron-Zustand die Energie   und Radius  . Der Faktor   in Coulomb-Termen kann als   kodiert werden.

Der Helium-Grundzustand hat Energie 2K(Z) von Kernbindung der zwei Elektronen, plus der Elektron-Elektron-Abstoßung A(Z), auzuwerten als das Matrixelement  . Das führt auf obiges Integral mal  , geteilt durch die Normierung:

 .

Damit das geschätzte Energieniveau:  .

Die zwei Elektronen schirmen eines fürs andere die Kernladung teilweise ab. Daher ist zu erwarten, dass eine Variation der 1s-Wellen zu einer kleineren Ladung Y < Z, das heißt zu größerem Radius a(Y), einen besseren, niedrigeren Erwartungswert für den Grundzustand aufweist. Die Welle zu Y ist aber keine Eigenfunktion mehr zur wahren Kernladung Z, daher muss der Erwartungswert von   für   neu berechnet werden. Energien werden wieder geteilt durch die Referenz-Energie E_0.

Es gilt:  . Es folgt der Coulombterm:  .

In den kinetischen Term geht ein die Mittelung über den Laplace-Operator

 

Dies macht mit dem Integral von vorhin =  . Geteilt durch   kommt dimensionslos heraus:  .

Ersetzt man noch a=a(Y) durch das dimensionslose  , ergibt sich das einfache Modell für die Bindungsenergie zum Kern, pro Elektron:  . Andererseits gilt von vorhin der Kalkül der Abstoßung:  .

Die Variation besteht nun darin, Y = Z-x mit variablem x bis zum Minimum der Gesamtenergie herunterzufahren.

 
Ergebnis fürs Mimimum:  .

Die Variationsrechnung verbessert also die Energie der ersten Störungs-Näherung. Diese bleibt aber immer noch über der experimentellen, ganz wie es theoretisch erwartet wird.

Wasserstoff-Bindung

Bearbeiten

Das einfachste System, das eine Vorstufe für ein Molekül darstellt, ist das Ion aus zwei Protonen und einem Elektron, das   - Ion. Mit der Variationsmethode wird versucht zu zeigen, dass es einen optimalen Abstand R zwischen den Protonen gibt, wo sie vom gemeinsamen Elektron gebunden werden. Das heißt, verglichen mit dem Grundzustand von Wasserstoff-Atom plus weit entferntem Proton, ist da der Energie-Erwartungswert niedriger, wenn die Wellenfunktion geeignet überlagert wird aus denen, die beim einen oder anderen Kern konzentriert sind. Schon die einfache beste Linearkombination aus den zwei 1s-Zuständen mit Bohr-Radius führt zu einem Energie-Minimum, wenn R variiert wird. Eine verbesserte Rechnung mit zwei variablen Parametern, Abstand R und Radius der Elektronenwolken, ergibt schon den experimentell richtigen Wert des Protonen-Abstands.

Das angehängte Python-Skript macht eine rudimentäre Grafik-Datei, wo das Energie-Minimum (mit der besseren Variation) klar beim Proton-Abstand von zwei Bohr-Radien liegt. Die Superposition aus zwei Wellen produziert zwei Eigenzustände, den bindenden und den anti-bindenden, der auch gezeigt ist.

In dieser Näherung sind die Protonen unbeweglich. Nur das eine Elektron kommt in die Wellenfunktion. Man denke sich einen trägen Faktor für die Protonen, deren Freiheitsgrade abgespalten wurden von der theoretisch korrekten Dreiteilchen-Wellenfunktion.

Die Versuchsfunktion ist die Superposition von zwei Wasserstoff-1s-Wellen  , je an einem der zwei Kerne. Im ersten Anlauf wird nur der Abstand der Protonen variiert und der niedrigste Energie-Erwartungswert gesucht. Im verbesserten Schema wird noch der Bohr-Radius der 1s-Wellen geschrumpft, bei jedem Abstand optimal. Denn wenn die Protonen sich nahe kommen, sollte man vom Elektron einen ähnlichen Zustand wie an einem Helium-Kern erwarten, also mit kleinerem Radius.

Durchgeführt wird die Variationsrechnung mit nicht-orthonormierter Basis. Gegeben, linear unabhängige   und ihre Skalarpordukt-Matrix  .

Gesucht sind Linearkombinationen  , die Eigenvektoren des symmetrischen (oder hermiteschen) H-operators sein sollen. Der kleinste der Eigenwerte wird der gesuchte Minimal-Erwartungswert E sein.

 . Skalarprodukte mit den f_i ergeben:
 .

Mit Matrix   ist also eine verallgemeinerte Eigenwertgleichung zu lösen:  .

Die Determinante der Matrix ([H]-E[S]) ist ein Polynom in E, dessen Nullstellen die gesuchten Energiewerte sind. Die aufwändige Etappe ist die Integralrechnung, um die Matrizen [S] und [H] herzustellen.

Im vorliegenden Fall sind H-Operator und Testfunktionen reell, die Matrizen symmetrisch, sogar  .

In Dimension 2 wird die Eigenwertgleichung einfach:

 
 
 

Etwas Integralrechnung

Bearbeiten

Von den kartesischen Koordinaten der Welle   wird übergegangen zu elliptischen Koordinaten   mit folgender Definition. Die Protonen sitzen auf der z-Achse bei Punkten   und  .

Nach folgendem Schema bekommt der Punkt   neue Koordinaten  :

  •   sei der Winkel nach Projektion auf die xy-Ebene
  •   die Abstände zu den zwei Zentren A,B
  •   ; wo R der Abstand der zwei Zentren ist.

Die Erfindung von   hat Vorteile bei der Integralrechnung, wenn Faktoren im Integranden um Punkt A zentralsymmetrisch sind und andere um Punkt B. Was sind die Wertebereiche, wie sieht das Integrationsmaß aus?

  •   denn  
  •   denn  
  •  
  •  

(Ausrechnung noch im Mathe-Kapitel nachzuliefern).

Integranden vom Typ   werden nach Einsetzen   zu Summen von Termen des gleichen Typs in u und v. Jeder Term kann entkoppelt über u und v integriert werden.

 
 

Die drei Integranden des Beispiels sind von der angesprochenen Sorte:

 
 
 .

Nach leidiger, langer Rechnerei voller einfacher partieller Integrationen:

 
 
 
  mit  
 
 

Fortsetzung ()-Ion

Bearbeiten

Der Hamilton-Operator enthält das Potenzial von zwei Kernen, die das Elektron anziehen, sowie die Abstoßung der beiden Protonen.

 

Versuchsfunktion:  .

 

Sei ein Proton am Nullpunkt. Hat die Testfunktion eine stationäre Lösung?

 

Natürlich ist f eine Eigenfunktion, genau wenn a der Bohr-Radius ist;  .

Ab hier wird dimensionslos, relativ zur Wasserstoff-Energie E gerechnet. Mit   und   ist alles im Bohr-Radius verpackt:

 

Die Testfunktionen   werden mit allgemeinem Radius q um a herum variiert.

Man nehme   als Basis der Testfunktionen. Dann hat   die Form wie oben, wenn a durch q ersetzt wird. Also wird

 
 

Es ist   das Normquadrat und   das Skalarprodukt von  . Damit ist die Matrix [S] der Variation fertig.

 

Die Matrixelemente des Operators H' benutzen noch den Coulomb-Erwartungswert

 .

Mit all den definierten Ausdrücken   wird als Funktion von 3 Parametern  :

 
 

Das Problem ist dimensionsbefreit: werden R,a,q mit demselben Faktor z skaliert, bleibt die Eigenwertgleichung gleich (  in allen Matrixelementen). Man darf den Bohr-Radius a=1 setzen und bekommt die Energie als Funktion des Proton-Abstands in Einheiten von a. Das Skript setzt a=1, variiert erst R von 1 bis 4 mit q=a; die Protonen durchfahren 1 bis 4 Bohr-Radien Abstand. Dann noch einmal R von 1 bis 4, wo bei jedem Schritt der Wellenradius q probeweise von a auf 0,25a herunterfährt. Die Werte   und   nach obigen Formeln werden gespeichert, eine schnelle unordentliche Grafik entsteht.

Beispiel Spin-Orbit-Kopplung

Bearbeiten

Beispiel Stark-Effekt am Wasserstoffatom

Bearbeiten

Es soll berechnet werden, wie sich die Energie-Niveaus des H-Atoms ändern, wenn ein äußeres homogenes, konstantes elektrisches Feld einwirkt. Das Stör-Potenzial ist  , F bezeichnet die elektrische Feldstaärke. Mit dem Bohr-Radius  . Gebraucht wird also die Matrix eines dimensionslosen Operators   zwischen allen Eigenzusänden des Atoms. Diese haben eine Basis aus den folgenden Wellenfunktionen:

 
 
  in Zugeordneten Laguerre-Polynomen.

Die entarteten Energie-Eigenwerte bilden die Balmer-Folge  .

Das Integral in Kugelkoordinaten:  
Der Stör-Operator in Kugelkoordinaten:  .

Operator   vertauscht mit Drehimpuls-Komponente  , deren Eigenwerte die Quantenzahlen m der Tripel (nlm) der Wasserstoff-Niveaus sind.

Diagonal-Elemente von (z/a) verschwinden, da der Integrand über den Winkel   dann   mal ein Polynomquadrat   ist, also antisymmetrisch um die Mitte   herum. Allgemeiner hat das Produkt zweier   bei gleichem m nur Terme von gerader Potenz, wenn die Differenz der Quantenzahl l gerade ist. Auch dann verschwinden die Matrixelemente von (z/a).

Die erste Ordung ist daher nicht-diagonal und besteht nur aus den Eigenwerten der Stör-Matrix innerhalb der entarteten Mengen {(n,m)=const, l variabel}. Ist ein Unterraum wie (n,m)= (1,0) oder (2,1) oder (3,-2) nur eindimensional, entfällt der Stark-Effekt in erster Ordnung.

Für m=0, n=3 gibt es ohne   drei entartete Zustände  . Jeder Unterraum m=const ist invariant unter   und verschiedene m haben Matrixelemente Null mit  . Wenn   die Entartung aufhebt, dann errechnet sich die erste Ordnung der Aufspaltung durch Diagonalisieren der Matrix, zum Beispiel der 3x3-Matrix von   auf dem Unterraum  .

Das lästige Ausrechnen der Matrixelemente und Normen wurde automatisiert. Ein Python-Skript baut erst die unnormierten Radialfunktionen   und Winkel-Polynome   aus deren Rekursions- Rezepten. Es beherrscht die Arithmetik mit rationalen Zahlen (Paare ganzer Zahlen) und deren Quadratwurzeln (Quadrupel), etwas Manipulation von entsprechenden Polynomen, sowie die nötige Integralrechnung in den Koordinaten Radius und Winkel \theta. Die Normquadrate und Normen der Basisfunktionen werden ausgerechnet. Die Teil-Matrizen des Stör-Operators (z/a) für relevante Paare (n,m), mit normierten Wellen, sind dann das Endergebnis des Skripts. Hier einige:

  • Polynome der Radialfunktionen  , Radial-Normen für a=1.

 

  •   als Polynome von (s,c) mit  . Normen.

 

Die Matrizen M des (z/a)-Operators,  , enthalten die gesuchten Energie- Verschiebungen x als Eigenwerte, also als Nullstellen der Polynome  , die so genannte Säkular-Gleichung.

Matrix n=2 m=0 Liste l= 0 1

 

Matrix n=3 m=0 Liste l= 0 1 2

 

Matrix n=3 m=1 Liste l= 1 2

 

Matrix n=4 m=0 Liste l= 0 1 2 3

 

Matrix n=4 m=1 Liste l= 1 2 3

 

Matrix n=4 m=2 Liste l= 2 3

 

Für die Niveaus n=2,m=0 sind Eigenwerte   der Energie-Verschiebung möglich, gerechnet in der Einheit (qFa). Da   unverändert bleiben, spaltet sich n=2 insgesamt in drei Niveaus mit Abstand 3 Einheiten.

Für   gibt es Verschiebungen von   Einheiten, keine für  . Und für n=3,m=0 erscheinen als Eigenwerte einer 3x3-Matrix,   Einheiten. Insgesamt kann n=3 fünf Niveaus liefern, im Abstand von 4,5 Einheiten.

Für n=4 sieht es nach 7 Niveaus aus; die Eigenwerte der drei berechneten Matrizen liegen bei   Einheiten.

Für die Korrektur zweiter Ordnung etwa an (n,m)=(0,0) bestimmt man die Matrixelemente von   zwischen (0,0,0) und allen anderen (n,l,0) und summiert deren Betragsquadrate, geteilt durch die Energiedifferenz nach der Balmer-Serie; ganz nach Vorschrift. Das numerische Resultat ist sehr klein, während die Aufspaltung erster Ordnung, also zum Beispiel für n=3 in fünf mögliche Niveaus bei hohen elektrischen Feldern durchaus im Spektrum zu sehen ist.

Es gibt eine Rechenmethode in Parabelkoordinaten, die das Wasserstoff-Spektrum statt in den Quantenzahlen (n,l,m) in alternativen Tripeln   ausdrückt. Man fängt ganz von vorne an und erhält eine andere Eigenfunktions-Basis. In einer solchen Matrixdarstellung ist   praktisch diagonal, seine Erwartungswerte sind proportional zu  , wobei  .

Python-Skript

Bearbeiten
# hydrogen atom eigenfunctions and stark effect perturbation

def hasfact(x,z) : return (x>0) and ((x % z)==0)
def div(p,q) : return int(p/q)

def division(p,q) : # to print integer fractions
  def hasfact(x,z) : return (x>0) and ((x % z)==0)
  pn=[2,3,5,7,11,13,17,19,23]; ln= len(pn) # small prime nbrs
  p,q,s = (abs(p),abs(q),-1) if ((p*q)<0) else (abs(p),abs(q),1) 
  for i in range(ln) :
    z=pn[i]
    while hasfact(p,z) and hasfact(q,z) : p=div(p,z); q=div(q,z)
  return s*p,q

''' sphericals
- integ(0,pi) dt sin(t) Pl'm'(cos t) Plm(cos t) = 
-  2/(2n+1) (l+m)!/(l-m)! delta(l,l') delta(m,m')

z= cos(t), x=sin(t). shorten z=c, x=s. 
l Pl(z)=(2l-1) z P(l-1) - (l-1)P(l-2)
Pl=Pl0. recurs Pl(z):= (1/2^l l!) d^l (zz-1)^l
Plm(z,x) = (-)^m x^m d^m Pl(z) where x= sqrt(1-zz)~ sin(t); xx+zz=1
'''

def getprimes(n) : # want n prime numbers
  p=[2]; i=1; plast=2
  while i<=n :
    q=plast+1; found=False
    while not found:
      j=0
      while ((q%p[j])!=0) and ((p[j]*p[j])<=q) :  j +=1
      if (q%p[j] != 0) : p += [q]; plast=q; found=True; i+=1
      else : q+=1
  return p

def greatestdiv(x) : # greatest divisor, skip 0s in list
  n=len(x); y=[0]*n; w=[0]*n; h=0; ymin=-1; gcd=1
  pn=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]; ln= len(pn) # small primes
  for i in range(n) :
    z=abs(x[i])
    if z>0 : ymin= z if(ymin<0) else min(ymin,z); w[h]=z; h+=1
  w=w[:h]; ip=0
  while (ip<ln)and(pn[ip]<=ymin) :
    z=pn[ip]; ok=True
    while ok :
      for i in range(h) : ok= ok and ((w[i]%z)==0)
      if ok : 
        for i in range(h) : w[i]= div(w[i],z)
        ymin= div(ymin,z); gcd *= z
    ip +=1 
  return gcd

def smallestmult(x) : # smallest multiple of nonzero numbers
  n=len(x); y=abs(x[0])
  for i in range(1,n) : 
    z=abs(x[i]); y = y if (z==0) else div(y*z,greatestdiv([y,z]))
  return y

def divide(p,q) : # improve integer fractions
  def hasfact(x,z) : return (x>0) and ((x % z)==0)
  pn=[2,3,5,7,11,13,17,19,23,29,31,37]; ln= len(pn) # small prime nbrs
  p,q,s = (abs(p),abs(q),-1) if ((p*q)<0) else (abs(p),abs(q),1) 
  if p==0 : q=1
  for i in range(ln) :
    z=pn[i]
    while hasfact(p,z) and hasfact(q,z) : p=div(p,z); q=div(q,z)
  return [s*p,q]

def polynscale(u,p,q) : # scale factor on polynomial
  n=len(u); z=[[]]*n
  for i in range(n) : z[i]= divide(p*u[i][0],q*u[i][1])
  return z

def mult(a,b) : # multiply rationals or integers, result rational
  ta=str(type(a)); inta= (ta.find('int')>0)
  tb=str(type(b)); intb= (tb.find('int')>0)
  pa,qa= (a,1) if inta else (a[0],a[1])
  pb,qb= (b,1) if intb else (b[0],b[1])
  return [pa*pb,qa*qb]

def add(a,b) : # add rational or int
  ta=str(type(a)); inta= (ta.find('int')>0)
  tb=str(type(b)); intb= (tb.find('int')>0)
  pa,qa= (a,1) if inta else (a[0],a[1])
  pb,qb= (b,1) if intb else (b[0],b[1])
  return divide(pa*qb+pb*qa,qa*qb)

def polynadd(u,v,a,b) : # rational-coeff polynomial added, with int? factors
  n=len(u); m=len(v); k=max(n,m); w=[[]]*k
  for i in range(k): 
    uu= mult(a,u[i]) if (i<n) else [0,1]
    vv= mult(b,v[i]) if (i<m) else [0,1]
    q=uu[1]*vv[1]; p=uu[0]*vv[1]+vv[0]*uu[1]; w[i]= divide(p,q)
  return w

def polynmul(u,v) : # multiply polynomials
  n=len(u); m=len(v); w=[[]]*(n+m-1)
  for i in range(n) :
    for k in range(m) :
      a=u[i][0];b=u[i][1]; c=v[k][0];d=v[k][1]; z= divide(a*c,b*d); j=i+k
      if w[j]==[] : w[j]=z # first use of j
      else : p=z[0]*w[j][1]+z[1]*w[j][0]; q=z[1]*w[j][1]; w[j]=divide(p,q)
  return w

def polyndiff(u) : # derivative of polynomial
  n=len(u); w=[[]]*(n-1)
  for i in range(1,n) : w[i-1]= [i*u[i][0],u[i][1]]
  return w

def polynpurge(u) : # omit leading zero terms and scale to integers
  # return new polyn and scalefactor applied
  n=len(u); j=n-1; k=0 
  while (u[j][0]==0)and (j>=0) : j -=1
  while (u[k][0]==0)and (k<n) : k+=1
  j +=1; v= u[:j]; p=[0]*j; q=[0]*j; sign= (-1) if (u[k][0]<0) else 1
  for i in range(j) : p[i]= v[i][0]; q[i]=v[i][1]
  z=smallestmult(q) 
  for i in range(j) : r= divide(p[i]*z,q[i]); p[i]=r[0]
  y=greatestdiv(p); 
  for i in range(j) : r=divide(p[i],y); v[i]=[sign*r[0],1]
  return v, divide(z,y)

def present(s,q,mode,noisy) :
  # mode 0 no x, mode 1 complementary x, mode -n const x^n, mode>0 l=mode 
  n=len(q); dmax=1; j= (n-1) if (mode==1) else 0; s+='('
  if mode>0 : j=mode # new
  v= '' if (mode >=0) else ('x'+str(-mode))
  for i in range(n) : dmax= q[i][1] if (q[i][1]>dmax) else dmax
  for i in range(n) :
    a=q[i][0]; b= q[i][1]; bad= (dmax % b) != 0; sig=''
    if bad : print('present bug exit'); exit()
    t=('x'+str(j)) if (j>0) else ''; u=('z'+str(i)) if (i>0) else ''
    if a!=0 : cf= div(dmax,b)*a; sig='+' if (cf>0) else ''
    if a!=0 : s+= ' '+sig+str(cf)+v+t+u
    j -=1
  s+=')/'+str(dmax)
  if noisy : print(s)
  return (s+'\n') 

def plmore(pl,l,noisy) :
  #Plm(z,x) = (-)^m x^m d^m Pl(z) where x= sqrt(1-zz)~ sin(t); xx+zz=1
  # option: homogenize with products (xx+zz) when deficient
  ql=pl;pwr=1;sign=-1; pi=''; qlist=[[l,0,[]+pl]]
  for i in range(1,l+1) :
    ql= polyndiff(ql); pwr += 1; ql=polynscale(ql,sign,1); qlist+=[[l,i,ql]]
    pi += present('Plm['+str(l)+str(i)+']=', ql, -i, noisy)
    # power i of x included, z^k must get (l-(k+i))/2 factors (1+zz). 
  return pi,qlist
 
def plzero(n,noisy) : # rational polys of z
  #  l Pl(z)=(2l-1) z P(l-1) - (l-1)P(l-2)
  #  l Pl(z,x)= (2l-1) z P(l-1)(z,x) - (l-1)(zz+[1~=xx])P(l-2) 
  pl=[[]]*20; pl[0]= [[1,1]]; z= [[0,1],[1,1]]; un=[[1,1],[0,1],[1,1]]
  pli='Plm[0,0]=(1)\n'; 
  pllist=[[0,0,[[1,1]] ]] # l=0 m=0 constant
  for i in range(1,n) :
    y= polynmul(pl[i-1],z); y= polynscale(y, 2*i-1,1)
    if (i-2)>= 0 : w= polynscale(pl[i-2],i-1,1); y=polynadd(y,w,1,-1)
    pl[i]= polynscale(y,1,i); pli += present('Plm['+str(i)+'0]=',pl[i],0,noisy)
    pi,qlist = plmore(pl[i],i,noisy); pllist += qlist 
    pli+=pi
  return pl,pli,pllist

'''
  I(m,n)= integ 0 to pi of sin^m cos^n [ n even ]
  - m += 2 implies  I(m+2,n) = (m+1)/(m+n+2) *I(m,n)
  - n += 2 implies  I(m,n+2) = (n+1)/(m+n+2) *I(m,n)
  - start functions : 1, sin, cos, sincos: 0,0 1,0 0,1, 1,1 
  0,0: pi. 1,0: 2 0,1 : 0  1,1 : 0
'''
def isico0pi(m,n) : # integral 0 to pi of sin^m(x) cos^n(x)
  if (n%2 != 0) : return [0,1] # ugly trick: negative val encode pi factor
  nn=0; mm= 0 if (m%2 == 0) else 1; p=-1 if (mm==0) else 2; q=1 
  while nn<n : p *= (nn+1); q *= (mm+nn+2); nn += 2
  p,q= tuple(divide(p,q))
  while mm<m : p *= (mm+1); q *= (mm+nn+2); mm += 2
  return divide(p,q)

def csproduct(n,a,b,c, m,d,e,f) : # product of 2-var polynomials p(x,z)
  # length n and m. a,d= lists of coeffs, b,e powers of x, c,f powers of z.
  # returns list of coeffs and list of power pairs they target
  # isico0pi uses these power pairs, results to be summed up then
  # variant: a,d are pairs=rationals.
  def ratpr(a,b) : return [a[0]*b[0],a[1]*b[1]]
  def ratsum(a,b) :
    if a==[] : a=[0,1]
    p=a[0]*b[1]+a[1]*b[0];q=a[1]*b[1]; return divide(p,q)
  def getindex(ix,p,q) : # where to store x^p y^q
    i=0; n=len(ix); found=False
    while (not found) and (i<n) and (ix[i]!=[]) :
      found= (ix[i][0]==p)and(ix[i][1]==q); i= (i+1) if (not found) else i
    if not found : ix[i]=[p,q] # new exponent combination
    return i 
  t=str(type(a[0])); ratnum= (t.find('list')>0)
  x= ([[]]*(n+m)) if ratnum else ([0]*(n+m)) 
  ix=[[]]*(n+m); jmax=0
  for i in range(n) :
    for k in range(m) :
      yy=b[i]+e[k]; zz=c[i]+f[k]; j=getindex(ix,yy,zz) # side effect on ix
      if ratnum : x[j]= ratsum(x[j],ratpr(a[i],d[k]))
      else : x[j] += a[i]*d[k]
      if j>jmax : jmax=j
  return x[:(jmax+1)],ix[:(jmax+1)]

def intprod(pa,ia,la, pb,ib,lb) : # integral of product of 2 plm(x,z) polyns
  # pa(x,z)= sum(k,pa[k]*x^ia*z^k, pb same. nonhomog, const x power=i+j
  # status ok for plzero
  na=len(pa); cfa=[0]*na; pxa=[0]*na; pza=[0]*na
  for i in range(na) : cfa[i]=pa[i]; pxa[i]=ia; pza[i]=i
  nb=len(pb); cfb=[0]*nb; pxb=[0]*nb; pzb=[0]*nb
  for i in range(nb) : cfb[i]=pb[i]; pxb[i]=ib; pzb[i]=i
  cf,pw= csproduct(na,cfa,pxa,pza, nb,cfb,pxb,pzb); nt=len(cf); sum=[]
  # print('intprod cf,pw='+str([cf,pw]))
  for j in range(nt) :
    pw[j][0] +=1 # sin factor integral measure
    z= isico0pi(pw[j][0],pw[j][1]); a=z[0]; b=z[1]
    z= [[0,1],[-a,b]] if (a<0) else [[a,b]]
    sum= polynadd(sum,z, 1, cf[j])
  # print('pa,sum='+str([pa,sum]))
  return sum

'''
hydrogen eigenfuncs
 |n,l,m> = \exp(im\phi)P_{lm}(\theta) r^l \exp(-r/na) Rad{nl}(r/na)
 Rad{nl}(z) := L_{n-l-1}^{2l+1}(2z) associated Laguerre polynomial
  int_0^\infty\;dr\; r^m exp(-r/na)) = (na)^(m+1) m!
 unnormalized (nlm) is exp(im\phi)Plm(s,c) L()(2s) exp(-s)*r^l; s=r/na
   here L(2s) = polyn(r/n), stored as Rnl(r). Bohrradius a=1.
 radial funcs for different n same l must be orthogonal, checked.
 L(q-p,p)= p-th deriv of L(q) poly of deg q.

perturbation z: inspect n=1,2,3,4 m=0..n-1 submatrices.
  matrix elements with z=r \cos\theta
'''

def sqroot(pq) : # root-of-rationals are size-4 int lists
  def hasfact(u,v) : return (u>=v) and ((u % v)==0)
  pn=[2,3,5,7,11,13,17,19,23,29,31,37]; ln= len(pn) # small prime nbrs
  p,q= tuple(pq) ; rp=1; rq=1
  if (p<0)or(q<=0) : print('sqroot bad arg. exit'); exit()
  if p==0 : q=1; ln=0
  for i in range(ln) :
    z=pn[i]; y=z*z
    while hasfact(p,y) : rp *= z; p=div(p,y) #;print('rp,i,p,y'+str([rp,i,p,y]))
    while hasfact(q,y) : rq *= z; q=div(q,y) #;print('rq,i,q,y'+str([rq,i,q,y]))
  return [p,q,rp,rq] # meaning: sqrt(p/q)*(rp/rq)

def normalize(x,na,nb) : # x is rational, na nb are square-roots, 4-component
  if len(x)==1 : x=x[0] # some x are inside a one-element list
  if len(x) != 2 : print('wrong type of argument.exit.'); exit()
  xrp,xrq=tuple(x); sign,xrp= (1,xrp) if (xrp>0) else (-1,-xrp)
  ap,aq,arp,arq=tuple(na); bp,bq,brp,brq=tuple(nb)
  xp=aq*bq; xq=ap*bp; xrp *= (arq*brq); xrq *= (arp*brp)
  yp,yq,yrp,yrq= tuple(sqroot([xp,xq]))
  return divide(yp,yq) + divide(sign*xrp*yrp,xrq*yrq)

def mulroot(x,y) : # two square-root rationals
  xp,xq,xrp,xrq= tuple(x); yp,yq,yrp,yrq= tuple(y)
  zp,zq,zrp,zrq= tuple(sqroot([xp*yp,xq*yq])); zrp*= xrp*yrp; zrq *= xrq*yrq
  return divide(zp,zq) + divide(zrp,zrq)

def stroot(x) : # root-number to string
  p,q,rp,rq= tuple(x); s='0'
  if (p!=0)and(rp!=0) :
    t= str(rp) if(rq==1)   else (str(rp)+'/'+str(rq))
    u= str(p)  if (q==1)   else (str(p)+'/'+str(q))
    s= t       if (u=='1') else (t+'*sqrt('+u+')')
    if (t=='1')and(u != '1') : s= 'sqrt('+u+')'
  return s

def lxroot(x) : # root-number to latex-string
  p,q,rp,rq= tuple(x); s='0'
  if (p!=0)and(rp!=0) :
    t= str(rp) if(rq==1)   else ('\\frac{'+str(rp)+'}{'+str(rq)+'}')
    u= str(p)  if (q==1)   else ('\\frac{'+str(p)+'}{'+str(q)+'}')
    s= t       if (u=='1') else (t+'\\sqrt{'+u+'}')
    if (t=='1')and(u != '1') : s= '\\sqrt{'+u+'}'
  return s

def lxmatrix(m) : # m is 2-dim array of strings
  nrow= len(m); ncol=len(m[0]) # print('nrow,ncol='+str([nrow,ncol])) 
  s=':<math>\\begin{pmatrix}\n'
  for i in range(nrow) :
    for k in range(ncol) : 
      t= m[i][k]; u= (' & '+t) if (k>0) else t; s +=u
    s += ('\\\\\n') if (i<(nrow-1)) else '\n'
  s +='\\end{pmatrix}</math>\n'; return s

def lxradials(rnl) : # list with normsquare int r^(2l+2) R^2(r) exp(-2r/n)
  n=len(rnl); s='<math>\\begin{array}{ll}\n'
  for i in range(n) :
    nn,nl,p,nm,nmr = tuple(rnl[i]); t='R_{'+str(nn)+str(nl)+'}='; d=len(p)
    for k in range(d) :
      x,y= tuple(p[k]); x,sx= (x,'+') if (x>=0) else (-x,'-')
      u= str(x) if(y==1)  else ('\\frac{'+str(x)+'}{'+str(y)+'}')
      if (k==0)and(sx=='+') : sx=''
      t += sx+u; t+= ('\\,r^{'+str(k)+'}') if (k>0) else ''
    s += t+' & '+lxroot(nmr)+'\\\\\n'
  s +='\\end{array}</math>\n'; return s

def lxsphericals(plm) : # format Plm(s,c)= s^|m| c^k, s,c= sin,cos (theta)
  n=len(plm); s='<math>\\begin{array}{ll}\n'
  for i in range(n) :
    nl,nm,p,nr,nrr = tuple(plm[i]); t='P_{'+str(nl)+str(nm)+'}=';d=len(p);pfx=0
    for k in range(d) :
      x,y= tuple(p[k]); x,sx= (x,'+') if (x>=0) else (-x,'-')
      u= str(x) if(y==1)  else ('\\frac{'+str(x)+'}{'+str(y)+'}')
      if (pfx==0)and(sx=='+') : sx=''
      sc= ('s^'+str(nm)) if(nm>1) else ('s' if (nm==1) else '') 
      sc+=('c^'+str(k)) if (k>1) else ('c' if(k==1) else '')
      if x != 0 : t += sx+u +'\\,'+sc; pfx=1 # pfx=1 show prefix even if +
    s += t+' & '+lxroot(nrr)+'\\\\\n'
  s +='\\end{array}</math>\n'; return s

def radialpolys(lmax=3,mode=0) : # coeffs of radial hydrogen atom polyns
  # related to associated Laguerre L(n-l-1;2l+1). L(1-1,1) is poly of deg zero
  pols=[]  # n=1,2,3,4, l=0..n-1, polynomials in (r/a). 
  for n in range(1,lmax+1) :
    for l in range(n) :
      j=2*l+2; k=0; p=1; q=1; t=''; pol=[]
      while p != 0 :
        p,q= division(p,q); pol += [[p,q]]
        t += ' '+ (str(p) if(q==1) else (str(p)+'/'+str(q)))
        q *= (k+1)*(k+j); p *= (-2*n+2*k+j); k+=1 
      #print('n='+str(n)+' l='+str(l)+' '+t)
      j=len(pol); q=1 # pol has args ~ r/(an).  multiply divisors by n^h
      for h in range(j) : pol[h][1] *= q; q *= n
      pols += [[n,l,pol,[0,1],[]]]; # print(str([n,l,pol]))
  return pols

def radialprd(pols,h,ia,ib,mode) :
  na,nb,la,lb= pols[ia][0],pols[ib][0], pols[ia][1],pols[ib][1] 
  a=1; b= [na*nb, na+nb] # exp [-r/(na a) - r/(nb a) ]
  pa=pols[ia][2]; pb=pols[ib][2]; q=polynmul(pa,pb); sum=[0,1]
  #print('pa='+str(pa)+' pb='+str(pb)) 
  #print('b='+str(b)+' q='+str(q))
  for i in range(len(q)) :
    p=la+lb+i+h+2 # f=[b[0],b[1]] # p true power of r, add q[i]*b^(p+1) p!?
    f=[1,1] #  optional const prefactor b ?
    for k in range(1,p+1) : f= mult(f,b); f[0] *= k # = b^p p!
    z=mult(q[i],f); sum=add(sum,z) 
    #print('i,p,k='+str([i,p,k])+' q[i]='+str(q[i])+' f='+str(f)+' z='+str(z))
  if (h==0)and(na==nb)and(la==lb) :
    pols[ia][3]= sum; pols[ia][4]= sqroot(sum) # append normsquare
  if mode==0 : print('radial h nl nl sum',str([h,na,la,nb,lb,sum]))
  return sum

def radialprod(pols, h, na,la, nb,lb,mode) : # radial matrix of operator r^h
  # polyn for na has coeffs for r'= r/(na), already divided
  # radial integral adds factor r^(2+la+lb)  mix should be (r/na)^la?
  ia=0; ib=0; m=len(pols); # print('h,na,la,nb,lb='+str([h,na,la,nb,lb]))
  while (ia<m) and ((pols[ia][0] != na)or(pols[ia][1] != la)) : ia +=1
  while (ib<m) and ((pols[ib][0] != nb)or(pols[ib][1] != lb)) : ib +=1
  #print('ia,ib='+str(ia)+','+str(ib))
  return radialprd(pols,h, ia,ib,mode)

def normsquared(pols,nmax,mode) :
  for n in range(nmax+1) :
    for l in range(n) : sum= radialprod(pols,0, n,l, n,l,mode)

def crossradial(pols,na,nb,mode) :
  bugs=0 # different n, same l must be orthogonal
  for la in range(na) :
    for lb in range(nb) :
      sum= radialprod(pols,0, na,la, nb,lb,mode); z=sum[0]
      if (na!=nb)and(la==lb)and(z!=0): print('BUG'); bugs +=1
  print('BUGS for ('+str(na)+','+str(nb)+') ='+str(bugs))

def getradials(nmax=4,mode=0) : # radial polyns
  pols=radialpolys(nmax,mode)
  normsquared(pols,nmax,mode)
  np=len(pols); pmx=[[]]*np # symm perturbation matrix
  for i in range(np) : pmx[i] =[[]]*np #; print(str(pols[i]))
  for i in range(np) : # later: divide each by sqrt(normsquares)
    for k in range(i+1) : 
      pmx[i][k]= radialprd(pols, 1,i,k,mode); pmx[k][i]= pmx[i][k]
  #for i in range(np) : print(str(pols[i][4]))
  return pols, pmx

def integplms(pa,ia, pb,ib, fc) :
  # integral of product of 2 plm(x,z) polyn, times sin * (cos power fc)
  # pa(x,z)= sum(k,pa[k]*x^ia*z^k, pb same. x=sin, z=cos. const x-power
  na=len(pa); cfa=[0]*na; pxa=[0]*na; pza=[0]*na
  for i in range(na) : cfa[i]=pa[i]; pxa[i]=ia; pza[i]=i
  nb=len(pb); cfb=[0]*nb; pxb=[0]*nb; pzb=[0]*nb
  for i in range(nb) : cfb[i]=pb[i]; pxb[i]=ib; pzb[i]=i
  cf,pw= csproduct(na,cfa,pxa,pza, nb,cfb,pxb,pzb); nt=len(cf); sum=[]
  # print('intprod cf,pw='+str([cf,pw]))
  for j in range(nt) :
    pw[j][0] +=1 # sin factor = integral measure
    pw[j][1] += fc # cos factor for z operator
    z= isico0pi(pw[j][0],pw[j][1]); a=z[0]; b=z[1]
    z= [[0,1],[-a,b]] if (a<0) else [[a,b]]
    sum= polynadd(sum,z, 1, cf[j])
  # print('pa,sum='+str([pa,sum]))
  return sum

def getsphericals(lmax=4,mode=0) :
  # return list of Plm and matrix for z operator. mode==0 debug output
  plz,plri,pllist= plzero(lmax+1,mode==0); ipl=len(pllist); mpl=[[]]*ipl
  # plz contains the Legendre polyn and associates, alt. pllist is used
  for i in range(ipl) : # recursion, nonnormalized Plm, and their integrals
    l=pllist[i][0]; m=pllist[i][1]; plm= pllist[i][2]
    y= integplms(plm,m, plm,m, 0); ry= sqroot(y[0]) # explicit integral
    if mode==0 : print(str(pllist[i])+' y='+str(y)+' ry='+str(ry))
    pllist[i].append(y); pllist[i].append(ry) # store with pllist
    mpl[i]= [[]]*ipl # matrix element mpl[i][k] of z=r cos(theta) operator 
  for i in range(ipl) :
    la=pllist[i][0]; ma=pllist[i][1]; pa= pllist[i][2]
    for k in range(i+1) :
      lb=pllist[k][0]; mb=pllist[k][1]; pb= pllist[k][2]
      y= integplms(pa,ma, pb,mb, 1); mpl[i][k]=y; mpl[k][i]=y
  return pllist, mpl

def matdisplay(m) : # m is 2-dim array of strings
  nrow= len(m); ncol=len(m[0]); wcol=[0]*ncol; sp=' '*80; s=''
  for i in range(nrow) :
    for k in range(ncol) : w= len(m[i][k]); wcol[k]= max(w,wcol[k])
  for i in range(nrow) :
    for k in range(ncol) : 
      t=m[i][k]; w=len(t); t= sp[:(wcol[k]+2-w)] +t; s +=t
    print(s); s=''

def perturbation(mode=0) : # stark effect on hydrogen degenerate subspaces
  # mode=1 latex-type  output, mode=0 debug screen output
  # for all pairs (n,m) need the matrix of all l with Rnl*Plm elements
  # rnl entries have entries [n,l, polynomial, sqnorm,norm]; ditto plm
  rnl,rmat= getradials(4,mode) # radial func Rnl,norms,their r-matrix elements
  plm,smat= getsphericals(4,mode) # Plm func,norms,cos(theta) matrix elements
  nr=len(rnl); ns=len(plm); rad=[0]*32;lrad=[0]*32;sph=[0]*32;lsph=[0]*32
  pair=[[]]*32; noisy=True; noisy=False 
  ##           perturbation matrix degenerate level n,m
  def setupmatrix(n,m,pair,noisy) : # pair[i]= [l-value, irad,iang]
    p=len(pair); sl=''; mat=[[]]*p; lxm=[[]]*p # terminal and latex matrix
    for i in range(p) : sl +=' '+str(pair[i][0])
    print('Matrix  n='+str(n)+'  m='+str(m)+' Liste l= '+sl)
    for k in range(p) : mat[k]= ['']*p; lxm[k]= ['']*p
    for i in range(p) :
      irad=pair[i][1]; iang=pair[i][2]; irnl=rnl[irad]; iplm=plm[iang]
      #print(' i='+str(i)+ str(irnl)+ str(iplm)) 
      irnorm=irnl[4]; ipnorm=iplm[4]
      for k in range(i+1) :
        krad=pair[k][1]; kang=pair[k][2]; krnl=rnl[krad]; kplm=plm[kang]
        krnorm=krnl[4]; kpnorm=kplm[4]; r=rmat[irad][krad];a=smat[iang][kang]
        if noisy and (a[0][0] != 0) and (mode==0) :
          print('  ik,irnorm,ipnorm,krnorm,kpnorm '+str(i)+str(k)+' '
            +str(irnorm)+str(ipnorm)+str(krnorm)+str(kpnorm))
          print(' mat-el r,a='+str(r)+str(a))
        # matrixelement is: (r/irnorm/krnorm)*(a/ipnorm/kpnorm)
        r= normalize(r,irnorm,krnorm); a=normalize(a,ipnorm,kpnorm)
        ra=mulroot(r,a); z= stroot(ra); r=stroot(r); a=stroot(a) 
        mat[i][k]=z; mat[k][i]=z
        z=lxroot(ra); lxm[i][k]=z; lxm[k][i]=z
        # print('  mat,rad,ang:  '+z+'  , '+r+','+a) # angular=0 on diagonals
    return mat,lxm
  ##
  for n in range(1,5) : # find all l that match radial(n,l) and angular(l,m) 
    h=0
    for i in range(nr) : 
      if rnl[i][0]==n : rad[h]= i;lrad[h]=rnl[i][1]; h+=1
    # print('n='+str(n)+' lrad='+str(lrad[:h]))
    for m in range(5) : # find all l from Plm and Rnl 
      g=0; ix=0 # pair[ix]=[lvalue, index to rnl, index to plm]
      for i in range(ns) : 
        if plm[i][1]==m : sph[g]=i; lsph[g]=plm[i][0]; g+=1
      # print('   m='+str(m)+' lsph='+str(lsph[:g])) 
      for j in range(h) :
        ll=lrad[j]
        for k in range(g) : 
          if (ll==lsph[k]) : pair[ix]=[ll, rad[j],sph[k]]; ix+=1
      if ix>1 : # matrix, not a single nondegen level
        mat,lxm= setupmatrix(n,m,pair[:ix],noisy); noisy=False
        if mode==0 : matdisplay(mat)
        if mode==1 : lm= lxmatrix(lxm); print(lm)
  if mode==1 :
    print(lxradials(rnl)) 
    print(lxsphericals(plm)) 
  
#perturbation(0) # debug
#print('*** Wikibook-Latex Output ***')
perturbation(1)

Beispiel Zeeman-Effekt

Bearbeiten

Die WKB-Methode

Bearbeiten

Die Wentzel-Kramers-Brillouin-Methode versucht, näherungsweise die stationäre Schrödingergleichung in einer Dimension zu lösen. Die Energie sollte genügend weit vom Grundzustand entfernt sein. In jeder Umgebung gibt es eine Basis von zwei Wellen, da die Differenzialgleichung zweiter Ordnung und reell ist. Es werden diese Fälle um einen Punkt x herum behandelt:

  • E weit über dem Potenzial V(x), ergibt ein Paar oszillierender Funktionen
  • E weit unter V(x), eine exponentiell fallende und eine steigende Funktion
  • E=V(x), das heißt, x ist ein Umkehrpunkt für klassische Bahnen.

Unter der Annahme, dass V(x) genügend weit linear um x herum ist, leisten am Umkehrpunkt dann Paare aus Besselfunktionen die nötige Approximation. Danach werden Linearkombinationen aller Segmente so bestimmt, dass sie stetig-differenzierbar aufeinander treffen und global als Lösung durchgehen.

Mit   wird die Schrödinger-Gleichung

  •   in einem Intervall wo  
  •   in einem Intervall wo  

Die Näherung postuliert nun, dass die Amplitude von u wesentlich langsamer variiert als die Phase. Im Fall E>V um Punkt   wird angesetzt:

 .

Einsetzen in die Gleichung liefert dies für a(x) :

 

Wird hier   vernachlässigt, folgt  , was eine Lösung   hat. Damit wird die WKB-Näherung für E>V:

 

Mit   wird  . Die Bedingung   kann dann übersetzt werden in folgende Langsamkeits-Kriterien für k(x):   und  .

Im Gebiet E<V, worin die Funktion u per Tunneleffekt exponentiell eindringt, ergibt sich nach dem gleichen Muster die Approximation:

 

Wie geht man vor, um die Umkehrpunkte V(w)=E zu überbrücken?

An einem Punkt w mit E=V(w) sei   mit V>E für x>w. Also  .

Mit der Variablen   hat u(y) die Schrödingergleichung

 

Auf der anderen Seite x<w, E<V, ist  .

Dort setze  . Die Schrödingergleichung wird zu

 

Die Differenzialgleichungen für u(y) am Umkehrpunkt haben die Form

  wo  

Der Trick ist, die Gleichung für Besselfunktionen z(t) da herauszuschälen:

 

Dazu hier die funktionierende Variablen-Substitution mit Parametern p,q:

 

Einsetzen in die Gleichung für u und Auflösen nach z und t führt zu:

 

Mit der geforderten Bessel-Gleichung ergeben sich q,p und s aus a und b:

 

Die zwei Wurzeln für s ergeben zwei linear unabhängige Besselfunktionen. q kann imaginär sein, s ebenfalls? Die Familie von Besselfunktionen   ist analytisch in beiden Argumenten s und t, t kann imaginäre Werte haben.

 
 

Auf der Tunnel-Seite E<V des Umkehrpunkts gibt es das Paar von Lösungen:

  mit  .

Es wird angenommen, dass u(y) bis in einen Bereich   hinein gut ist, so dass dort die Exponential-Lösung angeflanscht wird. Es gibt dort folgende Asymptotik:

 
 

Nun war  , daher  .

Die Asymptote bekommt glatten Anschluss an die WKB-Lösung, wenn sie exponentiell abfällt, daher:  .

Auf der oszillierenden Seite E>V:  . Das Grenzverhalten für kleine x auf beiden Seiten des Punktes w wird gebraucht, um die stetig differenzierbare Fortsetzung zu erreichen:

 
 
 
 

Der  -Teil hat glatten Anschluss an   und der  -Teil an  . Also ergibt sich als Fortsetzung von der Tunnelseite in (E>V)-Seite:

 

Auch hier hoffentlich bis in einen Bereich   mit der Asymptotik:

 
 

Andererseits gilt auf offener Strecke (E>V) zwischen Umkehrpunkten nach WKB eine Linearkombination aus  . Diese kann nun fixiert werden bezüglich des nächsten Umkehrpunktes w>x:

 .

In einem Potentialtopf zwischen zwei Umkehrpunkten a<x<b muss symmetrisch die andere Randbedingung verwertet werden:

 , also muss gelten
 

Mit positivem Faktor ergäbe sich daraus

 .

Aber als Funktion von x variieren hier beide Seiten im Gegensinn, was diese Wahl ausschließt. Vielmehr stimmt der Faktor -1 und man kann die Integrale sofort vereinen zu einer Forderung an die Funktion k(x):

  mit nicht-negativem ganzen n.

Funktion k(x) ist die Rate, mit der die Phase der Welle sich ändert. Die summierte Phase muss, von Randeffekten abgesehen, ein Vielfaches von   sein, damit u(x) als stehende Welle in eine Potenzialmulde passt.

Die WKB-Methode findet also quantisierte Niveaus. Sie produziert sogar für den harmonischen Oszillator exakt die richtigen Energiewerte, während für die l=0 Wellen im Coulomb-Potenzial eine verschobene Energie-Folge herauskommt, nämlich anstatt   etwa  . Dies unterstreicht nur, dass die Methode nicht zu nahe am Grundzustand des Systems anzuwenden ist.

Tunneleffekt mit WKB

Bearbeiten

Zur Behandlung eines Potenzial-Hügels statt eines Tals wird noch die Anschluss- Bedingung an Umkehrpunkten gebraucht, die sich exponentiell ansteigend in den Tunnelbereich fortsetzt. Dann wird folgende Aufgabe angegangen: Eine freie Welle kommt von links, dringt in die Potenzial-Mauer V(x) ein und wird zum Teil rechts zu einer durchgehenden Welle, zum anderen Teil reflektiert. Die Linearkombinationen aus je zwei WKB-Wellen in den drei Bereichen sind mit den Übergangs-Formeln stetig zu verbinden. Gesucht ist die Amplitude für die Transmission, also auch die Tunnel-Wahrscheinlichkeit als Quadrat davon.

Mit Hilfe von asymptotischen Formeln für Besselfunktionen, analog zu oben, begründet man folgenden Regelsatz für die Verbindung von WKB-Wellen:

  • V(x)<E für x<a, Umkehrpunkt a, V(x)>E für x>a :
 
 
  • V(x)>E für x<b, Umkehrpunkt b, V(x)<E für x>b :
 
 

Ansatz der Wellenfunktion für einen Potential-Berg V(x)>E im Intervall a<x<b, setze Variable x immer als Integral-Obergrenze:

 
 
 

Um die Anpassungs-Regeln einzusetzen, werden erst   in   umcodiert und bei Bedarf Integral-Grenzen umgeklappt:

 
 
 

mit  . I misst die Opazität und ist typisch eine große Zahl bei dicker,hoher Barriere (V(x)-E). Die Verbindung geschieht dann nach dem Dreisatz: Soll   sich mit   treffen, dann muss der erste Faktor in   gleich 2C sein. Alles zusammen ergibt 4 lineare Gleichungen mit A,B,C,D,E,F. Aufgelöst nach A,B ergibt sich mit   :

 
 

Das erste Matrixelement (u/2) ergibt die Durchlass-Wahrscheinlichkeit der Welle, oder auch den Durchlass-Koeffizienten, wenn G zu Null gesetzt wird (keine einlaufende Welle von rechts).

  weil typischerweise  .

Alternative Behandlung von Umkehrpunkten Kurz erwähnt sei ein Verformungs-Verfahren für Potenziale mit Umkehrpunkten. Sein Anwendungsbereich ist 'quasiklassisch' wie WKB. Das Prinzip: Sowohl der Definitions- wie der den Wertebereich der zeitunabhängigen Schrödinger-Gleichung sollen nichtlinear so transformiert werden, dass in den neuen Koordinaten eine äquivalent Schrödinger-Gleichung mit einem leichter lösbaren Potenzial-Term auftaucht,  .

Aus   (v reell oder imaginär in Intervallen) wird  . Das gewählte   soll dieselbe Folge von Umkehrpunkten und postiven/negativen Intervallen haben wie  . Die Transformation habe die Form  ; es sei   mit einer reellen Amplitudenmodulation T(x).

 

Der mittlere Term wird wegtransformiert, wenn T(x) als Potenz von   gewählt wird:  . Es bleibt diese Gleichung mit  :

 

Andereseits folgt aus der Original-Schrödinger-Gleichung für das Paar {T,s}:

 .

Jetzt wird die quasiklassische Näherung gemacht,   zu vernachlässigen. Damit wird  , woraus sich die Funktion s(x) als Obergrenze des folgenden Integrals indirekt ergibt.   ist dabei ein Umkehrpunkt:

 .

Die Approximations-Lösung der Schrödinger-Gleichung folgt dem Rezept:

  • wähle ein gutmütiges Potenzial   kompatibel mit  
  • berechne aus p() und v() die Funktionen  , eventuell numerisch
  • prüfe mit  , ob   klein genug ist
  • finde Lösungen der Schrödinger-Gleichung,   zum Potenzial  
  •  

Zeitabhängige Störungsrechnung

Bearbeiten

Das Ziel einer zeitabhängigen Rechnung ist es, mehr herauszufinden als die stationären Zustände. Nämlich die Dynamik der Prozesse. Geschwindigkeiten der Übergänge zwischen Zuständen, Regeln für erlaubte und verbotene Prozesse, Verteilungen von Häufigkeiten, Energien und Winkeln von Spaltprodukten bei Explosionen und Kollisionen, usw.

Anhang Python-Skript

Bearbeiten
#h2plus.py  basicmodel of hydrogen ion bond, variation method

from math import exp,sqrt
def div(a,b) : return int(a/b)

# h2plus energy level approx
def erglvl2(r,q,a) : # r=proton distance, q=wave radius, a=bohr radius
  # all factors pi are suppresed, nq= S11=S22, s=S12=S21
  # to return energies with respect to hydrogen, factor 2a
  nq= q*q*q; cq= q*q; x=q/r; z=2*q/r; y=exp(-r/q)
  s= r*r*r*y*( x/3.0 + x*x + x*x*x)
  u= r*r * y * (x+x*x)
  q2=q/2.0; z=2.0*q2/r; y2=exp(-r/q2); t=r*r*((z*z+z*z*z)*(-y2) + z*z*z)
  f=(1.0/r-a/(2.0*q*q))
  h11= f*nq + (a/q-1)*cq - t
  h12= f*s + (a/q-2.0)*u
  # solve (h11-nq u)^2=(h12-s u)^2: 
  uplus= 2*a*(h11-h12)/(nq-s); uminu= 2*a*(h11+h12)/(nq+s)
  return uplus,uminu, [nq,s,t,u, cq]

def findmini(x,y) : # parabola interpolation y=a+bx+cx^2
  x1,x2,x3=tuple(x); y1,y2,y3=tuple(y) # find y=a+bx+cxx
  z1= (y2-y1)*(x3*x3-x2*x2); z2= (y3-y2)*(x2*x2-x1*x1)
  w1= (x2-x1)*(x3*x3-x2*x2); w2= (x3-x2)*(x2*x2-x1*x1) # b(w1-w2)=z1-z2
  b=(z1-z2)/(w1-w2); c= ((y2-y1)-b*(x2-x1))/(x2*x2-x1*x1)
  a= y1 - b*x1 - c*x1*x1; xmin=-b/(2*c); ymin=a-b*b/(4.0*c)
  return xmin,ymin

def scanerglevel(r,a) :
  noisy=False; fmax=1.2; df=0.04; refine=True; tbl=[]
  for k in range(101) : # r from 1 to 4 step 0.03
    emin=1e9; imin=-1; r= 1.0+ 0.03*k; y=0
    u0,v0,bug= erglvl2(r,a,a)
    if noisy : print('erglevel scan r='+str(r)); 
    for i in range(20) :
      f=fmax- df*i; q=a*f 
      u,v,bug= erglvl2(r,q,a); w=min(u,v)
      if noisy : print('i='+str(i)+' e='+str(w))
      emin,imin= (w,i) if (w<emin) else (emin,imin)
    if refine :
      x=fmax-df*imin; q1=a*x; q0=a*(x+df); q2=a*(x-df)
      u,v,bug= erglvl2(r,q0,a); y0=min(u,v)
      u,v,bug= erglvl2(r,q1,a); y1=min(u,v)
      u,v,bug= erglvl2(r,q2,a); y2=min(u,v)
      x,y= findmini([q0,q1,q2],[y0,y1,y2]) 
      u1,v1,bug= erglvl2(r,x,a); tbl +=[[r,u0,v0,u1,v1]]
    print('erg r,i,emin ='+str([r,imin,emin,y])) 
  return tbl

class svgdump :
  def plotbegin(sd, w,h,f) : # args: width height zoomfactor
    W=str(w); H=str(h); u=''; fw=str(f*w); fh=str(f*h)
    s=( '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'+
     '<!DOCTYPE svg>\n'+
     '<svg xmlns="http://www.w3.org/2000/svg"\n'+
     '  xmlns:xlink="http://www.w3.org/1999/xlink"\n'+
     '  width="'+W+'" height="'+H+'" viewBox="0 0 '+fw+' '+fh+'">\n')
    return s

  def plotend(sd) : return '</svg>\n'
  def move(sd,x,y) : return 'M'+str(x)+','+str(y)
  def line(sd,x,y) : return 'L'+str(x)+','+str(y)
      
  def labl(sd,s,x,y, size=14,fill='#000') :
    f=' font-family="Arial"'
    t='<tspan x="'+str(x)+'" y="'+str(y)+'">'+s+'</tspan>'; fsz= str(size)
    return ('<text fill="'+fill+'"'+f+' font-size="'+fsz+'px">'+t+'</text>\n')

  def path(sd,w,s,lc='#000') : # path w=with lc=linecolor
    t='<path fill="none" stroke="'+lc+'" stroke-width="'+str(w)+'" '
    t+= ' stroke-linecap="round" ' 
    return t + 'd="\n'+s+'" />\n'

# end class svgdump

def aprx(x) : # 3-digit approximation
  y= (-x) if(x<0) else x; z= int(1e3*y+1e-3); z= (-z) if (x<0) else z
  return z/1000.0

def curve(sd,scale,xy, fill='#000', width=1) :
  (xs,fx,xmin, ys,fy,ymin) = scale; s=''; n= div(len(xy),2) 
  for i in range(n) : 
    x= aprx(xmin+fx*(xy[2*i]-xs)); y= aprx(ymin+fy*(xy[2*i+1]-ys))
    s+= sd.move(x,y) if(i==0) else sd.line(x,y)
  return sd.path(width,s,lc=fill)

def myplot(tbl) :
  n=len(tbl); xmin=1e9;xmax=-xmin; ymin=xmin;ymax=-ymin; k=len(tbl[0])
  print('n='+str(n)+' k='+str(k))
  xys=[[]]*(k-1)
  for j in range(k-1) : xys[j]= [0.0]*(2*n)
  for i in range(n) :
    xx= tbl[i][0]; xmin=min(xmin,xx); xmax=max(xmax,xx)
    for j in range(1,k) :
      z=tbl[i][j]; ymin=min(ymin,z);ymax=max(ymax,z)
      xys[j-1][2*i]=xx; xys[j-1][2*i+1]= z
  print('[xmin,xmax,ymin,ymax]='+str([xmin,xmax,ymin,ymax]))
  m=len(xys); print('len xys='+str(m))
  for i in range(m) : print(' i='+str(i)+' len='+str(len(xys[i])))
  xmin=1;xmax=4;ymin=-1.5;ymax=0;yref=-1
  xyf=[ xmin,ymin, xmax,ymin, xmax,ymax, xmin,ymax, xmin,ymin,
        xmin,yref, xmax,yref, xmax,ymin,3,ymin,3,ymax,2,ymax,2,ymin] # frame
  xs,fx,xpix= xmin, 750/(xmax-xmin),25 # scale start user units, min in pixels
  ys,fy,ypix= ymin,-550/(ymax-ymin),575 ; scale=(xs,fx,xpix, ys,fy,ypix)
  colo=['#000','#000','#00a','#077','#0a0','#770','#a00']
  fn='h2plus';  xys= [xyf] + xys
  text='H2+ Ion Energy levels. Base line = Hydrogen atom ground state.';
  sd=svgdump(); buf=sd.plotbegin(800,600,1)
  buf += sd.labl(text,50,500); i=0
  text='Verticals: 1,2,3,4 Bohr radius';
  buf += sd.labl(text,50,530); i=0
  for t in xys : buf += curve(sd,scale, t, fill=colo[i%7]); i +=1
  g=open(fn+'.svg','w'); g.write(buf+sd.plotend()); g.close()

tbl= scanerglevel(2,1); myplot(tbl)
print('File h2plus.svg written.')