Quantenmechanik/ Drehimpuls-Kopplung/ CG-Skript

Skript zu Clebsch-Gordan-Koeffizienten

Bearbeiten

Das Skript listet Clebsch-Gordan-Koeffizienten zu Paaren von Drehimpulsen L und S. Sie sollen zum Gesamt-Drehimpuls mit dem Operator J=L+S kombiniert werden. Nur die nichtverschwindenden Werte für die möglichen Eigenvektoren   des Gesamt-Drehimpulses werden aufgezählt.

Alle Rechnerei ist Ganzzahl-Arithmetik, mit der "Rationale Wurzelzahlen" aufgebaut werden. Das sind hier Quintupel von Ganzzahlen [r,p,q,u,v] mit q>0, v>0 und der Bedeutung:  .
Der Imaginärteil p wird in diesem Skript nicht benutzt, er ist immer Null. Die Funktion reformat() verwandelt ganze und einfach-rationale, d.h. Paare [p,q], in solche Quintupel. Rechenschritte erfolgen mit den Funktionen
multip,addit,sqroot,recip,scale,lincomb,quotient,iszero.
Die Addition geht nur, wenn beide Argument rationale Vielfache derselben Wurzel sind, sonst bricht das Programm ab.

Zu Eingabe-Paaren (L,S) berechnet das Skript alle Clebsch-Gordan-Koeffizienten. L und S sind Vielfache von (1/2). Also (2L)=1,2,3,... und (2S)=1,2,3,... Eine Clebsch-Gordan-Matrix enthält (2L+1)*(2S+1) zum Quadrat rationale Wurzelzahlen.
De Matrix ist hier eine Tabelle mit 4 Indizes  .
Quantenzahl J ist die absteigende Folge (L+S),(L+S-1)...|L-S|.
J,L und S sind entweder halb- oder ganzzahlig.
i,j,k,l fangen bei Null an, meinen aber folgende physikalischen Werte:

Index i adressiert den Wert J, vom Maximum (L+S) abwärts.
Index j adressiert den Wert J_3, in absteigender Reihenfolge J,J-1,...,-J.
Index k adressiert den Wert L_3, Reihenfolge abfallend L,L-1,...,-L
Index l adressiert S_3, Reihenfolge S,S-1,...,-S.

In physikalischer Notation ist  .
Die Elemente mit festem   und   variabel nennen wir etwas willkürlich eine Zeile der Matrix, bei umgekehrter Variation eine Spalte. Für L=3,S=2 hat eine Zeile die Länge (2*3+1)(2*2+1)=35 und eine Spalte (2*1+1)+(2*2+1)+(2*3+1)+(2*4+1)+(2*5+1)=3+5+7+9+11=35, ganz wie geplant.

Der Algorithmus folgt sklavisch dem Standard-Rezept. Angefangen wird mit dem maximalen J,J_3 und einem Koeffizienten 1. Mit den Absteige-Operatoren werden die Zeilenvektoren für die Folge der J_3 errechnet. Beim nächst niedrigeren J wird angefangen mit dem orthogonalen Komplement zu den bis dahin bereits vorhandenen Vektoren zur Quantenzahl J_3=J. Der Orthogonalisierungs-Algorithmus bekommt n-tupel von Wurzelzahlen als Eingabe zu sehen, jeweils (n-1) davon. Ist   eine Menge von schon orthogonalen Vektoren und y ein Kandidat für ihr Komplement, dann werden einfach alle seine Projektionen abgezogen:  . Für   werden brutal die Standard-Einheitsvektoren durchprobiert bis ein orthonormierbares   da ist. Es gibt mindestens eine der (n) Achsen, die von den angelieferten (n-1) Vektoren linear unabhängig ist, daher wird ein Komplement-Vektor gefunden.
Konvention: Der Eintrag zum maximalen Wert von J_3 und L_3 bei jeder neuen Treppenstufe von J wird positiv gemacht.

Halbzahlige Variablen werden vermieden zugunsten ihres Doppels. Wenn ein Variablenname wie j2 oder m2 mit der Ziffer 2 endet, enthält er zweimal die physikalische Größe j oder m.

Das Skript wurde durchprobiert mit den Quantenzahlen L und S = (1/2),1,(3/2),2,(5/2),3. Keine Garantie darüber hinaus. Mit L=S=3 hat die CG-Matrix 2401=(7*7)*(7*7) Elemente, davon 216 nicht Null!

Der Code hat (langsame) Testsequenzen dazu, dass alle Zeilen und Spalten der Clebsch-Gordan-Matrix orthonormal sind, dass die Symmetrie beim Umklappen von   stimmt und dass die Kopfzeile jedes j-Blocks auch mit den Racah-Formeln genau gleich herauskommt.

Definierende Gleichung der CG-Matrix:

 

Symmetrien der Koeffizienten:

  •  
  •  

Auf- und Absteigeregeln für festes J:

 
 
 

Beziehung zwischen J-Nachbarn bei festem   :

  •  
 
 

Racah-Formel für den Spezialfall  

  •  
 
 

