Module progbg.util

Utility function and classes used throughout progbg

Expand source code
"""Utility function and classes used throughout progbg"""
import itertools
import sys
import os
from pprint import pformat
from typing import List, Dict, Tuple


import numpy as np
import pandas as pd

from .globals import _sb_rnames

REQUIRED = object()


class Metrics:
    """Metrics collection object

    This object is given to executions to allow for users to specify what data
    must be kept. The main functio that should be used by users is the add_metric
    function, which simply appends data points to a list to be used to calculate
    means and standard deviation later
    """

    def __init__(self):
        self._vars = dict()
        self._consts = dict()

    def add_metric(self, key, val):
        """Add a data point to a metric key

        Args:
            key (str): Key to add value to
            val (int, float): value to append to a list

        Example:
            Suppose we have a parser we wish to use. This parser takes
            both a metrics object and a file path.

            >>> def my_parser(metrics: Metrics, out: str):
            >>>     with open(out, 'r') as f:
            >>>         mydata = f.read()
            >>>         val = find_specific_value(mydata)
            >>>         metrics.add_metric('my-stored-val', val)
        """
        if key not in self._vars:
            self._vars[key] = [val]
        else:
            self._vars[key].append(val)

    def to_file(self, path):
        stats = self.get_stats()
        with open(path, "w") as f:
            for k, val in stats.items():
                f.write("{}={}\n".format(k, val))

    def add_metrics(self, key, vals):
        for v in vals:
            self.add_metric(key, v)

    def stat(self, key):
        obj = self.get_stats()
        return obj[key]

    def __getitem__(self, key):
        obj = self.get_stats()
        if key in self._vars:
            return self._vars[key]

        return self._consts[key]

    def __contains__(self, key):
        return (key in self._vars) or (key in self._consts)

    def add_constant(self, key, val):
        """Add a constant to a metric object

        Args:
            key (str): Key for constant
            val (obj): Value of this constant
        """
        self._consts[key] = val

    def _combine(self, other: Dict):
        for key, val in other._vars.items():
            if key in self._vars:
                self._vars[key].append(val)
            else:
                self._vars[key] = val

    def get_stats(self):
        """Returns the metrics object

        Will return the current metrics of this object. Each associated
        key will have its mean and standard deviation calculated.  Standard deviation
        is stored within a "_std" key.

        For example. If I had some metrics with associated key "my-metric". The returned
        dictionary would store the mean at key "my-metrics", and store standard deviation
        at key "my-metrics_std".  This allows users to also manually set standard deviation
        of objects if needed. For example when using then `plan_parse` style executions.

        Returns:
            dict
        """
        obj = dict()
        for key, val in self._vars.items():
            obj[key] = np.mean(val)
            obj[key + "_std"] = np.std(val)
        for key, val in self._consts.items():
            obj[key] = val

        return obj

    def __repr__(self):
        obj = self.get_stats()
        return pformat(obj)


def normalize(group_list, index_to):
    normal = group_list[index_to]
    final_list = []
    for group in group_list:
        stddev = group[1] / group[0]
        newval = group[0] / normal[0]
        final_list.append((newval, stddev * newval))
    return final_list


def silence_print():
    sys.stdout = open(os.devnull, "w")


def restore_print():
    sys.stdout.close()
    sys.stdout = sys.__stdout__


def error(strn: str):
    print("\033[0;31m[Error]:\033[0m {}".format(strn))
    sys.exit(-1)


def dump_obj(file: str, obj: Dict):
    """Dump dictionary to file key=val"""
    with open(file, "w") as ofile:
        for key, val in obj.items():
            line = "{}={}\n".format(key, val)
            ofile.write(line)


def retrieve_obj(file: str) -> Dict:
    """Retrieve dictionary from file key=val"""
    obj = {}
    with open(file, "r") as ofile:
        for line in ofile.readlines():
            vals = line.strip().split("=")
            try:
                obj[vals[0]] = vals[1]
            except:
                print("Issue with file: {}".format(file))
                exit(0)
    return obj


