import java.util.*;
/**
* A class which computes the combinatorics of a fundamental domain.
*
* @author Jonathan Poritz
* @version 1.7, 17 Sep 1998
*/
public class DomainCombinatorics {
/**
* A Vector of BoundaryArcs which form the boundary of the
* current fundamentaldomain
*/
Vector boundary;
/**
* The precision we require to consider two circles in the plane
* to be overlapping.
*/
public static final double CIRCLAPRES = .000001;
/**
* The precision we require to consider two angles to be different.
*/
public static final double ANGDIFPRES = .000001;
/**
* Constructor for DomainCombinatorics's using only the trace, axis
* distance and domain type.
*
* @param trace the trace whose domain we are to examine
* @param dist the axis distance for the domain in question
* @param domaintype the domaintype, given by the selected index of
* outputpanel's Choice fordordirichlet
*/
public DomainCombinatorics(Complex trace, double dist, int domaintype) {
this(trace, dist, domaintype, Complex.make_z11(trace));
}
/**
* Constructor for DomainCombinatorics's using only the trace, axis
* distance, domain type and (1,1) element of the generator.
*
* @param trace the trace whose domain we are to examine
* @param dist the axis distance for the domain in question
* @param domaintype the domaintype, given by the selected index of
* outputpanel's Choice fordordirichlet
* @param z11 the (1,1) element of the generator matrix at the given trace
*/
public DomainCombinatorics(Complex trace, double dist, int domaintype, Complex z11) {
this(trace, dist, domaintype, z11, Complex.make_maxpow(trace, z11));
}
/**
* Constructor for DomainCombinatorics's using the trace, axis distance,
* domain type, (1,1) element of the generator and corresponding maxpow.
*
* @param trace the trace whose domain we are to examine
* @param dist the axis distance for the domain in question
* @param domaintype the domaintype, given by the selected index of
* outputpanel's Choice fordordirichlet
* @param z11 the (1,1) element of the generator matrix at the given trace
* @param maxpow the maxpow corresponding to the other input data
*/
public DomainCombinatorics(Complex trace, double dist, int domaintype, Complex z11, int maxpow) {
// create the vector to hold the boundary's BoundaryArcs
boundary = new Vector();
// the +/- 1 circles are a good starting point, we'll always need them
BoundaryArc plusone;
BoundaryArc minusone;
if (trace.y == 0D) {
// here we are either elliptic, parabolic or hyperbolic
// make an empty BoundaryArc
plusone = new BoundaryArc();
if (Math.abs(trace.x) >= 2D) // hyerbol or parabol: only need indx 1
plusone.index = 1;
else {
// here be elliptic elements
// this z11 is e^theta, compute theta
double theta = Math.atan2(-z11.y, z11.x);
// now find the power of z11 for which the maximum height of the
// corresponding circle is greatest, up to the power maxpow
double maxheight = 0D;
int maxheightindex = 0;
for (double k=1; k <= maxpow; k++) {
double ktheta = k*theta;
double skt = Math.sin(ktheta);
double ckt = Math.cos(ktheta);
// try both the positive and negative powers, to see which
// gives the greatest height
double height = Math.abs(1D/skt)-(ckt/skt);
if (height > maxheight) {
maxheight = height;
maxheightindex = (int) k;
}
height = Math.abs(-1D/skt)+(ckt/skt);
if (height > maxheight) {
maxheight = height;
maxheightindex = -((int) -k);
}
}
// here, then, is the index of the highest circle, note that
// this may not be a positive index, despite the name
plusone.index = maxheightindex;
}
// make a negative copy of the plusone BoundaryArc, put both
// in the Vector boundary and return
minusone = BoundaryArc.makeNeg(plusone);
boundary.addElement(plusone);
boundary.addElement(minusone);
return;
}
// now loxodromic elements. make the +/- 1 BoundaryArcs
// start and end angles are set to the nonsensical values of 0 and -1;
// these angles should either never be important, if the domain is of
// type {1}, or should be reset to reasonable values if there are
// intersections
plusone = new BoundaryArc(z11, dist, 1, domaintype, 0D, -1D);
minusone = BoundaryArc.makeNeg(plusone);
// put them in the boundary Vector
boundary.addElement(plusone);
boundary.addElement(minusone);
// if we are outside the circle of radius 2 in the trace plane or have
// zero axis distance in a Dirichlet domain, +/- 1 is the entire boundary
if ((trace.modulus() >= 2D) || ((domaintype == 1) && (dist == 0D)))
return;
// also the entire boundary is the +/- 1 circles if they do not intersect
double cendist = BoundaryCircle.distance(plusone, minusone);
if (cendist > 2*plusone.rad)
return;
double angoffset = Math.acos(cendist/(2D*plusone.rad));
double cenlineang = Math.atan2((plusone.cen.y - minusone.cen.y),(plusone.cen.x - minusone.cen.x));
// +/- 1 boundary circles intersect, set the beginning and end of their
// arcs which are outside their negatives
minusone.start = fixAngle(cenlineang + angoffset);
minusone.end = minusone.start + 2D*Math.PI - 2D*angoffset;
plusone.start = fixAngle(minusone.start + Math.PI);
plusone.end = plusone.start + 2D*Math.PI - 2D*angoffset;
Complex z11powers = new Complex(z11);
// we'll now loop through all powers of the generator up to maxpow and
// see if the corresponding positive or negative boundary circle
// contributes to the boundary
g: for(int i=2; i <= maxpow; i++) {
// make the next power of z11, the corresp pos boundary arc and its neg
z11powers = Complex.multiply(z11powers, z11);
BoundaryArc nextpos = new BoundaryArc(z11powers, dist, i, domaintype, 0, -1);
BoundaryArc nextneg = BoundaryArc.makeNeg(nextpos);
// the following are the number and values of the angles on the
// boundary circle nextpos where it intersects other boundary circles
int anglecnt = 0;
double[] angles = new double[2*boundary.size()+3];
// first see if and where nextpos intersects nextneg
cendist = BoundaryCircle.distance(nextpos, nextneg);
if (cendist <= 2*nextpos.rad) {
angoffset = Math.acos(cendist/(2D*nextpos.rad));
cenlineang = Math.atan2((nextneg.cen.y - nextpos.cen.y),(nextneg.cen.x - nextpos.cen.x));
if (angoffset == 0D) {
angles[0] = cenlineang;
anglecnt = 1;
}
else {
angles[0] = cenlineang - angoffset;
angles[1] = cenlineang + angoffset;
anglecnt = 2;
}
}
// next loop through the current list of boundary circles to
// see if and where they intersect nextpos
for(int j=0; j < boundary.size(); j++) {
// working on the jth BoundaryArc in the Vector boundary
BoundaryArc arcj = ((BoundaryArc) boundary.elementAt(j));
double cendistnptoj = BoundaryCircle.distance(nextpos, arcj);
// if arcj contains nextpos completely, arcj does not contribute to
// the boundary at all, and we can go to the next step of the loop g
if ((cendistnptoj + nextpos.rad) <= arcj.rad)
continue g;
// if nextpos contains arcj or they are too far apart to intersect,
// continue the current loop
if (((cendistnptoj + arcj.rad) < nextpos.rad) || ((arcj.rad + nextpos.rad) < cendistnptoj))
continue;
// have at least one intersection; compute and put in array of angles
angoffset = Math.acos((nextpos.rad*nextpos.rad+cendistnptoj*cendistnptoj-arcj.rad*arcj.rad)/(2D*nextpos.rad*cendistnptoj));
cenlineang = Math.atan2((arcj.cen.y - nextpos.cen.y),(arcj.cen.x - nextpos.cen.x));
if (angoffset == 0D)
angles[anglecnt++] = cenlineang;
else {
angles[anglecnt++] = cenlineang - angoffset;
angles[anglecnt++] = cenlineang + angoffset;
}
}
// fix all the angles we just made
for (int j=0; j=0; ) {
for (int jj=0; jj angles[jj+1]) {
double switcheroo = angles[jj];
angles[jj] = angles[jj+1];
angles[jj+1] = switcheroo;
}
}
}
// put on a last angle, so that between successive angles we have
// arcs of nextpos which do not intersect any other boundary circle
angles[anglecnt] = angles[0]+2*Math.PI;
// we will loop through and see if these arcs of nextpos lie inside
// some boundary circle and thus can be discarded from the boundary
boolean[] discard = new boolean[anglecnt];
for (int j=0; j nextneg.rad*nextneg.rad) {
// if this arc not contained in nextneg, it may contribute to the bdy
discard[j] = false;
for (int jj=0; jj < boundary.size(); jj++) {
// now check if this arc is inside any of the circles
// already known to contribute to the boundary
BoundaryCircle circlejj = ((BoundaryCircle) boundary.elementAt(jj));
if (Complex.distsq(midpoint, circlejj.cen) < circlejj.rad*circlejj.rad) {
// yes, this arc is hidden, set to discard and break out of loop
discard[j] = true;
break;
}
}
}
else {
// this arc hidden inside nextneg, discard
discard[j] = true;
}
}
// start putting together the next idea of the boundary
Vector newboundary = new Vector();
for (int j=0; j= arcj.rad) && ((cendistnptoj + arcj.rad) >= nextpos.rad) && ((arcj.rad + nextpos.rad) >= cendistnptoj)) {
angoffset = Math.acos((arcj.rad*arcj.rad+cendistnptoj*cendistnptoj-nextpos.rad*nextpos.rad)/(2D*arcj.rad*cendistnptoj));
cenlineang = Math.atan2((nextpos.cen.y - arcj.cen.y),(nextpos.cen.x - arcj.cen.x));
if (angoffset == 0D)
angles[anglecnt++] = cenlineang;
else {
angles[anglecnt++] = cenlineang - angoffset;
angles[anglecnt++] = cenlineang + angoffset;
}
}
// here we look for intersections with nextneg
double cendistnntoj = BoundaryCircle.distance(nextneg, arcj);
if (((cendistnntoj + nextneg.rad) >= arcj.rad) && ((cendistnntoj + arcj.rad) >= nextneg.rad) && ((arcj.rad + nextneg.rad) >= cendistnntoj)) {
angoffset = Math.acos((arcj.rad*arcj.rad+cendistnntoj*cendistnntoj-nextneg.rad*nextneg.rad)/(2D*arcj.rad*cendistnntoj));
cenlineang = Math.atan2((nextneg.cen.y - arcj.cen.y),(nextneg.cen.x - arcj.cen.x));
if (angoffset == 0D)
angles[anglecnt++] = cenlineang;
else {
angles[anglecnt++] = cenlineang - angoffset;
angles[anglecnt++] = cenlineang + angoffset;
}
}
// now loop through and fix all these angles. this involves putting
// them in the interval [0,2Pi), then seeing if they are in the
// range (start,end) of arcj, or likewise with the angle + 2Pi
int newj = 0;
for (int jj=0; jj arcj.start) && (currangle < arcj.end)) {
angles[newj++] = currangle;
continue;
}
currangle += 2*Math.PI;
if ((currangle > arcj.start) && (currangle < arcj.end))
angles[newj++] = currangle;
}
anglecnt = newj;
// add the beginning and end of arcj and then bubblesort
// the angles in increasing order
angles[anglecnt++] = arcj.start;
angles[anglecnt++] = arcj.end;
for (int jj=anglecnt; --jj>=0; ) {
for (int jjj=0; jjj angles[jjj+1]) {
double switcheroo = angles[jjj];
angles[jjj] = angles[jjj+1];
angles[jjj+1] = switcheroo;
}
}
}
// now remove any possible duplicate angles
newj = 1;
for (int jj=1; jj ANGDIFPRES)
angles[newj++] = currangle;
}
anglecnt = newj - 1;
// now we can compute midpoints of all of the subarcs of arcj
// whose endpoints are successive angles of the array angles
for (int jj=0; jj nextneg.rad*nextneg.rad) && (Complex.distsq(midpoint, nextpos.cen) > nextpos.rad*nextpos.rad)) {
// if the midpoint is not inside nextpos or nextneg this subarc
// does contribute to the bdy, so add it to the Vector newboundary
BoundaryArc next = arcj.copyArc();
next.start = fixAngle(angles[jj]);
next.end = next.start + angles[jj+1] - angles[jj];;
newboundary.addElement(next);
}
}
}
// done fixing up newboundary, use it as the actual boundary
boundary = newboundary;
}
}
/**
* Make a string suitable for use in inputpanel's
* currdomaincomblabel describing this domain combinatorics.
* The string is either the domain type in the sense of Drumm and Poritz
* or a comma-separated list of indices (beginning and ending with two
* question marks and enclosed in square brackets) of sides that have been
* computed to appear on the boundary of the fundamental domain at
* infinity.
*
* @return the domain type or error side listing string
*/
public String makeLabel() {
// how many sides in the boundary
int siz = boundary.size();
// keep a list of just the indices of the sides
int[] indices = new int[siz];
// and the location in the Vector boundary of these indices
int[] location = new int[siz];
for(int i=0; i < siz; i++) {
indices[i] = ((BoundaryArc) boundary.elementAt(i)).index;
location[i] = i;
}
if (siz == 2) {
// if two faces, one should be the negative of the other;
// if not, convert boundary to String and return with questions
if (indices[1] != -indices[0])
return "?? "+boundary.toString()+" ??";
else
return "{"+Math.abs(indices[0])+"}";
}
// should always have at least an even number of faces;
// if not, convert boundary to String and return with questions
if (siz != 2*((int) Math.floor(((double) siz)/2D)))
return "?? "+boundary.toString()+" ??";
for (int i=siz; --i >= 0; ) {
//bubblesort the indices, keep the location of these indices
// in the Vector boundary up to date
for (int j=0; j < i; j++) {
if (indices[j] > indices[j+1]) {
int switcheroo = indices[j];
indices[j] = indices[j+1];
indices[j+1] = switcheroo;
switcheroo = location[j];
location[j] = location[j+1];
location[j+1] = switcheroo;
}
}
}
// if indices not appearing in positive/negative pairs,
// convert boundary to String and return with questions
for (int jj=0; jj < (siz/2); jj++)
if (indices[jj] != - indices[siz - jj - 1])
return "?? "+boundary.toString()+" ??";
int j = siz-1;
// find now the location, as a Complex number, of the counterclockwise
// end of the arc corresponding to the biggest index
BoundaryArc arcbig = (BoundaryArc) boundary.elementAt(location[j]);
Complex bigend = new Complex(arcbig.cen.x+arcbig.rad*Math.cos(arcbig.end), arcbig.cen.y+arcbig.rad*Math.sin(arcbig.end));
for ( ; --j >= 0; ) {
// break if bigend is on the circle of the jth arc; this means
// that the counterclockwise successor to arcbig is arcj
BoundaryArc arcj = (BoundaryArc) boundary.elementAt(location[j]);
if (Math.abs(Complex.distsq(arcj.cen, bigend)-arcj.rad*arcj.rad)= 2D*Math.PI)
return angle - 2D*Math.PI;
if (angle < 0D)
return angle + 2D*Math.PI;
return angle;
}
}