Es existiert eine alleswissende, abschreckend komplizierte Racah-Formel für die Gesamtheit der CG-Koeffizienten.

Wigner-3j-Symbole sind eine alternative Form der CG-Koeffizienten.

 .

Kostprobe der Ausgabe

Bearbeiten

Clebsch-Gordan L=(1/2) S=(1/2)
 

Clebsch-Gordan L=(1/2) S=1
 

Clebsch-Gordan L=(1/2) S=(3/2)
 

Clebsch-Gordan L=(1/2) S=2
 

Clebsch-Gordan L=1 S=(1/2)
 

Clebsch-Gordan L=1 S=1
 

Python-Listing

Bearbeiten

def colsalign(s) : # align string array s columns, strings start with ' '
  n=len(s); m=32; cols=[0]*m; kmax=0
  for i in range(n) :
    t=s[i]; h=0; j=t.find(' ',h+1); k=0
    while j>=0 : 
      if ((j-h)>cols[k]) : cols[k]=j-h-1
      h=j; j=t.find(' ',h+1); k+=1
    j=len(t) 
    if ((j-h)>cols[k]) : cols[k]=j-h-1
    if (k+1)>kmax : kmax=k+1
  # dbg print('Cols: '+str(cols[:kmax]))
  for i in range(n) :
    t=s[i]; h=0; j=t.find(' ',h+1); lt=len(t); k=0; v=''
    while j<=lt :
      u=t[h:j]+(' '*(cols[k]-(j-h-1))); v+=u  
      h=j; j= (lt+10) if (h==lt) else t.find(' ',h+1)
      k+=1; j= lt if (j<0) else j
    s[i]= v
  return s

def hasfact(x,z) : return (x>0) and ((x % z)==0)
def havefact(x,y,z) : # x,y have common factor z?
  fx=hasfact(x,z); fy=hasfact(y,z) 
  return (fx and fy) or (fx and (y==0)) or (fy and (x==0))

def normalize(x) : # rational-square root number x
  pn=[2,3,5,7,11,13,17,19,23]; ln= len(pn) # small prime nbrs
  ps=[0,1,4,9,16,25,36,49,64,81,100,121,144]; ls=len(ps) # small squares
  pr,pi,q,sp,sq = tuple(x)
  pr,sgr= (-pr,-1) if (pr<0) else (pr,1) 
  pi,sgi= (-pi,-1) if (pi<0) else (pi,1)
  for i in range(2,len(ps)) :
    z=ps[i]
    while hasfact(sp,z) : sp=div(sp,z); pr *= i; pi *= i
    while hasfact(sq,z) : sq=div(sq,z); q *=i
  for i in range(ln) :
    z=pn[i]
    while hasfact(sp,z) and hasfact(sq,z) : sp=div(sp,z); sq=div(sq,z)
    while havefact(pr,pi,z) and hasfact(q,z) :
      pr=div(pr,z); pi=div(pi,z); q=div(q,z)
  if (pr==0)and(pi==0) : q=1;sp=1;sq=1
  x[0],x[1],x[2],x[3],x[4] = (pr*sgr,pi*sgi,q,sp,sq)

def real(x) : # rational square root number, get real part
  pr,pi,q,sp,sq = tuple(x); return sqrt(sp/(sq+0.0))*pr/(q+0.0)

def encodenb(x) :
  pr,pi,q,sp,sq = tuple(x); s=''; w=''
  if (sp!=1)or(sq!=1) :
    w='\\sqrt{'; 
    w += str(sp) if(sq==1) else ('\\frac{'+str(sp)+'}{'+str(sq)+'}')
    w +='}'
  if pr != 0 :
    u=str(pr); s += u if (q==1) else ('\\frac{'+u+'}{'+str(q)+'}')
    s = w if((s=='1')and(w!='')) else (s if (s=='1') else (s+w))
  if pi != 0 : 
    if (pr!=0) and (pi>0) : s +='+i'
    elif (pi<0) : s += '-i'; pi=-pi
    else : s += 'i'
    u=str(pi); u='' if ((pi==1)and(q==1)) else u
    s += u if (q==1) else ('\\frac{'+u+'}{'+str(q)+'}')
    s = s+w
  if (pr==0)and(pi==0) : s='0' 
  return s

def finetune(x) : 
  pn=[2,3,5,7,11,13,17,19,23]; ln= len(pn) # small primes
  xx=reformat(x); pr,pi,q,sp,sq = tuple(xx)
  nmax=max(sp,sq);imax=0 # minimize values under root
  for i in range(2,5) : 
    tp=sp*i*i; tq=sq
    for k in range(ln) :
      z=pn[k]
      while hasfact(tp,z) and hasfact(tq,z) : tp=div(tp,z); tq=div(tq,z)
    if max(tp,tq)<=nmax: nmax=max(tp,tq);imax=i; pmax=tp;qmax=tq
  if imax>0 : q *=imax; sp=pmax; sq=qmax
  return [pr,pi,q,sp,sq]