class Variables:
    """
    Variables is a container class for variables for a given execution

    This will define the permutations that can occur for those that utilize the
    class (see Variables.produce_args documentation)

    Attributes:
        const: A dictionary of argument names to values that will be passed
        var: A tuple of an argument name and some iterable object
    """

    def __init__(self, consts: Dict = None, var: List[Tuple[str, List]] = None) -> None:
        if len(var):
            if any([i in consts for i in _sb_rnames]) or (var[0] in _sb_rnames):
                raise Exception(
                    "Cannot use a reserved name for a variable {}".format(
                        pformat(_sb_rnames)
                    )
                )
            for vals in var:
                if vals[0] in consts:
                    raise Exception(
                        "Name defined as constant and varying: {}".format(vals[0])
                    )

        self.consts = consts
        self.var = var

    def produce_args(self) -> List[Dict]:
        """Produces a list of arguments given the consts and vars
        Example:
            Variables(
                sb.Variables(
                    consts = {
                        "other" : 1
                    },
                    var = [("x" ,range(0, 3, 1)), ("test", range(0, 5, 2))]
                ),

            In this example this would produce args as follows:
                { other = 1, x = 0, test = 0},
                { other = 1, x = 0, test = 2},
                { other = 1, x = 0, test = 4},
                { other = 1, x = 1, test = 0},
                ...
                { other = 1, x = 2, test = 4},
        """
        if not len(self.var):
            return [dict(self.consts)]

        key_names, ranges = zip(*self.var)
        args = []
        for perm in itertools.product(*ranges):
            run_vars = dict(self.consts)
            for i, k in enumerate(key_names):
                run_vars[k] = perm[i]
            args.append(run_vars)

        return args

    def param_exists(self, name: str) -> bool:
        """Checks if a variable is defined either as a constant or a varrying variable"""
        return (name in self.consts) or any([name == x[0] for x in self.var])

    def y_names(self) -> List[str]:
        """Returns the names of varrying or responding variables"""
        return [x[0] for x in self.var]

    def const_names(self) -> List[str]:
        """Returns names of constants"""
        return self.consts.keys()

    def __repr__(self) -> str:
        return pformat(vars(self), width=30)


class Backend:
    def __init__(self, path, variables):
        self.backends = path.split("/")
        self.runtime_variables = variables

    @staticmethod
    def user_to_sql(path):
        return "_b_".join(path.split("/"))

    @staticmethod
    def user_to_out(path):
        return "-".join(path.split("/"))

    @staticmethod
    def out_to_user(path):
        return "/".join(path.split("-"))

    @staticmethod
    def out_to_sql(path):
        return "_b_".join(path.split("-"))

    @property
    def path_sql(self):
        return "_b_".join(self.backends)

    @property
    def path_user(self):
        return "/".join(self.backends)

    @property
    def path_out(self):
        return "-".join(self.backends)

    def __eq__(self, path):
        return (
            (self.path_sql == path)
            or (self.path_out == path)
            or (self.path_user == path)
        )


class ExecutionStub:
    def __init__(self, **kwargs):
        metric = Metrics()
        for k, v in kwargs.items():
            metric.add_constant(k, v)
        self._cached = [metric]

Functions

def dump_obj(file: str, obj: Dict)

Dump dictionary to file key=val

Expand source code
def dump_obj(file: str, obj: Dict):
    """Dump dictionary to file key=val"""
    with open(file, "w") as ofile:
        for key, val in obj.items():
            line = "{}={}\n".format(key, val)
            ofile.write(line)
def error(strn: str)
Expand source code
def error(strn: str):
    print("\033[0;31m[Error]:\033[0m {}".format(strn))
    sys.exit(-1)
def normalize(group_list, index_to)
Expand source code
def normalize(group_list, index_to):
    normal = group_list[index_to]
    final_list = []
    for group in group_list:
        stddev = group[1] / group[0]
        newval = group[0] / normal[0]
        final_list.append((newval, stddev * newval))
    return final_list
def restore_print()
Expand source code
def restore_print():
    sys.stdout.close()
    sys.stdout = sys.__stdout__
def retrieve_obj(file: str) ‑> Dict

Retrieve dictionary from file key=val

Expand source code
def retrieve_obj(file: str) -> Dict:
    """Retrieve dictionary from file key=val"""
    obj = {}
    with open(file, "r") as ofile:
        for line in ofile.readlines():
            vals = line.strip().split("=")
            try:
                obj[vals[0]] = vals[1]
            except:
                print("Issue with file: {}".format(file))
                exit(0)
    return obj
