package org.nevec.rjm; import java.math.BigInteger; import java.util.Scanner; import it.cavallium.warppi.util.Error; /** * Exact representations of Wigner 3jm and 3nj values of half-integer arguments. * * @see R. J. Mathar, Corrigendum to * "Universal factorzation fo 3n-j (j>2) symbols ..[J. Phys. A: Math. Gen.37 (2004) 3259]" * * @see R. J. Mathar, Symmetries in * Wigner 18-j and 21-j Symbols * @since 2011-02-15 * @author Richard J. Mathar */ public class Wigner3j { /** * Test programs. This supports three types of direct evaluations:
* java -cp . org.nevec.rjm.Wigner3j 3jm 2j1+1 2j2+1 2j3+1 2m1+1 2m2+1 2m3+1 *
* java -cp . org.nevec.rjm.Wigner3j 6j 2j1+1 2j2+2 .. 2j6+1
* java -cp . org.nevec.rjm.Wigner3j 9j 2j1+1 2j2+2 .. 2j9+1
* The first command line argument is one of the three tags which determine * whether a 3jm, a 6j or a 9j symbol will be computed. The other arguments * are 6 or 9 integer values, which are the physical (half-integer) values * multplied by 2 and augmented by 1. The order of the 6 or 9 values is as * reading the corresponding standard symbol as first row, then second row * (and for the 9j symbol) third row. * * @since 2011-02-15 * @author Richard J. Mathar * @throws Error */ static public void main(final String args[]) throws Error { if (args[0].compareTo("6j") == 0) { try { final String m1 = "6"; final String t1 = "1 2 -3 -1 5 6"; final String t2 = "4 -5 3 -4 -2 -6"; String j = ""; for (int i = 1; i <= 6; i++) { j += args[i] + " "; } final BigSurdVec w = Wigner3j.wigner3j(m1, t1, t2, j); System.out.println(w.toString()); } catch (final Exception e) { System.out.println(e.getMessage()); } } else if (args[0].compareTo("9j") == 0) { try { final String m1 = "9"; final String t1 = "1 3 2 4 6 5 7 9 8"; final String t2 = "2 8 5 6 3 9 7 4 1"; String j = ""; for (int i = 1; i <= 9; i++) { j += args[i] + " "; } final BigSurdVec w = Wigner3j.wigner3j(m1, t1, t2, j); System.out.println(w.toString()); } catch (final Exception e) { System.out.println(e.getMessage()); } } else if (args[0].compareTo("3jm") == 0) { final int j1 = new Integer(args[1]).intValue(); final int j2 = new Integer(args[2]).intValue(); final int j3 = new Integer(args[3]).intValue(); final int m1 = new Integer(args[4]).intValue(); final int m2 = new Integer(args[5]).intValue(); final int m3 = new Integer(args[6]).intValue(); try { BigSurd w = Wigner3j.wigner3jm(j1, j2, j3, m1, m2, m3); System.out.println(w.toString()); w = w.multiply(new BigSurd(j3 + 1, 1)); System.out.println("CG factor sqrt" + (j3 + 1) + "sign " + (j2 - j2 - m3) / 2 + " " + w.toString()); } catch (final Exception e) { System.out.println(e.getMessage()); } } else { System.out.println("usage:"); System.out.println(args[0] + " 6j 2j1+1 2j2+1 2j3+1 2j4+1 2j5+1 2j6+1"); System.out.println(args[0] + " 9j 2j1+1 2j2+1 2j3+1 2j4+1 2j5+1 2j6+1.. 2j9+1 "); System.out.println(args[0] + " 3jm 2j1+1 2j2+1 2j3+1 2m1+1 2m2+1 2m3+1 "); } } /* Wigner3j.main */ /** * The Wigner 3jm symbol (j1,j2,j3,m1,m2,m3). All arguments of the function * are the actual parameters multiplied by 2, so they all allow an integer * representation. * * @param j1 * integer representing 2*j1 * @param j2 * integer representing 2*j2 * @param j3 * integer representing 2*j3 * @param m1 * integer representing 2*m1 * @param m2 * integer representing 2*m2 * @param m3 * integer representing 2*m3 * @return The value of the symbol. Zero if any of the triangular * inequalities is violated or some parameters are out of range. * @since 2011-02-13 * @author Richard J. Mathar * @throws Error */ static public BigSurd wigner3jm(final int j1, final int j2, final int j3, final int m1, final int m2, final int m3) throws Error { final Rational J1 = new Rational(j1, 2); final Rational J2 = new Rational(j2, 2); final Rational J3 = new Rational(j3, 2); final Rational M1 = new Rational(m1, 2); final Rational M2 = new Rational(m2, 2); final Rational M3 = new Rational(m3, 2); return Wigner3j.wigner3jm(J1, J2, J3, M1, M2, M3); } /* wigner3jm */ /** * Wigner 3jn symbol. For the 6j symbol, the input of the 3 lines is * "1 2 3 1 5 6", "4 5 3 4 2 6" "2j1+1 2j2+1 2j3+1 2l1+1 2l2+1 2l3+1" * * @param m1 * The information on the number of angular momenta. * @param t1 * The list of one half of the triads, indexing j, whitespace * separated * @param t2 * The list of the second half of the triads, indexing j, * whitespace separated * @param j * The list of the integer values of the angular momenta. They * are actually the doubled j-values plus 1, whitespace * separated. Only as many as announced by the m1 parameter are * used; trailing numbers are ignored. * @see A. Bar-Shalom and M. Klapisch, * NJGRAF... * , Comp. Phys Comm. 50 (3) (1988) 375 * @since 2011-02-13 * @since 2012-02-15 Upgraded return value to BigSurdVec * @author Richard J. Mathar * @throws Error */ static public BigSurdVec wigner3j(final String m1, final String t1, final String t2, final String j) throws Error { /* * The first number in the line "m" is the number of angular momenta. * The rest of the line is ignored. */ Scanner s = new Scanner(m1); final int m = s.nextInt(); if (m % 3 != 0) { s.close(); throw new IllegalArgumentException("Angular momenta " + m + " not a multiple of three."); } /* * Scan the numbers in the line "j". Excess numbers beyond what has been * announced in the "m" line are ignored. */ final int[] jvec = new int[m]; final int[] tvec = new int[2 * m]; s.close(); /* * the third row contains positive 2j+1. */ s = new Scanner(j); int ji = 0; while (s.hasNextInt() && ji < m) { jvec[ji++] = s.nextInt(); if (jvec[ji - 1] < 1) { s.close(); throw new IllegalArgumentException("Illegal value " + jvec[ji - 1] + " for 2j+1."); } } s.close(); /* * the first two rows contain signed values of indices into the j list */ s = new Scanner(t1); int ti = 0; while (s.hasNextInt()) { tvec[ti++] = s.nextInt(); } s.close(); s = new Scanner(t2); while (s.hasNextInt()) { tvec[ti++] = s.nextInt(); } /* * Basic sanity checks. All indices in the first two lines address a * number in the third line, and each index occurs exactly twice. */ if (ji % 3 != 0) { s.close(); throw new IllegalArgumentException("j-count " + ji + " not a multiple of three."); } if (ti != 2 * ji) { s.close(); throw new IllegalArgumentException("triad-count " + ti + " not twice j-count " + ji); } final int[] jfreq = new int[m]; for (ji = 0; ji < jfreq.length; ji++) { jfreq[ji] = 0; } /* * maintain a 0-based index which shows where the j-value has its first * and second occurrence in the flattened list of triads. */ final int[][] jhash = new int[m][2]; for (ti = 0; ti < 2 * m; ti++) { final int t = tvec[ti]; if (t == 0 || Math.abs(t) > jvec.length) { s.close(); throw new IllegalArgumentException("Triad index " + t + " out of bounds"); } if (jfreq[Math.abs(t) - 1] >= 2) { s.close(); throw new IllegalArgumentException("Node " + t + " referenced more than twice"); } jhash[Math.abs(t) - 1][jfreq[Math.abs(t) - 1]] = ti; jfreq[Math.abs(t) - 1]++; } /* * Move on from the 2j+1 values of the input to the j-values. Subtract * one and divide through 2. */ final Rational[] J = new Rational[jvec.length]; for (ji = 0; ji < jvec.length; ji++) { J[ji] = new Rational(jvec[ji] - 1, 2); } /* * Convert the 1-based indices to 0-based indices, loosing the sign * information. */ final int[] triadidx = new int[tvec.length]; for (ti = 0; ti < tvec.length; ti++) { triadidx[ti] = Math.abs(tvec[ti]) - 1; } /* * The M-values are all null (undetermined) at the start. */ final Rational[] M = new Rational[J.length]; s.close(); return Wigner3j.wigner3j(tvec, J, M, triadidx); } /* wigner3j */ /** * Wigner 3jn symbol. Computes sum_{mi} (-1)^(j1-m1+j2-m2+...) * triad(triadidx[0..2])*triad(triadidx[3..5])*... where each factor is a * Wigner-3jm symbol with each sign of m_i occurring once at the * corresponding l-value. * * @param triadidx * 0-based indices into the list of J * @param J * The list of J-values * @param M * The list of M-values associated with the J. This contains null * where the parameter has not yet been set by an outer loop. * @since 2011-02-13 * @since 2012-02-15 Upgraded to return BigSurdVec * @author Richard J. Mathar * @throws Error */ static private BigSurdVec wigner3j(final int[] tvec, final Rational[] J, final Rational[] M, final int[] triadidx) throws Error { /* * The result of the computation. The sum over all m-combinations of the * triads. */ BigSurdVec res = new BigSurdVec(); /* * First step is to monitor the triangular conditions on the J. If at * least one is violated, the result is zero. Loop over the triads. */ for (int t = 0; t < triadidx.length; t += 3) { /* Ensure |J[t]-J[t+1]| <= J[t+2] <= J[t]+J[t+1] */ if (J[triadidx[t]].subtract(J[triadidx[t + 1]]).abs().compareTo(J[triadidx[t + 2]]) > 0) { return res; } if (J[triadidx[t]].add(J[triadidx[t + 1]]).compareTo(J[triadidx[t + 2]]) < 0) { return res; } } /* * the index of the preferred member of the triad list. Preference given * to those dangling in triads where alreaday two others are fixed, then * to members where at least one is fixed, then to smallest associated * J-values. */ int freeM = -1; int freeMrank = -1; for (int i = 0; i < triadidx.length; i++) { /* * found an m-value which has not yet been summed over. */ if (M[triadidx[i]] == null) { /* * two cases: value is fixed implicitly because already two * others values are set in the triad. or it is still to * maintain its own explicit loop. */ final int triadn = i / 3; final int triadr = i % 3; /* * the neighbors in the triad have indices triadn*3+ (tiradr+1) * mod 3 and triadn*3+(triadr+2) mod3 */ final int nei1 = 3 * triadn + (triadr + 1) % 3; final int nei2 = 3 * triadn + (triadr + 2) % 3; /* * found a candidate for which the two other values are already * set. */ if (M[triadidx[nei1]] != null && M[triadidx[nei2]] != null) { freeM = i; break; } else { /* * rough work load estimator: basically (2J1+1)*(2J2+1) */ Rational wt = J[triadidx[i]].multiply(2).add(1); if (M[triadidx[nei1]] == null) { wt = wt.multiply(J[triadidx[nei1]].multiply(2).add(1)); } if (M[triadidx[nei2]] == null) { wt = wt.multiply(J[triadidx[nei2]].multiply(2).add(1)); } final int thiswt = wt.intValue(); if (freeM < 0 || thiswt < freeMrank) { freeM = i; freeMrank = thiswt; } } } } if (freeM >= 0) { /* * found an m-value which has not yet been summed over. */ if (M[triadidx[freeM]] == null) { final Rational[] childM = new Rational[M.length]; for (int ji = 0; ji < M.length; ji++) { if (M[ji] != null) { childM[ji] = M[ji]; } } /* * two cases: value is fixed implicitly because already two * others values are set in the triad. or it is still to * maintain its own explicit loop. */ final int triadn = freeM / 3; final int triadr = freeM % 3; /* * the neighbors in the triad have indices triadn*3+ (triadr+1) * mod 3 and triadn*3+(triadr+2) mod3 */ final int nei1 = 3 * triadn + (triadr + 1) % 3; final int nei2 = 3 * triadn + (triadr + 2) % 3; if (M[triadidx[nei1]] == null || M[triadidx[nei2]] == null) { /* * The J-value is J[triadidx[freeM]]. Loop from -J to +J, * the allowed range. */ Rational newm = J[triadidx[freeM]].negate(); while (newm.compareTo(J[triadidx[freeM]]) <= 0) { childM[triadidx[freeM]] = tvec[freeM] > 0 ? newm : newm.negate(); res = res.add(Wigner3j.wigner3j(tvec, J, childM, triadidx)); newm = newm.add(Rational.ONE); } } else { /* * Set its value and the value at its companion j-value. Sum * of the three m-values in the triad is to be zero for a * non-zero contribution. */ Rational m1 = M[triadidx[nei1]]; Rational m2 = M[triadidx[nei2]]; /* * negate if these are the second occurrences of the J in * the triads */ if (tvec[nei1] < 0) { m1 = m1.negate(); } if (tvec[nei2] < 0) { m2 = m2.negate(); } /* m3 = -(m1+m2) */ final Rational newm = tvec[freeM] > 0 ? m1.add(m2).negate() : m1.add(m2); /* * No contribution if the m-value enforced by the other two * entries is outside the range -|J|..|J| enforced by its * associated J-value. One could essentially remove this * branching and let wigner3j() decide on this, but this is * inefficient. */ if (newm.abs().compareTo(J[triadidx[freeM]]) <= 0) { childM[triadidx[freeM]] = newm; res = res.add(Wigner3j.wigner3j(tvec, J, childM, triadidx)); } /* * zero contribution if this m-value cannot be set to any * value compatible with the triangular conditions. */ } return res; } } /* * reached the bottom of the loop where all M-values are assigned. Build * the product over all Wigner-3jm values and the associated sign. */ res = BigSurdVec.ONE; for (int ji = 0; ji < triadidx.length; ji += 3) { Rational m1 = M[triadidx[ji]]; Rational m2 = M[triadidx[ji + 1]]; Rational m3 = M[triadidx[ji + 2]]; /* * negate if these are associated with in-flowing vectors in the * triads */ if (tvec[ji] < 0) { m1 = m1.negate(); } if (tvec[ji + 1] < 0) { m2 = m2.negate(); } if (tvec[ji + 2] < 0) { m3 = m3.negate(); } res = res.multiply(Wigner3j.wigner3jm(J[triadidx[ji]], J[triadidx[ji + 1]], J[triadidx[ji + 2]], m1, m2, m3)); /* * if a partial product yields zero, the total product is zero, too, * and offers an early exit. */ if (res.signum() == 0) { return BigSurdVec.ZERO; } } /* * The overal sign is product_{J-Mpairs} (-1)^(J-M). This is an integer * because all the J-M are integer. */ Rational sig = new Rational(); for (int ji = 0; ji < J.length; ji++) { sig = sig.add(J[ji]).subtract(M[ji]); } /* * sign depends on the sum being even or odd. We assume that "sig" is * integer and look only at the numerator */ if (sig.a.abs().testBit(0)) { res = res.negate(); } return res; } /* wigner3j */ /** * The Wigner 3jm symbol (j1,j2,j3,m1,m2,m3). Warning: there is no check * that each argument is indeed half-integer. * * @param j1 * integer or half-integer j1 * @param j2 * integer or half-integer j2 * @param j3 * integer or half-integer j3 * @param m1 * integer or half-integer m1 * @param m2 * integer or half-integer m2 * @param m3 * integer or half-integer m3 * @return The value of the symbol. Zero if any of the triangular * inequalities is violated or some parameters are out of range. * @since 2011-02-13 * @author Richard J. Mathar * @throws Error */ static protected BigSurd wigner3jm(final Rational j1, final Rational j2, final Rational j3, final Rational m1, final Rational m2, final Rational m3) throws Error { /* * Check that m1+m2+m3 = 0 */ if (m1.add(m2).add(m3).signum() != 0) { return BigSurd.ZERO; } /* * Check that j1+j2+j3 is integer */ if (j1.add(j2).add(j3).isBigInteger() == false) { return BigSurd.ZERO; } /* * Check that |j1-j2|<=j3 <= |j1+j2| */ final Rational j1m2 = j1.subtract(j2); if (j1m2.abs().compareTo(j3) > 0) { return BigSurd.ZERO; } final Rational j1p2 = j1.add(j2); if (j1p2.abs().compareTo(j3) < 0) { return BigSurd.ZERO; } /* * Check that |m_i| <= j_i */ if (m1.abs().compareTo(j1) > 0 || m2.abs().compareTo(j2) > 0 || m3.abs().compareTo(j3) > 0) { return BigSurd.ZERO; } /* * Check that m_i-j_i are integer. */ if (!m1.subtract(j1).isBigInteger() || !m2.subtract(j2).isBigInteger() || !m3.subtract(j3).isBigInteger()) { return BigSurd.ZERO; } /* * (-)^(j1-j2-m3)*delta(-m3,m1+m2)*sqrt[ (j3+j1-j2)! (j3-j1+j2)! * (j1+j2-j3)! /(j1+j2+j3+1)! * *(j3-m)!*(j3+m)!(j1-m1)!*(j1+m1)!*(j2-m2)!*(j2+m2)!] *sum_k * (-1)^k/[k!(j1+j2-j3-k)!(j1-m1-k)!(j2+m2-k)!(j3-j2+m1+k)!)*(j3-j1-m2+k * )!] */ /* * It is tacitly assumed that all the major j_i, m_i values are in the * integer range. This is implicitly plausible since otherwise the * execution times of the following loop over the k-values would be * immense. */ int j1j2jk = j1p2.subtract(j3).intValue(); int j1m1k = j1.subtract(m1).intValue(); int j2m2k = j2.add(m2).intValue(); int jj2m1k = j3.subtract(j2).add(m1).intValue(); int jj1m2k = j3.subtract(j1).subtract(m2).intValue(); int k = Math.max(0, -jj2m1k); k = Math.max(k, -jj1m2k); if (k > 0) { j1j2jk -= k; j1m1k -= k; j2m2k -= k; jj2m1k += k; jj1m2k += k; } final Factorial f = new Factorial(); Rational sumk = new Rational(); while (true) { final BigInteger d = f.at(k).multiply(f.at(j1j2jk)).multiply(f.at(j1m1k)).multiply(f.at(j2m2k)).multiply(f.at(jj2m1k)).multiply(f.at(jj1m2k)); if (k % 2 == 0) { sumk = sumk.add(new Rational(BigInteger.ONE, d)); } else { sumk = sumk.subtract(new Rational(BigInteger.ONE, d)); } j1j2jk--; j1m1k--; j2m2k--; jj2m1k++; jj1m2k++; if (j1j2jk < 0 || j1m1k < 0 || j2m2k < 0) { break; } k++; } /* * sign factor (-1)^(j1-j2-m3) */ if (j1m2.subtract(m3).intValue() % 2 != 0) { sumk = sumk.negate(); } k = j1m2.add(j3).intValue(); BigInteger s = f.at(k); k = j3.subtract(j1m2).intValue(); s = s.multiply(f.at(k)); k = j1p2.subtract(j3).intValue(); s = s.multiply(f.at(k)); k = j3.add(m3).intValue(); s = s.multiply(f.at(k)); k = j3.subtract(m3).intValue(); s = s.multiply(f.at(k)); k = j1.add(m1).intValue(); s = s.multiply(f.at(k)); k = j1.subtract(m1).intValue(); s = s.multiply(f.at(k)); k = j2.add(m2).intValue(); s = s.multiply(f.at(k)); k = j2.subtract(m2).intValue(); s = s.multiply(f.at(k)); k = j1p2.add(j3).intValue(); k++; final Rational disc = new Rational(s, f.at(k)); return new BigSurd(sumk, disc); } /* wigner3jm */ } /* Wigner3j */