def displaynb(x) : # x is 5-tuple. to readable string, root in square brackets
  # bug: ambiguous output rt(2/3)=2*rt(1/6)= (1/3)*sqrt(6) 
  # minimize max(p,q) in sqrt(p/q)? reduce {4,9,1/4,1/9)*p/q= p'/q'?
  # prefer sqrt(p) over sqrt(1/q)
  xx=reformat(x); pr,pi,q,sp,sq = tuple(xx); s=''; w=''
  if (sp!=1)or(sq!=1) :
    w='['; 
    w += str(sp) if(sq==1) else (str(sp)+'/'+str(sq))
    w +=']'
  if pr != 0 :
    u=str(pr); s += u if (q==1) else (u+'/'+str(q))
    s = w if((s=='1')and(w!='')) else (s if (s=='1') else (s+w))
  if pi != 0 : 
    if (pr!=0) and (pi>0) : s +='+i'
    elif (pi<0) : s += '-i'; pi=-pi
    else : s += 'i'
    u=str(pi); u='' if ((pi==1)and(q==1)) else u
    s += u if (q==1) else (u+'/'+str(q))
    s = s+w
  if (pr==0)and(pi==0) : s='0' 
  return s

def reformat(x) : # int, rational, rcomplex to root numbers
  if type(x)==type(1) : z=[x,0,1,1,1]; n=-1
  elif type(x)==type([]) : n=len(x)
  if n==1 : z=[x[0],0,1,1,1]
  if n==2 : z=[x[0],0,x[1],1,1]
  if n==3 : z=[x[0],x[1],x[2],1,1]
  if n==5 : z=x
  if (n>5)or(n==0)or(n==4): print('reformat '+str(x)+' not supported.'); exit()
  return z

def multip(x,y) : # format (u,v,w,p,q) = (u+iv)/w sqrt(p/q)
  xx=reformat(x); yy=reformat(y)
  xpr,xpi,xq,xsp,xsq= tuple(xx); ypr,ypi,yq,ysp,ysq= tuple(yy)
  pr= xpr*ypr-xpi*ypi; pi= xpr*ypi+ypr*xpi; q=xq*yq; sp=xsp*ysp;sq=xsq*ysq
  z= [pr,pi,q,sp,sq]; normalize(z); return z

def addit(x,y) :
  xx=reformat(x); yy=reformat(y)
  return lincomb(xx,yy,1,1)

def sqroot(x) : # input should be (p,0,q,1,1) 
  xx=reformat(x); pr,pi,q,sp,sq = tuple(xx)
  if (pi!=0)or(sp!=1)or(sq!=1) : print('sqroot undefined.'); exit()
  sp=pr;sq=q;pr=1;q=1; z=[pr,pi,q,sp,sq]; normalize(z); return z

def recip(x) :
  xx=reformat(x); pr,pi,q,sp,sq = tuple(xx); qq=pr*pr+pi*pi; 
  y= [pr*q,-pi*q,qq,sq,sp]; normalize(y); return y

def imagi(x) : pr,pi,q,sp,sq = tuple(x); return [-pi,pr,q,sp,sq]

def scale(x,f) :
  xx=reformat(x); pr,pi,q,sp,sq = tuple(xx); return [pr*f,pi*f,q,sp,sq]

def lincomb(x,y,f,g) : # f,g integer, x,y fractional root numbers
  # a sqrt(r/s) + b sqrt(p/q) ?= sqrt(rp/qs) ?
  # can only be combined if r/s and p/q differ by square factor T/U ?
  # first write rq/qs, ps/qs and test if rq,ps have square factor
  ps=[0,1,4,9,16,25,36,49,64,81,100,121,144]; ls=len(ps) # small squares
  xpr,xpi,xq,xsp,xsq= tuple(x); xpr *= f; xpi *=f
  ypr,ypi,yq,ysp,ysq= tuple(y); ypr *= g; ypi *=g
  if (xpr==0)and(xpi==0) :   z=[ypr,ypi,yq,ysp,ysq]
  elif (ypr==0)and(ypi==0) : z= [xpr,xpi,xq,xsp,xsq] 
  else :
    sq=xsq*ysq; xp=xsp*ysq; yp=ysp*xsq # test common sqroot-denom
    for i in range(2,len(ps)) :
      z=ps[i]
      while hasfact(xp,z) : xp=div(xp,z); xpr *= i; xpi *= i
      while hasfact(yp,z) : yp=div(yp,z); ypr *= i; ypi *= i 
    # here xp/sq new sqrt arg? if yp=n xp, rewrite x ?
    if xp != yp : print('lincomb:sqrt clash'+str([xp,yp,x,y])); exit()
    sp=xp; q=xq*yq; pr=xpr*yq+ypr*xq; pi=xpi*yq+ypi*xq
    z= [pr,pi,q,sp,sq]
  normalize(z); return z

