Bouncer / bouncer / counters / morris_counter.c
morris_counter.c
Raw
static void
Morris_dealloc(Morris *self)
{
    free(self->X);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
Morris_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    // initialize attributes to 0
    Morris *self;
    self = (Morris *) type->tp_alloc(type, 0);
    self->epsilon = self->delta = 0; 
    return (PyObject *) self;
}

static float byte_to_float(byte a){
    float ret; 
    ret = a / 100.00 ; 
    return ret; 
}
static byte float_to_byte(float a){
    byte ret; 
    ret = a * 100; 
    return ret; 
}

static int Morris_init_helper(Morris *self, uint64_t size, byte version){
    if( (self->X = (byte*) calloc(size , sizeof(byte))) == NULL){ 
        PyErr_SetString(MemoryError, "Unable to allocate space, terminating");
        return -1;
    }
    self->version_flag = version; 
    return 0; 
}

static int
Morris_init(Morris *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"version", "epsilon", "delta", NULL};
    const char* v = "Morris"; 
    PyObject *version = Py_BuildValue("s#", v, strlen(v));;
    uint32_t t,s; 
    float epsilon = 0, delta = 0; 

    // passing in arguments 
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Off", kwlist, &version, &epsilon, &delta)){
        return -1; 
    }

    if (!PyUnicode_Check(version)) {
        PyErr_SetString(PyExc_TypeError,
                        "The version attribute must be a string");
        return -1;
    }
    const char* passed_in_vesion = PyUnicode_DATA(version);

    // if we only have to track one stream 
    if(strcmp(passed_in_vesion, "Morris") == 0){
        return Morris_init_helper(self, 1, 0); 
    }

    if((epsilon >= 1) || (epsilon <= 0) ){
        PyErr_SetString(HyperParameterError, "epsilon must set between 0 and 1");
        return -1;
    } 
    // error check for delta val
    if((delta >= 1) || (delta <= 0)){
        PyErr_SetString(HyperParameterError, "delta must be set between 0 and 1");
        return -1;
    } 

    self->epsilon = float_to_byte(epsilon);
    self->delta = float_to_byte(delta);

    if(strcmp(passed_in_vesion, "Morris+") == 0){ 
        s = (uint32_t) ceil( 1/((float) 2 * pow(epsilon, 2) * delta));
        return Morris_init_helper(self, s, 1); 
    }

    if(strcmp(passed_in_vesion, "Morris++") == 0 ){
        s  = (uint32_t) ceil( 3/( (float)  2 * pow(epsilon, 2) ));
        t = (uint32_t) ceil( 18 * log(1/ (float) delta));
        return Morris_init_helper(self, s*t, 2); 
    }

    PyErr_SetString(VersionError, "version must be set to: Morris, Morris+, Morris++");
    return -1;
}

static PyObject *
Morris_update_helper(Morris *self, uint64_t range){
    for(uint32_t i = 0; i < range; i++){
        self->X[i] < log2(RAND_MAX) - log2(rand()) ? self->X[i]++ : self->X[i]; 
    }
    return Py_None;
}

static PyObject *
Morris_update(Morris *self)
{
    // assuming range is less than or equal to 2^32
    uint32_t s,t; 
    float epsilon, delta; 

    if(self->version_flag == 0){
        return Morris_update_helper(self, 1); 
    }

    epsilon = byte_to_float(self->epsilon);
    delta = byte_to_float(self->delta); 

    if(self->version_flag == 1){
        s = (uint32_t) ceil( 1/((float) 2 * pow(epsilon, 2) * delta));
        return Morris_update_helper(self, s); 
    }

    s  = (uint32_t) ceil( 3/( (float)  2 * pow(epsilon, 2) ));
    t = (uint32_t) ceil( 18 * log(1/ (float) delta));
    return Morris_update_helper(self, s*t); 
    
}

// https://stackoverflow.com/questions/1787996/c-library-function-to-perform-sort
static int comp (const void * elem1, const void * elem2) 
{
    int f = *((float*)elem1);
    int s = *((float*)elem2);
    if (f > s) return  1;
    if (f < s) return -1;
    return 0;
}

static uint64_t output_helper(uint64_t raw_count){
    return (1 << raw_count) - 1; 
}

// outputs 2^x -1 
static PyObject *
Morris_output(Morris *self)
{
    float* averages; 
    float ret; 
    uint64_t sum = 0; 
    uint32_t s,t; 
    float epsilon, delta; 

    // checking for case of no update calls
    if(self->X[0] == 0){
        return PyLong_FromLong(0);
    }

    if(self->version_flag == 0){
        return PyLong_FromLong((1 << self->X[0]) - 1);
    }

    epsilon = byte_to_float(self->epsilon);
    delta = byte_to_float(self->delta); 

    if(self->version_flag == 1){
        s = (uint32_t) ceil( 1/((float) 2 * pow(epsilon, 2) * delta));
        for(uint32_t i = 0; i < s; i++){
            sum += output_helper(self->X[i]); 
        }
        ret = sum/ (double) s; // need to use doubles for as no floats in python
        return PyFloat_FromDouble(ret); 
    }

    s  = (uint32_t) ceil( 3/( (float)  2 * pow(epsilon, 2) ));
    t = (uint32_t) ceil( 18 * log(1/ (float) delta));

    averages = (float*) calloc(t, sizeof(float));
    if (averages == NULL){ // will still only use floats here to save space
        PyErr_SetString(MemoryError, "Unable to allocate space, terminating");
        return Py_None; 
    }

    for(uint32_t row = 0; row < t; row++){
        for(uint32_t column = 0; column < s; column++){
            sum += output_helper(self->X[(row*(s)) + column]); 
        }
        averages[row] = sum/ (float) s; 
        sum = 0; 
    }

    qsort(averages,t,sizeof(float),comp);
    // get the median 
    ret = (t % 2 == 0) ? ((averages[(t-1)/2] + averages[t/2])/ (float) 2.0) : averages[t/2];
    free(averages); 
    
    return PyFloat_FromDouble((double) ret); 
}