Beispiel #1
0
class Max(NumpyAggregate):
    np_func = np.amax
    nan_func = (nanmax, )
    dtype = firstarg_dtype
    # manually defined argspec so that is works with bottleneck (which is a
    # builtin function)
    argspec = argspec('a, axis=None', **NumpyAggregate.kwonlyargs)
Beispiel #2
0
class Where(NumexprFunction):
    funcname = 'if'
    argspec = argspec('cond, iftrue, iffalse')

    @property
    def cond(self):
        return self.args[0]

    @property
    def iftrue(self):
        return self.args[1]

    @property
    def iffalse(self):
        return self.args[2]

    def as_simple_expr(self, context):
        cond = as_simple_expr(self.cond, context)

        # filter is stored as an unevaluated expression
        context_filter = context.filter_expr
        local_ctx = context.clone()
        if context_filter is None:
            local_ctx.filter_expr = self.cond
        else:
            # filter = filter and cond
            local_ctx.filter_expr = LogicalOp('&', context_filter, self.cond)
        iftrue = as_simple_expr(self.iftrue, local_ctx)

        if context_filter is None:
            local_ctx.filter_expr = UnaryOp('~', self.cond)
        else:
            # filter = filter and not cond
            local_ctx.filter_expr = LogicalOp('&', context_filter,
                                              UnaryOp('~', self.cond))
        iffalse = as_simple_expr(self.iffalse, local_ctx)
        return Where(cond, iftrue, iffalse)

    def as_string(self):
        args = as_string((self.cond, self.iftrue, self.iffalse))
        return 'where(%s)' % self.format_args_str(args, [])

    def dtype(self, context):
        assert getdtype(self.cond, context) == bool
        return coerce_types(context, self.iftrue, self.iffalse)
Beispiel #3
0
class Choice(NumpyRandom):
    np_func = np.random.choice
    # choice(a, size=None, replace=True, p=None)
    argspec = argspec('choices, p=None, size=None, replace=True',
                      **NumpyRandom.kwonlyargs)

    def __init__(self, *args, **kwargs):
        NumpyRandom.__init__(self, *args, **kwargs)

        probabilities = self.args[0]
        if isinstance(probabilities, basestring):
            fpath = os.path.join(config.input_directory, probabilities)
            probabilities = load_ndarray(fpath)
            # XXX: store args in a list so that we can modify it?
            # self.args[1] = load_ndarray(fpath, float)
            # XXX: but we should be able to do better than a list, eg.
            # self.args.need = load_ndarray(fpath, float)
            self.args = (probabilities, ) + self.args[1:]

    # TODO: document the change in behavior for the case where the sum of
    # probabilities is != 1
    # random.choice only checks that the error is < 1e-8 but always
    # divides probabilities by sum(p). It is probably a better choice
    # because it distributes the error to all bins instead of only
    # adjusting the probability of the last choice.

    # We override _eval_args only to change the order of arguments because we
    # do not use the same order than numpy
    def _eval_args(self, context):
        (a, p, size, replace), kwargs = NumpyRandom._eval_args(self, context)
        return (a, size, replace, p), kwargs

    def compute(self, context, a, size=None, replace=True, p=None):
        if isinstance(a, la.LArray):
            assert p is None
            outcomes_axis = a.axes['outcomes']
            outcomes = outcomes_axis.labels
            other_axes = a.axes - outcomes_axis

            if other_axes:
                a = index_array_by_variables(a, context, other_axes)
                p = np.asarray(a.transpose('outcomes'))
            else:
                p = np.asarray(a)
            a = outcomes

        if isinstance(p,
                      (list, np.ndarray)) and len(p) and not np.isscalar(p[0]):
            assert len(p) == len(a)
            assert all(len(px) == size for px in p)
            assert len(a) >= 2

            if isinstance(p, list) and any(
                    isinstance(px, la.LArray) for px in p):
                p = [np.asarray(px) for px in p]
            ap = np.asarray(p)
            cdf = ap.cumsum(axis=0)

            # copied & adapted from numpy/random/mtrand/mtrand.pyx
            atol = np.sqrt(np.finfo(np.float64).eps)
            if np.issubdtype(ap.dtype, np.floating):
                atol = max(atol, np.sqrt(np.finfo(ap.dtype).eps))

            if np.any(np.abs(cdf[-1] - 1.) > atol):
                raise ValueError("probabilities do not sum to 1")

            cdf /= cdf[-1]

            # I have not found a way to do this without an explicit loop as
            # np.digitize only supports a 1d array for bins. What we do is
            # worse than a linear "search" since we always evaluate all
            # possibilities (there is no shortcut when the value is found).
            # It might be faster to rewrite this using numba + np.digitize
            # for each individual (assuming it has a low setup overhead).

            # the goal is to build something like:
            # if(u < proba1, outcome1,
            #    if(u < proba2, outcome2,
            #       outcome3))

            data = {'u': np.random.uniform(size=size)}
            expr = a[-1]
            # iterate in reverse and skip last
            pairs = zip(cdf[-2::-1], a[-2::-1])
            for i, (proba_x, outcome_x) in enumerate(pairs):
                data['p%d' % i] = proba_x
                expr = Where(
                    ComparisonOp('<', Variable(None, 'u'),
                                 Variable(None, 'p%d' % i)), outcome_x, expr)
            local_ctx = context.clone(fresh_data=True, entity_data=data)
            return expr.evaluate(local_ctx)
        else:
            return NumpyRandom.compute(self, context, a, size, replace, p)

    dtype = firstarg_dtype