def matshow(s,x,new=0) :
  # new= 1 simplified, columns. new=0 raw.
  # new= 2 use encodenb, latex-like, check square hermitian 
  n=len(x); m=len(x[0]); print(s); t=''; ok=True; tt=['']*n
  for i in range(n) :
    for k in range(m) :
      if (new==2) and (k>0) : t +=' &'
      if (new==1) : t +=' '+displaynb(x[i][k])
      if (new==2) : t +=' '+encodenb(x[i][k])
      if new==0 : t +='  '+str(x[i][k]) # beware about v, we need a copy!
      if new==2 :
        u=x[i][k]; v= [] + x[k][i]; v[1]= -v[1]; ok= ok and isequal(u,v)
    if (new==2) and (i<(n-1)) : t +=' \\\\';
    tt[i]=t; t=''
  if new==1 : colsalign(tt)
  for i in range(n) : print(tt[i])
  if new==2 : print('Test Hermitian: '+str(ok))
  return tt

def vecshow(s,x) :
  n=len(x); print(s); t=''
  for k in range(n) : t +=' '+displaynb(x[k])
  print(t)

def div(p,q) :
  if p>=0 : return int(p/q)
  else : return -int(-p/q)

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 jj3(j,j3, l,l3, s,s3) : # get coeffs of vector (j,j3). numbers halfints
  j2=div(j[0]*2,j[1]); j32=div(j3[0]*2,j3[1]) 
  l2=div(l[0]*2,l[1]); l32=div(l3[0]*2,l3[1]) 
  s2=div(s[0]*2,s[1]); s32=div(s3[0]*2,s3[1]) 
  ok=True; jmax=abs(l2)+abs(s2); jmin=abs(l2-s2); z=[0,1]; sign=0
  if (s2 !=1) : print('spin s '+str(s)+' not supported'); ok=False
  if (j2>jmax)or(j2<jmin) : print('j='+str(j)+' outside |l-s|,(l+s)'); ok=False
  if (j32>j2)or(j32<-j2)or(l32>l2)or(l32<-l2)or(s32>s2)or(s32<-s2) :
    print('one of '+str([j3,l3,s3])+' out of range'); ok=False
  if (j2>l2) : # contribute l3,s3= (j3-0.5,+) and (j3+0.5,-)
    if (l32==(j32-1))and(s32==1) : z=[l2+j32+1,2*l2+2]; sign=1
    if (l32==(j32+1))and(s32==-1) : z=[l2-j32+1,2*l2+2]; sign=1
  else  : # contribute l3,s3= (j3+0.5,-) and (j3-0.5,+)
    if (l32==(j32+1))and(s32==-1) : z=[l2+j32+1,2*l2+2]; sign=1
    if (l32==(j32-1))and(s32==1) : z=[l2-j32+1,2*l2+2]; sign=-1
  if (not ok)or(z[0]==0) : z= [0,0,1,1,1]
  else : z=[sign,0,1,z[0],z[1]]
  return z

def testcleb(j,l,s=[1,2]) : # Clebsch-Gordan case spin 1/2 only
  # make a matrix of coeffs for (j,j3) in (l,l3)(x)(s,s3)
  # return matrix mx, rows in order j3max...j3min
  # mx rows ordered: (lmax,smax)...(lmax..smin) (lmax-1,smax)...
  noisy=2 # example l=[1,1]; j=[3,2]; s fixed for spin 1/2
  nl= div(2*l[0],l[1])+1
  ns= div(2*s[0],s[1])+1
  nj= div(2*j[0],j[1])+1; mx=[[]]*nj 
  for ij in range(nj) :
    j3=j if (ij==0) else [j3[0]-j3[1],j3[1]]; hj=0; mx[ij]=[[]]*(nl*ns) 
    if ij==0 : s3val=[[]]*(nl*ns); l3val=[[]]*(nl*ns)
    for il in range(nl) :
      l3=l if (il==0) else [l3[0]-l3[1],l3[1]] 
      for ks in range(ns) :
        s3=s if (ks==0) else [s3[0]-s3[1],s3[1]] 
        if ij==0 : s3val[hj]=s3; l3val[hj]=l3
        z= jj3(j,j3, l,l3,s,s3); normalize(z); mx[ij][hj]=z; hj+=1
        if noisy==1 : print(str([ij,il,ks,z]))
  if noisy==2 : matshow('',mx,1)
  return mx,s3val,l3val

def rootclass(x) : # x is list of real 5-tuple numbers, are they additive?
  noisy=False; # maybe root-incompatible subsets cancel?
  if noisy : print('  rootclass'+str(x))
  n=len(x); q=[0]*n; p=[0]*n; r=[0]*n; clas=[0]*n; pclas=[0]*n; ncl=0
  for i in range(n) : p[i]=x[i][3];q[i]=x[i][4];r[i]=x[i][0]
  qq= smallestmult(q); ls=12 
  for i in range(n) : 
    p[i]= div(qq*p[i],q[i]); j=0; found=False 
    for k in range(2,ls) : # take squares out of root
      z= k*k
      while hasfact(p[i],z) : p[i]=div(p[i],z); r[i] *= k
    while (not found) and (j<ncl) : found=(p[j]==pclas[j]); j+=1
    if not found : pclas[j]=p[i]; clas[i]=j; ncl +=1
    else : clas[i]= j-1 # equivalence class of same p factor
  if noisy : print('p-classes: '+str(pclas[:ncl])); 
  sp=[0]*ncl; sq=[1]*ncl
  for k in range(ncl) :
    for i in range(n) :
      if clas[i]==k : sp[k]= r[i]*sq[k]+ sp[k]*x[i][2]; sq[k]=sq[k]*x[i][2]
  if noisy : print('p-sums: '+str(sp)) # only one of them non-zero! 
  for i in range(n) : x[i]=[r[i],0,x[i][2],p[i],qq]
  return clas,pclas[:ncl]