def silence_print()
Expand source code
def silence_print():
    sys.stdout = open(os.devnull, "w")

Classes

class Backend (path, variables)
Expand source code
class Backend:
    def __init__(self, path, variables):
        self.backends = path.split("/")
        self.runtime_variables = variables

    @staticmethod
    def user_to_sql(path):
        return "_b_".join(path.split("/"))

    @staticmethod
    def user_to_out(path):
        return "-".join(path.split("/"))

    @staticmethod
    def out_to_user(path):
        return "/".join(path.split("-"))

    @staticmethod
    def out_to_sql(path):
        return "_b_".join(path.split("-"))

    @property
    def path_sql(self):
        return "_b_".join(self.backends)

    @property
    def path_user(self):
        return "/".join(self.backends)

    @property
    def path_out(self):
        return "-".join(self.backends)

    def __eq__(self, path):
        return (
            (self.path_sql == path)
            or (self.path_out == path)
            or (self.path_user == path)
        )

Static methods

def out_to_sql(path)
Expand source code
@staticmethod
def out_to_sql(path):
    return "_b_".join(path.split("-"))
def out_to_user(path)
Expand source code
@staticmethod
def out_to_user(path):
    return "/".join(path.split("-"))
def user_to_out(path)
Expand source code
@staticmethod
def user_to_out(path):
    return "-".join(path.split("/"))
def user_to_sql(path)
Expand source code
@staticmethod
def user_to_sql(path):
    return "_b_".join(path.split("/"))

Instance variables

var path_out
Expand source code
@property
def path_out(self):
    return "-".join(self.backends)
var path_sql
Expand source code
@property
def path_sql(self):
    return "_b_".join(self.backends)
var path_user
Expand source code
@property
def path_user(self):
    return "/".join(self.backends)
class ExecutionStub (**kwargs)
Expand source code
class ExecutionStub:
    def __init__(self, **kwargs):
        metric = Metrics()
        for k, v in kwargs.items():
            metric.add_constant(k, v)
        self._cached = [metric]
class Metrics

Metrics collection object

This object is given to executions to allow for users to specify what data must be kept. The main functio that should be used by users is the add_metric function, which simply appends data points to a list to be used to calculate means and standard deviation later

Expand source code
class Metrics:
    """Metrics collection object

    This object is given to executions to allow for users to specify what data
    must be kept. The main functio that should be used by users is the add_metric
    function, which simply appends data points to a list to be used to calculate
    means and standard deviation later
    """

    def __init__(self):
        self._vars = dict()
        self._consts = dict()

    def add_metric(self, key, val):
        """Add a data point to a metric key

        Args:
            key (str): Key to add value to
            val (int, float): value to append to a list

        Example:
            Suppose we have a parser we wish to use. This parser takes
            both a metrics object and a file path.

            >>> def my_parser(metrics: Metrics, out: str):
            >>>     with open(out, 'r') as f:
            >>>         mydata = f.read()
            >>>         val = find_specific_value(mydata)
            >>>         metrics.add_metric('my-stored-val', val)
        """
        if key not in self._vars:
            self._vars[key] = [val]
        else:
            self._vars[key].append(val)

    def to_file(self, path):
        stats = self.get_stats()
        with open(path, "w") as f:
            for k, val in stats.items():
                f.write("{}={}\n".format(k, val))

    def add_metrics(self, key, vals):
        for v in vals:
            self.add_metric(key, v)

    def stat(self, key):
        obj = self.get_stats()
        return obj[key]

    def __getitem__(self, key):
        obj = self.get_stats()
        if key in self._vars:
            return self._vars[key]

        return self._consts[key]

    def __contains__(self, key):
        return (key in self._vars) or (key in self._consts)

    def add_constant(self, key, val):
        """Add a constant to a metric object

        Args:
            key (str): Key for constant
            val (obj): Value of this constant
        """
        self._consts[key] = val

    def _combine(self, other: Dict):
        for key, val in other._vars.items():
            if key in self._vars:
                self._vars[key].append(val)
            else:
                self._vars[key] = val

    def get_stats(self):
        """Returns the metrics object

        Will return the current metrics of this object. Each associated
        key will have its mean and standard deviation calculated.  Standard deviation
        is stored within a "_std" key.

        For example. If I had some metrics with associated key "my-metric". The returned
        dictionary would store the mean at key "my-metrics", and store standard deviation
        at key "my-metrics_std".  This allows users to also manually set standard deviation
        of objects if needed. For example when using then `plan_parse` style executions.

        Returns:
            dict
        """
        obj = dict()
        for key, val in self._vars.items():
            obj[key] = np.mean(val)
            obj[key + "_std"] = np.std(val)
        for key, val in self._consts.items():
            obj[key] = val

        return obj

    def __repr__(self):
        obj = self.get_stats()
        return pformat(obj)