Beispiel #4
0
    def parse_function(self, k, v, context):
        if isinstance(v, list):
            # v should be a list of dicts (assignments) or strings (actions)
            if "(" in k:
                k, args = split_signature(k)
                argnames = argspec(args).args
                code_def = v
            else:
                argnames, code_def = [], v
                template = """\
Function definitions should have parentheses after their name even if they have no argument.
Please change "{name}:" to "{name}():".
You probably want to use the "upgrade" command to automatically convert your model file to the new syntax."""
                warnings.warn(template.format(name=k), UserDeprecationWarning)
            method_context = self.get_group_context(context, argnames)
            code = self.parse_process_group(k + "_code",
                                            code_def,
                                            method_context,
                                            purge=False)
            # TODO: use code.predictors instead (but it currently
            # fails for some reason) or at least factor this out
            # with the code in parse_process_group
            group_expressions = [
                list(elem.items())[0] if isinstance(elem, dict) else
                (None, elem) for elem in code_def
            ]
            group_predictors = self.collect_predictors(group_expressions,
                                                       in_process_group=True)
            method_context = self.get_group_context(method_context,
                                                    group_predictors)
            return Function(k, self, argnames, code)
        elif isinstance(v, dict) and ('args' in v or 'code' in v
                                      or 'return' in v):
            args = v.get('args', '')
            code = v.get('code', '')
            result = v.get('return', '')
            oldargs = "\n      args: {}".format(args) \
                if args else ''
            oldcode = "\n      code:\n          - ..." \
                if code else ''
            newcode = "\n      - ..." if code else ''
            oldresult = "\n      return: " + result \
                if result else ''
            newresult = "\n      - return " + result \
                if result else ''
            template = """
This syntax for defining functions with arguments or a return value is not
supported anymore:
{funcname}:{oldargs}{oldcode}{oldresult}

Please use this instead:
{funcname}({newargs}):{newcode}{newresult}"""
            msg = template.format(funcname=k,
                                  oldargs=oldargs,
                                  oldcode=oldcode,
                                  oldresult=oldresult,
                                  newargs=args,
                                  newcode=newcode,
                                  newresult=newresult)
            raise SyntaxError(msg)
        elif isinstance(v, dict) and 'predictor' in v:
            raise ValueError("Using the 'predictor' keyword is "
                             "not supported anymore. "
                             "If you need several processes to "
                             "write to the same variable, you "
                             "should rather use functions.")
        elif isinstance(v, (basestring, bool, int, float)):
            if k in self.fields.names:
                msg = """defining a process outside of a function is deprecated because it is ambiguous. You should:
 * wrap the '{name}: {expr}' assignment inside a function like this:
        compute_{name}:  # you can name it any way you like but simply '{name}' is not recommended !
            - {name}: {expr}
 * update the simulation.processes list to use 'compute_{name}' (the function name) instead of '{name}'.
"""
            else:
                msg = """defining a process outside of a function is deprecated because it is ambiguous.
1) If '{name}: {expr}' is an assignment ('{name}' stores the result of '{expr}'), you should:
 * wrap the assignment inside a function, for example, like this:
        compute_{name}:  # you can name it any way you like but simply '{name}' is not recommended !
            - {name}: {expr}
 * update the simulation.processes list to use 'compute_{name}' (the function name) instead of '{name}'.
 * add '{name}' in the entities fields with 'output: False'
2) otherwise if '{expr}' is an expression which does not return any value, you can simply transform it into a function,
   like this:
        {name}:
            - {expr}
            """
            warnings.warn(msg.format(name=k, expr=v), UserDeprecationWarning)
            # TODO: it would be cleaner if the process was wrapped in a function
            return self.parse_expr(k, v, context)
        else:
            raise Exception("unknown expression type for %s: %s (%s)" %
                            (k, v, type(v)))