def scalprod(x,y) : # square-root compatible subset
  n=len(x); m=len(y); w=[0]*n; z=0; bigbug=False
  if bigbug : print('x= '+str(x)+'\ny= '+str(y))
  if n != m : print('scalprod length mismatch.')
  for i in range(n) : w[i]=multip(x[i],y[i]); z=addit(z,w[i]) 
  if bigbug : print('w= '+str(w))
  return z

def quotient(x,y) : z=recip(y); return multip(z,x)
def iszero(x) :
  if type(x)==type(1) : return (x==0)
  else : return (x[0]==0)and(x[1]==0)

def orthogonize(y,x) : # make vector y orthonormal to vectorlist x[i]
  # as startvector y try maximal n linear indep ones, y=delta(i,k)
  n=len(y);m=len(x); ok=True;sok=True; z=[0]*n; norm=[[]]*m;pxy=[[]]*m
  for i in range(m) : ok= ok and (len(x[i])==n)
  if not ok : print('Vector Length Error')
  for i in range(m) :
    for j in range(i,m) :
      sp=scalprod(x[i],x[j]) # root-non-compatible here?
      if i==j : norm[i]=sp; pxy[i]=scalprod(y,x[i])
      if i != j : sok= sok and iszero(sp)
  if not sok : print('Orthogonality error')
  for k in range(n) : z[k]= reformat(y[k])
  for i in range(m) : 
    u= quotient(pxy[i],norm[i])
    for k in range(n) : v=multip(u,x[i][k]); z[k]=lincomb(z[k],v,1,-1)
  nz= scalprod(z,z); ok=True
  if iszero(nz) : print('Orthogonize result zero.'); ok=False
  else :
    for i in range(m) : sp=scalprod(x[i],z); ok= ok and iszero(sp)
    if not ok : print('Result z not orthogonal.')
    nz= sqroot(nz); qz= recip(nz)
    for k in range(n) : z[k] = multip(z[k],qz)
    sp=scalprod(z,z); sp=lincomb(sp,reformat(1),1,-1); nok= iszero(sp)
    if not nok : print('Result z not normalized.') 
  return z,ok

def signflip(z) : # make first entry positive
  n=len(z); flip= z[0][0]<0; nf= n if flip else 0
  for i in range(nf) : z[i][0]= -z[i][0]
  if flip : print('Signflip')

def orthoplement(x,noisy) : # orthogonal complement
  # if all of x root-compatible, forget the roots
  # solve linear equ on rationals, then normalize
  n=len(x); m=len(x[0]); ok=True
  if noisy : print(' orthoplem '+str(n)+' vectors of length '+str(m))
  if (n+1)!=m : print('Bad number of of vectors!') 
  if m==2 : return [scale(x[0][1],1),scale(x[0][0],-1)] # other sign?
  for i in range(n) : clas,pclas=rootclass(x[i]); ok= ok and (len(pclas)==1)
  if not ok : print('Roots not compatible.')
  if noisy : print('Orthoplement '+str(n))
  n2=n; n2=0 #dbg, n2=n is fatally bad
  for k in range(n2) :
    print(str(x[k]))
    for j in range(len(x[k])) :
      if len(x[k][j])>4 : x[k][j][3]=1;x[k][j][4]=1 # roots away is fatal
  y=[0]*m; found=False; i=0
  while (not found) and (i<m) : # try all unit vector startpoints
    y[i]=1; z,found=orthogonize(y,x); y[i]=0; i+=1
  if not found : print('Cannot orthoplement m='+str(m)+', exit.'); exit() 
  else : signflip(z)
  return z
  # faster? guess int-only startvector y; Fi=prod(k,k!=i,(xk,xk))(xi,y);
  # G=prod(k,(xk,xk)); y'= Gy- sum(i,Fi xi) . if zero, modify y. int-solution?

def initclebsch(ja2,jb2,noisy) : # ja,jb half-integral, ja2,jb2 their double
  # matrices quad-index: [n][d][ja2+1][jb2+1], d=(da+db-x)...|da-db|(2)
  max2=ja2+jb2; min2=abs(ja2-jb2); n=div(max2-min2,2)+1; da=ja2+1;db=jb2+1
  cg=[[]]*n; sd=0; d=max2+1; s='CG-Matrices Cols='+str(da*db)+' Rows=' 
  for i in range(n) :
    cg[i]=[[]]*d ; s+=' '+str(d); sd+=d
    for j in range(d) :
      cg[i][j]=[[]]*da
      for k in range(da) : cg[i][j][k]=[0]*db # defines cg[i,j,k,h]
    d -= 2    
  j2=ja2+jb2; i=0; cg[i][0][0][0]=1
  if noisy : print(s+' Sd='+str(sd))
  return cg # quadruple-index array: j,m, ma,mb