Methods

def add_constant(self, key, val)

Add a constant to a metric object

Args

key : str
Key for constant
val : obj
Value of this constant
Expand source code
def add_constant(self, key, val):
    """Add a constant to a metric object

    Args:
        key (str): Key for constant
        val (obj): Value of this constant
    """
    self._consts[key] = val
def add_metric(self, key, val)

Add a data point to a metric key

Args

key : str
Key to add value to
val : int, float
value to append to a list

Example

Suppose we have a parser we wish to use. This parser takes both a metrics object and a file path.

>>> def my_parser(metrics: Metrics, out: str):
>>>     with open(out, 'r') as f:
>>>         mydata = f.read()
>>>         val = find_specific_value(mydata)
>>>         metrics.add_metric('my-stored-val', val)
Expand source code
def add_metric(self, key, val):
    """Add a data point to a metric key

    Args:
        key (str): Key to add value to
        val (int, float): value to append to a list

    Example:
        Suppose we have a parser we wish to use. This parser takes
        both a metrics object and a file path.

        >>> def my_parser(metrics: Metrics, out: str):
        >>>     with open(out, 'r') as f:
        >>>         mydata = f.read()
        >>>         val = find_specific_value(mydata)
        >>>         metrics.add_metric('my-stored-val', val)
    """
    if key not in self._vars:
        self._vars[key] = [val]
    else:
        self._vars[key].append(val)
def add_metrics(self, key, vals)
Expand source code
def add_metrics(self, key, vals):
    for v in vals:
        self.add_metric(key, v)
def get_stats(self)

Returns the metrics object

Will return the current metrics of this object. Each associated key will have its mean and standard deviation calculated. Standard deviation is stored within a "_std" key.

For example. If I had some metrics with associated key "my-metric". The returned dictionary would store the mean at key "my-metrics", and store standard deviation at key "my-metrics_std". This allows users to also manually set standard deviation of objects if needed. For example when using then plan_parse style executions.

Returns

dict

Expand source code
def get_stats(self):
    """Returns the metrics object

    Will return the current metrics of this object. Each associated
    key will have its mean and standard deviation calculated.  Standard deviation
    is stored within a "_std" key.

    For example. If I had some metrics with associated key "my-metric". The returned
    dictionary would store the mean at key "my-metrics", and store standard deviation
    at key "my-metrics_std".  This allows users to also manually set standard deviation
    of objects if needed. For example when using then `plan_parse` style executions.

    Returns:
        dict
    """
    obj = dict()
    for key, val in self._vars.items():
        obj[key] = np.mean(val)
        obj[key + "_std"] = np.std(val)
    for key, val in self._consts.items():
        obj[key] = val

    return obj
def stat(self, key)
Expand source code
def stat(self, key):
    obj = self.get_stats()
    return obj[key]
def to_file(self, path)
Expand source code
def to_file(self, path):
    stats = self.get_stats()
    with open(path, "w") as f:
        for k, val in stats.items():
            f.write("{}={}\n".format(k, val))
class Variables (consts: Dict = None, var: List[Tuple[str, List]] = None)

Variables is a container class for variables for a given execution

This will define the permutations that can occur for those that utilize the class (see Variables.produce_args documentation)

Attributes