Beispiel #5
0
 class FuncClass(baseclass):
     np_func = evalfunc
     funcname = name
     argspec = argspec(args, **baseclass.kwonlyargs)
     if dtypefunc is not None:
         dtype = dtypefunc
Beispiel #6
0
class Exp(NumexprFunction):
    argspec = argspec('expr')
    dtype = always(float)
Beispiel #7
0
    def parse_function(self, k, v, context):
        if isinstance(v, list):
            # v should be a list of dicts (assignments) or strings (actions)
            if "(" in k:
                k, args = split_signature(k)
                argnames = argspec(args).args
                code_def = v
            else:
                argnames, code_def = [], v
                template = """\
Function definitions should have parentheses after their name even if they have no argument.
Please change "{name}:" to "{name}():". 
You probably want to use the "upgrade" command to automatically convert your model file to the new syntax."""
                warnings.warn(template.format(name=k), UserDeprecationWarning)
            method_context = self.get_group_context(context, argnames)
            code = self.parse_process_group(k + "_code", code_def,
                                            method_context,
                                            purge=False)
            # TODO: use code.predictors instead (but it currently
            # fails for some reason) or at least factor this out
            # with the code in parse_process_group
            group_expressions = [list(elem.items())[0] if isinstance(elem, dict) else (None, elem)
                                 for elem in code_def]
            group_predictors = self.collect_predictors(group_expressions, in_process_group=True)
            method_context = self.get_group_context(method_context, group_predictors)
            return Function(k, self, argnames, code)
        elif isinstance(v, dict) and ('args' in v or 'code' in v or 'return' in v):
            args = v.get('args', '')
            code = v.get('code', '')
            result = v.get('return', '')
            oldargs = "\n      args: {}".format(args) \
                if args else ''
            oldcode = "\n      code:\n          - ..." \
                if code else ''
            newcode = "\n      - ..." if code else ''
            oldresult = "\n      return: " + result \
                if result else ''
            newresult = "\n      - return " + result \
                if result else ''
            template = """
This syntax for defining functions with arguments or a return value is not
supported anymore:
{funcname}:{oldargs}{oldcode}{oldresult}

Please use this instead:
{funcname}({newargs}):{newcode}{newresult}"""
            msg = template.format(funcname=k, oldargs=oldargs,
                                  oldcode=oldcode,
                                  oldresult=oldresult,
                                  newargs=args, newcode=newcode,
                                  newresult=newresult)
            raise SyntaxError(msg)
        elif isinstance(v, dict) and 'predictor' in v:
            raise ValueError("Using the 'predictor' keyword is "
                             "not supported anymore. "
                             "If you need several processes to "
                             "write to the same variable, you "
                             "should rather use functions.")
        elif isinstance(v, (basestring, bool, int, float)):
            if k in self.fields.names:
                msg = """defining a process outside of a function is deprecated because it is ambiguous. You should:
 * wrap the '{name}: {expr}' assignment inside a function like this:
        compute_{name}:  # you can name it any way you like but simply '{name}' is not recommended !
            - {name}: {expr}
 * update the simulation.processes list to use 'compute_{name}' (the function name) instead of '{name}'.
"""
            else:
                msg = """defining a process outside of a function is deprecated because it is ambiguous.
1) If '{name}: {expr}' is an assignment ('{name}' stores the result of '{expr}'), you should:
 * wrap the assignment inside a function, for example, like this:
        compute_{name}:  # you can name it any way you like but simply '{name}' is not recommended !
            - {name}: {expr}
 * update the simulation.processes list to use 'compute_{name}' (the function name) instead of '{name}'.
 * add '{name}' in the entities fields with 'output: False'
2) otherwise if '{expr}' is an expression which does not return any value, you can simply transform it into a function,
   like this:
        {name}:
            - {expr}
            """
            warnings.warn(msg.format(name=k, expr=v),
                          UserDeprecationWarning)
            # TODO: it would be cleaner if the process was wrapped in a function
            return self.parse_expr(k, v, context)
        else:
            raise Exception("unknown expression type for %s: %s (%s)" % (k, v, type(v)))