def gathervectors(cgm,jmax2,m2, ja2,jb2, noisy) : # cgm vectors filled at m2?
  # extract relevant row vectors from da*db-size submatrices
  # put together the row vectors for j2.3==m2 
  # to start (j2=m2), find an orthogonal vector to those found here
  n=len(cgm); mydim=m2+1; k=0; x=[[]]*n # all cgm with higher dims contribute
  terms=[0]*n ; nj=[0]*n; j2=m2; h=0; jm2=jmax2
  for i in range(n) :
    cg=cgm[i]; cdim=len(cg); okdim= cdim>mydim # if ok, add a vector x[k]
    if (noisy and okdim) : print('gather from submatrix dim='+str(cdim))
    # extract the da*db matrix entries to m2
    # cg submatrix i, row that counts belongs to m2: 
    ma2=jmax2;j=0; y=[0]*(jmax2+1) # items in cg start with max=ma2, min=mb2
    while ma2>ja2 : ma2 -=1
    k= div(jm2-m2,2) # relevant row in matrix cg
    while okdim and (ma2>=(-jmax2)) : # scan diagonal ma2+mb2=m2
      mb2=m2-ma2; ok= (abs(ma2)<=ja2)and(abs(mb2)<=jb2); z=0
      if ok : ia=div(ja2-ma2,2);ib=div(jb2-mb2,2); z=cg[k][ia][ib]; terms[i]+=1
      if noisy : print('     ma2,mb2,ok='+str([ma2,mb2,ok])) 
      if ok : y[j]=z; j+=1; dn= 1 if (not iszero(z)) else 0; nj[i]+=dn 
      ma2 -=2
    if (terms[i]>0) and (nj[i]>0) : x[h]=y[:j]; h+=1
    jm2 -= 2
  if noisy : print('Gather Terms: '+str(terms)+' NonZero: '+str(nj))
  return x[:h]

def downsweep(cgm, z, j2, ja2,jb2,noisy) :  
  # one diagonal z is ready: for m2=j2, all ma,mb with ma+mb=m2 are defined.
  # m index ordering is top down. input vector z = diagonal m2=j2.
  nz=len(z); da=ja2+1; db=jb2+1; ix= div(ja2+jb2-j2,2); cg=cgm[ix]; d=j2+1; k=0
  if noisy : print('downsweep ix,nz,j2,ja2,jb2='+str([ix,nz,j2,ja2,jb2]))
  if noisy : print(' input vector='+str(z));
  jmax2=ja2+jb2
  ma2=jmax2 # items in z ordered to start with maximal ma2, minimal mb2?
  while ma2>ja2 : ma2 -=1  # j2=2 ja2=jb2=1, must start ma2=1 not 2!
  while ma2>=(-jmax2) : # insert start-diagonal from vector z to cg[0] row
    mb2=j2-ma2; ok= (abs(ma2)<=ja2)and(abs(mb2)<=jb2); i=0;ia=-1;ib=-1
    if ok :
      ia=div(ja2-ma2,2); ib=div(jb2-mb2,2); cg[i][ia][ib]=z[k]; k+=1
      if noisy: print(' ins ma2,mb2, i,ia,ib,z='+str([ma2,mb2,i,ia,ib,z[k-1]]))
    ma2 -=2
  m2=j2; i=0; nput=0; imax=j2 # reach source row cg[2j]
  while m2>(-jmax2) : # fetch data from index m2
    ma2=jmax2
    if noisy : print(' Pass i,j2,m2,ja2='+str([i,j2,m2,ja2]))
    while ma2>ja2 : ma2 -=1
    while ma2>=(-jmax2) :
      mb2=m2-ma2; ok= (abs(ma2)<=ja2)and(abs(mb2)<=jb2)and(i<imax)
      if ok :  # add stuff to cg[ia+1][ib] and cg[ia][ib+1] if in range
        ia=div(ja2-ma2,2); ib=div(jb2-mb2,2); x=cg[i][ia][ib]
        q=j2*(j2+2)-m2*(m2-2) # divisor for norm
      if ok and ((ia+1)<da) :
        if noisy : print(' put1 i,ia,ib'+str([i,ia,ib, i+1,ia+1,ib,x]))
        y=ja2*(ja2+2)-ma2*(ma2-2); y=[y,q]; y= sqroot(y); nput+=1
        y=multip(y,x); z=cg[i+1][ia+1][ib]; cg[i+1][ia+1][ib]=addit(z,y)
      if ok and ((ib+1)<db) :
        if noisy : print(' put2 i,ia,ib'+str([i,ia,ib, i+1,ia,ib+1,x]))
        y=jb2*(jb2+2)-mb2*(mb2-2); y=[y,q]; y= sqroot(y); nput+=1
        y=multip(y,x); z=cg[i+1][ia][ib+1]; cg[i+1][ia][ib+1]=addit(z,y)
      ma2 -= 2
    m2 -=2; i+=1
  if noisy : print('Downsweep done, nput='+str(nput))