const
A dictionary of argument names to values that will be passed
var
A tuple of an argument name and some iterable object
Expand source code
class Variables:
    """
    Variables is a container class for variables for a given execution

    This will define the permutations that can occur for those that utilize the
    class (see Variables.produce_args documentation)

    Attributes:
        const: A dictionary of argument names to values that will be passed
        var: A tuple of an argument name and some iterable object
    """

    def __init__(self, consts: Dict = None, var: List[Tuple[str, List]] = None) -> None:
        if len(var):
            if any([i in consts for i in _sb_rnames]) or (var[0] in _sb_rnames):
                raise Exception(
                    "Cannot use a reserved name for a variable {}".format(
                        pformat(_sb_rnames)
                    )
                )
            for vals in var:
                if vals[0] in consts:
                    raise Exception(
                        "Name defined as constant and varying: {}".format(vals[0])
                    )

        self.consts = consts
        self.var = var

    def produce_args(self) -> List[Dict]:
        """Produces a list of arguments given the consts and vars
        Example:
            Variables(
                sb.Variables(
                    consts = {
                        "other" : 1
                    },
                    var = [("x" ,range(0, 3, 1)), ("test", range(0, 5, 2))]
                ),

            In this example this would produce args as follows:
                { other = 1, x = 0, test = 0},
                { other = 1, x = 0, test = 2},
                { other = 1, x = 0, test = 4},
                { other = 1, x = 1, test = 0},
                ...
                { other = 1, x = 2, test = 4},
        """
        if not len(self.var):
            return [dict(self.consts)]

        key_names, ranges = zip(*self.var)
        args = []
        for perm in itertools.product(*ranges):
            run_vars = dict(self.consts)
            for i, k in enumerate(key_names):
                run_vars[k] = perm[i]
            args.append(run_vars)

        return args

    def param_exists(self, name: str) -> bool:
        """Checks if a variable is defined either as a constant or a varrying variable"""
        return (name in self.consts) or any([name == x[0] for x in self.var])

    def y_names(self) -> List[str]:
        """Returns the names of varrying or responding variables"""
        return [x[0] for x in self.var]

    def const_names(self) -> List[str]:
        """Returns names of constants"""
        return self.consts.keys()

    def __repr__(self) -> str:
        return pformat(vars(self), width=30)

Methods

def const_names(self) ‑> List[str]

Returns names of constants

Expand source code
def const_names(self) -> List[str]:
    """Returns names of constants"""
    return self.consts.keys()
def param_exists(self, name: str) ‑> bool

Checks if a variable is defined either as a constant or a varrying variable

Expand source code
def param_exists(self, name: str) -> bool:
    """Checks if a variable is defined either as a constant or a varrying variable"""
    return (name in self.consts) or any([name == x[0] for x in self.var])
def produce_args(self) ‑> List[Dict]

Produces a list of arguments given the consts and vars

Example

Variables( sb.Variables( consts = { "other" : 1 }, var = [("x" ,range(0, 3, 1)), ("test", range(0, 5, 2))] ),

In this example this would produce args as follows: { other = 1, x = 0, test = 0}, { other = 1, x = 0, test = 2}, { other = 1, x = 0, test = 4}, { other = 1, x = 1, test = 0}, … { other = 1, x = 2, test = 4},

Expand source code
def produce_args(self) -> List[Dict]:
    """Produces a list of arguments given the consts and vars
    Example:
        Variables(
            sb.Variables(
                consts = {
                    "other" : 1
                },
                var = [("x" ,range(0, 3, 1)), ("test", range(0, 5, 2))]
            ),

        In this example this would produce args as follows:
            { other = 1, x = 0, test = 0},
            { other = 1, x = 0, test = 2},
            { other = 1, x = 0, test = 4},
            { other = 1, x = 1, test = 0},
            ...
            { other = 1, x = 2, test = 4},
    """
    if not len(self.var):
        return [dict(self.consts)]

    key_names, ranges = zip(*self.var)
    args = []
    for perm in itertools.product(*ranges):
        run_vars = dict(self.consts)
        for i, k in enumerate(key_names):
            run_vars[k] = perm[i]
        args.append(run_vars)

    return args
def y_names(self) ‑> List[str]

Returns the names of varrying or responding variables

Expand source code
def y_names(self) -> List[str]:
    """Returns the names of varrying or responding variables"""
    return [x[0] for x in self.var]