#!/usr/bin/python
"""
AQM Evaluation Suite - Ellipse Maker

This script generates confidence ellipses for delay-throughput scatter plots from
AQM evaluation data. It reads delay-throughput correlation data (typically generated
by generate-ellipseinput.py) and creates ellipse coordinates representing the
statistical distribution of the data points.

The ellipses show the confidence regions for delay vs throughput performance,
allowing visual comparison of different queue disciplines' operating characteristics.

Usage:
    python ellipsemaker <scenario_name> <queuedisc_name> <queuedisc_index> <base_output_dir>

Arguments:
    scenario_name    : Name of the simulation scenario
    queuedisc_name   : Name of the queue discipline
    queuedisc_index  : Numeric index for plotting (determines color/symbol in plots)
    base_output_dir  : Base directory containing simulation output data

Input:
    - Reads from: {base_output_dir}/{scenario_name}/data/{queuedisc_name}-result.dat
    - Expected format: "delay_ms throughput_bytes_per_sec" per line
    - Typically generated by generate-ellipseinput.py

Output:
    - Ellipse coordinates: {base_output_dir}/{scenario_name}/data/{queuedisc_name}-ellipse.dat
    - Gnuplot commands: Appends to {base_output_dir}/{scenario_name}/data/plot-shell

Mathematical Process:
    1. Converts delay from milliseconds to seconds
    2. Converts throughput from bytes/sec to Mbps
    3. Calculates covariance matrix of the data points
    4. Computes eigenvalues and eigenvectors for ellipse orientation
    5. Generates ellipse coordinates using parametric equations
    6. Centers ellipse at mean of data points

Example:
    python ellipsemaker mild-congestion Red 1 /path/to/output
    
    This generates ellipse data for Red queue discipline and appends
    gnuplot commands for visualization with index 1 (first in legend).

Integration:
    - Use after generate-ellipseinput.py to create correlation data
    - Multiple ellipsemaker calls can create comparative plots
    - Output integrates with gnuplot for final visualization
"""

import sys

import numpy as np

if len(sys.argv) < 5:
    print("Usage: python ellipsemaker <scenario_name> <queuedisc_name> <queuedisc_index> <base_output_dir>")
    sys.exit(1)

samples = []

scenario_name = sys.argv[1]
queuedisc_name = sys.argv[2]
queuedisc_index = sys.argv[3]
base_output_dir = sys.argv[4]

input_file = open(base_output_dir+"/"+scenario_name+"/data/"+queuedisc_name+"-result.dat", "r") 
lines = input_file.readlines()

output_file_name = base_output_dir+"/"+scenario_name+"/data/"+queuedisc_name+"-ellipse.dat"
output_file = open(output_file_name, "w") 

qdisc_label = {'PfifoFast': 'DropTail', 'CoDel': 'CoDel', 'FqCoDel': 'FqCoDel', 'Pie': 'Pie', 'FqPie': 'FqPie', 'Red': 'RED',  'AdaptiveRed': 'ARED', 'FengAdaptiveRed': 'FRED', 'NonLinearRed':'NLRED', 'Cobalt': 'Cobalt', "FqCobalt": "FQ-Cobalt"}

for line in lines:
    fields = line.split( ' ' )
    delay, throughput = (float(fields[ 0 ]))/1000.0, (float(fields[ 1 ]))/(1024.0*128.0)
    samples.append( [ delay, throughput ] )

samples = np.matrix( samples )

# taken from https://github.com/joferkington/oost_paper_code/blob/master/error_ellipse.py

def get_ellipse(points, nstd=1):
    def eigsorted(cov):
        vals, vecs = np.linalg.eigh(cov)
        order = vals.argsort()[::-1]
        return vals[order], vecs[:,order]

    cov = np.cov(points, rowvar=False)
    vals, vecs = eigsorted(cov)
    theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
    # Width and height are "full" widths, not radius
#    width, height = 2 * nstd * np.sqrt(vals)
    width = nstd[0,0] * np.sqrt(vals[0])
    height = nstd[0,1] * np.sqrt(vals[1])
    return [ width, height, theta ]

def get_ellipse_coords(a=0.0, b=0.0, x=0.0, y=0.0, angle=0.0, k=2):
    """ Draws an ellipse using (360*k + 1) discrete points; based on pseudo code
    given at http://en.wikipedia.org/wiki/Ellipse
    k = 1 means 361 points (degree by degree)
    a = major axis distance,
    b = minor axis distance,
    x = offset along the x-axis
    y = offset along the y-axis
    angle = clockwise rotation [in degrees] of the ellipse;
        * angle=0  : the ellipse is aligned with the positive x-axis
        * angle=30 : rotated 30 degrees clockwise from positive x-axis
    """
    pts = np.zeros((360*k+1, 2))

    beta = -angle * np.pi/180.0
    sin_beta = np.sin(beta)
    cos_beta = np.cos(beta)
    alpha = np.radians(np.r_[0.:360.:1j*(360*k+1)])
    sin_alpha = np.sin(alpha)
    cos_alpha = np.cos(alpha)
    pts[:, 0] = x + (a * cos_alpha * cos_beta - b * sin_alpha * sin_beta)
    pts[:, 1] = y + (a * cos_alpha * sin_beta + b * sin_alpha * cos_beta)

    return pts

means = np.mean( samples, axis=0 )
center_x = means[ 0, 0 ]
center_y = means[ 0, 1 ]
width, height, theta = get_ellipse( samples, np.std( samples, axis=0 ))

#print center_x, center_y
#print width, height, theta

for i in get_ellipse_coords(width, height, center_x, center_y, -theta, 10 ):
    output_file.write(str(i[ 0 ]*1000.0)+' '+str(i[ 1 ])+'\n')

output_file.close()
input_file.close()

gnu_file = open(base_output_dir+"/"+scenario_name+"/data/plot-shell", "a") 
gnu_file.write ("set rmargin 8\n")
gnu_file.write ("set label \"\" at "+str(center_x*1000.0)+","+str(center_y)+" point lt "+queuedisc_index+" pt "+queuedisc_index+" center font \"Verdana\"  tc lt "+queuedisc_index+" offset 1.5,0.4\n")
gnu_file.write ("set label \""+qdisc_label.get(queuedisc_name[:-9], queuedisc_name[:-9])+"\" at graph "+str(1.03)+","+str(0.96 - (0.06 * (float(queuedisc_index) - 1)))+" point lt "+queuedisc_index+" pt "+queuedisc_index+" font \"Verdana,12\"  tc lt "+queuedisc_index+" offset 0.7, -0.2\n")
gnu_file.close()