def cgshow(j2,cg) : 
  nj=len(cg); na=len(cg[0]); nb=len(cg[0][0]); ja2=na-1; jb2=nb-1; m2=j2
  print('CG-Matrix Nj,j2,ja2,jb2='+str([nj,j2,ja2,jb2])); s=''
  for i in range(nj) :
    cgi=cg[i]; na=len(cgi); nb=len(cgi[0]) 
    print('MatrixRow '+str(i)+' J2,M2='+str(j2)+','+str(m2))
    for j in range(na) :
      for k in range(nb) : z=finetune(cgi[j][k]); s+=' '+displaynb(z)
    print(s); s=''; m2 -=2
  
def clebschgordan(ja2,jb2,noisy=True) : # ja2,jb2 are double of j-quanta.
  # ja=(ja2/2) may be called 'L' and jb=(jb2/2) called 'S'
  cgm= initclebsch(ja2,jb2,noisy)
  j2=ja2+jb2;k2=j2; jmax2=j2; jmin2=abs(ja2-jb2);  i=0; z=[1]
  while j2>= jmin2 :
    if i>0 : x=gathervectors(cgm,jmax2,j2,ja2,jb2,noisy)
    if i>0 : z=orthoplement(x,noisy)
    downsweep(cgm, z, j2,ja2,jb2, noisy); j2-=2; i+=1
  if noisy :
    for i in range(len(cgm)) : cgshow(k2,cgm[i]); k2-=2
  return cgm

def wikidump(cg) : # list nonzero entries in latex style
  def wnum(n2) :
    s='' if (n2>=0) else '-'; na=abs(n2); whole= (na%2)==0; n=div(na,2)
    if whole : s+=str(n)
    else : s+='\\tfrac{'+str(na)+'}2'
    return s
  def label(l2,l32,s2,s32,j2,j32,x) :
    s='\\langle'+wnum(l2)+','+wnum(s2)+';'+wnum(l32)+','+wnum(s32)+'|'
    s+=wnum(j2)+','+wnum(j32)+'\\rangle='+encodenb(reformat(x))+'\\\\\n'
    return s 
  nj=len(cg); na=len(cg[0][0]); nb=len(cg[0][0][0]); s='\\begin{array}{l}\n'
  for ij in range(nj) : 
    mj=len(cg[ij]) 
    for im in range(mj) :
      for ia in range(na) :
        for ib in range(nb) :
          x=cg[ij][im][ia][ib]; ok= not iszero(x)
          j2=mj-1; l2=na-1; s2=nb-1; j32=j2-2*im; l32=l2-2*ia; s32=s2-2*ib
          if ok : s+= label(l2,l32,s2,s32,j2,j32,x)
  s+='\\end{array}\n'; return s

def bugcheck(cgm,mxa,mxb) :
  def subcheck(mx,my) :
    na=len(mx); nb=len(my[0]); nc=len(my[0][0]); bugs=0
    for i in range(na) :
      h=0
      for j in range(nb) :
        for k in range(nc) :
          x=reformat(mx[i][h]); y=reformat(my[i][j][k]); z=lincomb(x,y,1,-1)
          h+=1; bugs+= (0 if iszero(z) else 1) 
    return bugs
  n=len(cgm); ok=(n==2)
  na=len(cgm[0]); nb=len(cgm[1]); ma=cgm[0]; mb=cgm[1]
  ok= ok and (na==len(mxa)) and (nb==len(mxb))
  if not ok : print('Dimensions wrong, n,na,nb='+str([n,na,nb]))
  wa=len(ma[0])*len(ma[0][0]); va=len(mxa[0]); ok= ok and (va==wa)
  wb=len(mb[0])*len(mb[0][0]); vb=len(mxb[0]); ok= ok and (vb==wb)
  if not ok : print('Also wrong, wa,wb,va,vb='+str([wa,wb,va,vb]))
  bugs= subcheck(mxa,ma)+subcheck(mxb,mb)
  print('Nb of mismatch: '+str(bugs))

def cgtest(n2=2) : # compare cgm to mxa,mxb, for spin-(1/2) 2nd factors. ok.
  # n2=2L added to 2S=1, n2=1,2,3...
  cgm= clebschgordan(n2,1)
  mxa,s,t =testcleb([n2+1,2],[n2,2])
  mxb,s,t =testcleb([n2-1,2],[n2,2])
  bugcheck(cgm,mxa,mxb) # special check for spin-(1/2), ok no mismatch.
  #clebschgordan(1,2); print('')

def cg_getrow(cg,i) : # extract full row of CG matrix
  na=len(cg[0][0]); nb=len(cg[0][0][0]); n=na*nb; z=[[]]*n 
  j=0; h=i; ia=0;ib=0
  while h>=len(cg[j]) : h-=len(cg[j]);j+=1 # row is cg[j][h]
  for k in range(n) : 
    z[k]=cg[j][h][ia][ib]; ib+=1; (ia,ib)= (ia+1,0) if (ib>=nb) else (ia,ib) 
  return z

