/*
 *$Log$
 * usage: SimLowerBound numberOfSwitches
 */

package lb;
import java.util.*;

import general.*;
import network.*;

class Node {
  double theta;
  double phi;
  double x;
  double y;
  double z;

  public Node(double theta, double phi) {
    this.theta = theta;
    this.phi = phi;
    this.x = Math.sin(theta)*Math.cos(phi);
    this.y = Math.sin(theta)*Math.sin(phi);
    this.z = Math.cos(theta);
  }

  public Node(double x, double y, double z) {
    double r = Math.sqrt(x*x+y*y+z*z);
    this.x = x/r;
    this.y = y/r;
    this.z = z/r;
    this.theta = Math.acos(this.z);
    this.phi = Math.atan2(this.y, this.x);
  }

  public String toString() {
    return("[" + x + "," + y + "," + z + "]");
  }

  /** 
    distance between two nodes on the sphere, with
    coordinates phi and theta 
    */
  public static double distance(Node a, Node b) {

    //    double cosd = Math.sin(a.theta)*Math.sin(b.theta)*Math.cos(a.phi-b.phi)+
    //      Math.cos(a.theta)*Math.cos(b.theta);
    double cosd = a.x*b.x + a.y*b.y + a.z*b.z;
    return Math.acos(cosd);
  }  
}

public class SimLowerBound {
  final static int stepb = 10;
  final static double maxb = 1e4;
  final static double[] D = {0, 1/Math.PI, 2/Math.PI, 4/Math.PI, 8/Math.PI};
  int n;
  Node[] nodes;
  double[][] dist;
  String LPHeader;

  public SimLowerBound(int n) {
    this.n = n;
    nodes = new Node[n];
    Random rdm = new Random();
    dist = new double[n][n];

    // generate uniform distribution on the surface of a sphere
    for (int i = 0; i < n; i++) {
      /*
      double phi = rdm.nextDouble()*2.0*Math.PI;
      double thetax = rdm.nextDouble()*Math.PI/2.0;
      double thetay = rdm.nextDouble();
      double theta;
      if (thetay > Math.sin(thetax)) {
	theta = Math.PI/2.0 - thetax;
	if (theta < 0) theta += Math.PI;
      }
      else
	theta = thetax;
      nodes[i] = new Node(theta, phi);
      */

      double x, y, z, r2;
      do {
	x = rdm.nextDouble()*2 - 1.0;
	y = rdm.nextDouble()*2 - 1.0;
	z = rdm.nextDouble()*2 - 1.0;
	r2 = x*x + y*y + z*z;
      }
      while (r2 > 1);
      nodes[i] = new Node(x, y, z);

      // calculate the distances
      for (int j = 0; j < i; j++) {
	dist[i][j] = Node.distance(nodes[i], nodes[j]);
	dist[j][i] = dist[i][j];
      }
      dist[i][i] = 0;
    }

    // generate LP Header
    // first, objective function
    StringBuffer sbuf = new StringBuffer("max: ");
    for (int i = 0; i < n; i++)
      for (int j = 0; j < i; j++)
	sbuf.append(" + " + dist[i][j] + trafficName(i, j) +
		    " + " + dist[j][i] + trafficName(j, i));
    sbuf.append(";\n");
    // second, alpha and omega constraints
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
	if (j != i) {
	  sbuf.append(" +" + trafficName(i, j));
	}
      }
      sbuf.append("<=1.0;\n");
    }
    LPHeader = sbuf.toString();
  }

  String trafficName(int i, int j) {
    return ("T" + i + "_" + j);
  }

  public static void main(String[] args) {
    int n = Integer.parseInt(args[0]);
    SimLowerBound slb = new SimLowerBound(n);
    slb.simulate();
  }

  void simulate() {
    for (int indexD = 0; indexD < D.length; indexD++) {
      System.out.println("1/D = " + D[indexD]);
      System.out.print("lb = [");
      for (int nb = 0; nb <= stepb; nb++) {
	double b = Math.exp(Math.log(maxb)/stepb*nb);
	String sdata = getLPText(D[indexD], b);
	String rdata = CGI.linearProgramming(sdata);
	double lbound = LP.parseObjectiveValue(rdata)/(n*Math.PI);
	System.out.print(lbound + "\n");
      }
      System.out.println("]");
    }
  }

  String getLPText(double d, double b) {
    StringBuffer sbuf = new StringBuffer(LPHeader);
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
	if (j != i) {
	  double traffic = b/(n-1)*Math.exp(-dist[i][j]*d);
	  if (traffic >= 1) traffic = 1;
	  sbuf.append(trafficName(i, j) + " <= " + traffic + ";\n");
	  sbuf.append(trafficName(j, i) + " <= " + traffic + ";\n");
	}
      }
    }
    return sbuf.toString();
  }
}