def cg_getcol(cg,i) :
  na=len(cg[0][0]); nb=len(cg[0][0][0]); n=na*nb; z=[[]]*n 
  ia= div(i,nb); ib=i-ia*nb; j=0;h=0
  for k in range(n) : 
    z[k]=cg[j][h][ia][ib]; h+=1; (j,h)= (j+1,0) if (h>=len(cg[j])) else (j,h)
  return z

def orthotest(cg,mode,noisy) : # is cg orthogon.matrix? mode=0 rows =1 columns.
  na=len(cg[0][0]); nb=len(cg[0][0][0]); m=na*nb; x=[[]]*m 
  hint=['Rows','Cols']; nerr=0;pairs=0 
  for i in range(m) : x[i]= cg_getrow(cg,i) if(mode==0) else cg_getcol(cg,i)
  for i in range(m) :
    for j in range(i,m) :
      sp=scalprod(x[i],x[j]); pairs+=1
      if i==j : sp=lincomb(sp,reformat(1),1,-1)
      sok= iszero(sp); nerr= (nerr+1) if (not sok) else nerr
  if noisy : print('Orthonorm '+hint[mode]+' errs: '+str(nerr)+'/'+str(pairs))
  return nerr

def mirrortest(cg,noisy) :
  nonz=0 # test symmetry : <l,s;-l3,-s3|j,-j3>=(-1)^{l+s-j}<l,s;l3,s3|j,j3>
  nj=len(cg); na=len(cg[0][0]); nb=len(cg[0][0][0]); n=na*nb; nerr=0;pairs=0
  for ij in range(nj) : 
    mj=len(cg[ij]) 
    for im in range(mj) :
      for ia in range(na) :
        for ib in range(nb) :
          x=cg[ij][im][ia][ib]; y=cg[ij][mj-1-im][na-1-ia][nb-1-ib]
          j2=mj-1; a2=na-1; b2=nb-1; z=a2+b2-j2; nonz+=(0 if iszero(x) else 1)
          if (z%2)==1 : print('Bug '+str([j2,a2,b2])); exit()
          z=div(abs(z),2); sign=1 if ((z%2)==0) else (-1); pairs+=1 
          w=lincomb(reformat(x),reformat(y),1,-sign); ok= iszero(w)
          if not ok : nerr +=1; print(' Err'+str([ij,im,ia,ib,x,y]))
  if noisy :
    print('Mirror errs/pairs/nonz '+str(nerr)+'/'+str(pairs)+'/'+str(nonz))
  return nerr
  
def racah(j2,l2,s2,l32) : # racah formula, alternative j sequence start
  def facto(n) : # n!
    y=1; i=n
    while i>1 : y*=i; i-=1
    return y
  def fac2(n) : # (n/2)!
    if (n<=2) : return 1
    if (n%2)!=0 : print('fac2 wrong argument '+str(n)+'. exit.'); exit()
    i=div(n,2); return facto(i)
  s32=j2-l32; pp=facto(j2+1)*fac2(l2+s2-j2)
  pq= fac2(l2+s2+j2+2)*fac2(j2+l2-s2)*fac2(j2+s2-l2)
  qp= fac2(l2+l32)*fac2(s2+s32); qq=fac2(l2-l32)*fac2(s2-s32)
  x=div(l2-l32,2); sign= 1 if (x%2)==0 else -1
  z=sqroot([pp*qp,pq*qq]); z[0]=sign*z[0]; return z 

def racahtest(cg,noisy) : # test all cg[j][0] lines against the formula
  n=len(cg); j2=len(cg[0])-1; count=0; errs=0
  for i in range(n) :
    row=cg[i][0]; nl=len(row); ns=len(row[0]); l2=nl-1; l32=l2
    for k in range(nl) :
      s2= ns-1; s32=s2
      for h in range(ns) : 
        ok= (l32+s32)==j2; z=row[k][h]
        if ok : y=racah(j2,l2,s2,l32); count+=1; w=lincomb(y,reformat(z),1,-1)
        if ok : errs = errs if iszero(w) else (errs+1)
        s32 -=2
      l32 -=2
    j2 -=2
  if noisy : print('Racahtest errors='+str(errs)+'/'+str(count))
  return errs

def cgexample(l2,s2,noisy) :
  cgm= clebschgordan(l2,s2,noisy); errs=0; s=''
  errs += orthotest(cgm,0,noisy)
  errs += orthotest(cgm,1,noisy)
  errs += mirrortest(cgm,noisy)
  errs += racahtest(cgm,noisy)
  if errs==0 : s=wikidump(cgm); print('<math>'+s+'</math>')
  return errs,s

def fulltest() :
  tit=['','(1/2)','1','(3/2)','2']
  for i in range(1,5) :
    for k in range(1,5) :
      title="\n\'\'\'Clebsch-Gordan L="+tit[i]+" S="+tit[k]+"\'\'\'<br>" 
      print(title); cgexample(i,k,False)
  #errs=cgexample(6,6,True) 

fulltest()