Ejemplo n.º 1
0
    def _create_wrapper(self, engine):
        """
        Creates a wrapper Python function that would be run by PyCLIPS.
        It calls the user defined function and is also responsible for handling
        errors and for type conversions.
        """
        def wrapper(*args):
            if self._last_error is not None:
                return
            try:
                args = [(engine._wrap_clips_instance(arg) if isinstance(
                    arg, (clips.Fact, clips.InstanceName)) else arg)
                        for arg in args]
                res = self.func(*args)
                self._num_calls += 1
                if self._num_calls % self.NUM_CALLS_PER_PRINT == 0:
                    logger.debug('{}: called {} times.'.format(
                        self.func_name, self._num_calls))
                return self.type._to_clips_value(res)
            except:
                # Register error to be raised later by engine
                cls = type(self)
                type(self)._last_error = sys.exc_info()
                # Abort CLIPS execution
                engine.environment.Clear()

        wrapper.__name__ = self._counters.setdefault(
            self.func_name, UniqueIdCounter(self.func_name + '_')).next()
        return wrapper
Ejemplo n.º 2
0
    def __init__(self):
        self.premises = defaultdict(RulePremise)
        self.name = None
        self.description = None
        self.target = None
        self.target_fields = None
        self.salience = None
        self.variable_map = {}
        self.conditions = []
        self.actions = []
        self.python_actions = []
        self.aggregator_classes = []
        self.groupby = {}
        self.secondary_rules = []
        self.num_functions = 0
        self.comments = None

        self.premise_varnames = UniqueIdCounter("?fact")
        self.general_varnames = UniqueIdCounter("?var")
Ejemplo n.º 3
0
Archivo: rule.py Proyecto: ubarkai/rulu
    def __init__(self):
        self.premises = defaultdict(RulePremise)
        self.name = None
        self.description = None
        self.target = None
        self.target_fields = None
        self.salience = None
        self.variable_map = {}
        self.variable_values = {}
        self.conditions = []
        self.actions = []
        self.python_actions = []
        self.aggregator_classes = []
        self.groupby = {}
        self.secondary_rules = []
        self.num_functions = 0
        self.comments = None

        self.premise_varnames = UniqueIdCounter('?fact')
        self.general_varnames = UniqueIdCounter('?var')
Ejemplo n.º 4
0
class Rule(object):
    def __init__(self):
        self.premises = defaultdict(RulePremise)
        self.name = None
        self.description = None
        self.target = None
        self.target_fields = None
        self.salience = None
        self.variable_map = {}
        self.conditions = []
        self.actions = []
        self.python_actions = []
        self.aggregator_classes = []
        self.groupby = {}
        self.secondary_rules = []
        self.num_functions = 0
        self.comments = None

        self.premise_varnames = UniqueIdCounter("?fact")
        self.general_varnames = UniqueIdCounter("?var")

    def set_target(self, target=None):
        if self.target_fields is not None:
            raise RuleEngineError("Cannot set both target template and target fields")
        self.target = target
        self.target_fields = target._fields

    def set_name(self, name):
        if self.name not in (None, name):
            raise RuleEngineError('Tried to set target name to "{}", but it is already "{}"'.format(name, self.name))
        self.name = name

    def set_description(self, description):
        self.description = description

    def set_target_fields(self, **fields):
        if self.target is not None:
            raise RuleEngineError("Target already set")
        self.target_fields = {key: FieldExpr(_type=TYPE_MAP.get(_type, _type)) for key, _type in fields.iteritems()}
        self.target = make_slotted_type(Fact, self.name, **self.target_fields)

    def get_target_fields(self):
        return self.target_fields

    def get_target_name(self):
        if self.target is not None and self.target._name is not None:
            return self.target._name
        else:
            return self.name

    def set_salience(self, salience):
        self.salience = salience

    def add_variable(self, *fields):
        matching_var_names = set()
        for field in sorted(fields, key=str):
            if not isinstance(field, (FieldExpr, FactExpr)):
                raise TypeError("Exepected field or fact, found {}".format(field))
            try:
                matching_var_names.add(self.variable_map[field].var_name)
            except KeyError:
                pass

        if not matching_var_names:
            var_name = self.general_varnames.next()
            if fields[0].get_type() is Multifield:
                var_name = "$" + var_name
        else:
            var_names = sorted(matching_var_names)
            var_name = var_names[0]
            for prev_var_name in var_names[1:]:
                self._replace_variable(prev_var_name, var_name)

        for field in fields:
            if isinstance(field, FactExpr):
                self.set_premise(field.fact)
            else:
                self.set_premise(field.container).add(field, var_name)
                self.variable_map[field] = VariableExpr(var_name, field.get_type())
        return var_name

    def add_condition(self, expr):
        condition = Condition(expr)
        for field in condition.all_fields:
            self.add_variable(field)
        self.conditions.append(condition)

    def add_action(self, action):
        for field in action.get_all_fields():
            self.add_variable(field)
        action.prepare_rule(self)
        self.actions.append(action)

    def add_python_action(self, function):
        self.python_actions.append(function)

    def add_aggregator_cls(self, aggregator_cls):
        self.aggregator_classes.append(aggregator_cls)

    def set_comments(self, comments):
        self.comments = comments

    def set_premise(self, container):
        premise = self.premises.get(container)
        if premise is None:
            var_name = self.premise_varnames.next()
            premise = RulePremise(container, var_name)
            self.premises[container] = premise
            self.variable_map[container._to_expr()] = VariableExpr(var_name, FactIndexType)
        return premise

    def set_negative_premise(self, container):
        self.set_premise(container).set_negative()

    def set_groupby(self, **kwargs):
        self.groupby.update(kwargs)

    def add_secondary_rule(self, rule):
        self.secondary_rules.append(rule)

    def _replace_variable(self, prev_var_name, var_name):
        # TODO: Implement this
        raise NotImplementedError()

    @wrap_clips_errors
    def build(self, engine):
        if self.target is not None:
            self._init_target(engine)
        self._init_aggregators(engine)
        if self.python_actions:
            target_name = self.target._name if self.target else None
            self._add_python_action(engine, target_name)
        if self.name is None:
            self.name = "_anonymous_rule"
        rule_num = sum(1 for rule_name in engine.get_rule_names() if rule_name.startswith(self.name + "@"))
        self.clips_name = "{}@{}".format(self.name, rule_num + 1)
        lhs = self._build_lhs()
        rhs = self._build_rhs()
        logger.getChild("rule").debug(
            "Creating rule: %s\n<%s\n%s\n%s>\n%s", self.clips_name, "=" * 20, lhs, "=" * 20, rhs
        )
        self.clips_rule = engine.environment.BuildRule(self.clips_name, lhs, rhs, self.comments)
        for secondary_rule in self.secondary_rules:
            secondary_rule.build(engine)

    def _init_target(self, engine):
        target_name = self.get_target_name()
        if target_name is None:
            raise RuleEngineError("Cannot build rule, target name not set or multiple targets have the same name")
        if self.name is None:
            self.name = target_name
        if self.target._name is None:
            self.target._name = target_name
        if target_name not in engine.clips_types:
            self.target._build(engine)

    def _init_aggregators(self, engine):
        # TODO: Move this to separate module with secondary rule
        self.finalize_rules = []
        for aggregator_cls in self.aggregator_classes:
            aggregator = aggregator_cls(engine=engine, template=self.target)
            engine.preprocess_funcs.append(aggregator.init)
            self.add_python_action(lambda assert_, **kwargs: aggregator.process_one(**kwargs))
            finalize_name = "{}_{}_finalize".format(RULU_INTERNAL_PREFIX, self.name)
            finalize_fact_cls = type(Fact)(finalize_name, (Fact,), {"x": IntegerField()})
            finalize_fact_cls._build(engine)
            target = self.target
            self.target = finalize_fact_cls
            if self.salience is None:
                self.salience = -1000
            self.add_action(Assert(x=0))
            finalize_rule = Rule()
            finalize_rule.set_name(finalize_name)
            finalize_rule.set_premise(finalize_fact_cls)
            finalize_rule.set_salience(self.salience)
            finalize_rule.set_target(target)
            self.set_salience((self.salience) + 1)
            finalize_rule.add_python_action(lambda assert_, **kwargs: aggregator.finalize(assert_))
            finalize_rule.build(engine)
            self.finalize_rules.append(finalize_rule)

    def _add_python_action(self, engine, target_name):
        func = RuleFunc(partial(self._action, engine), Integer, "target_{}".format(target_name))
        params = [premise.container for premise in self.premises.itervalues() if not premise.negative]
        self.actions.append(func(*params).replace_fields(self.variable_map))

    def _build_lhs(self):
        lhs = []
        if self.salience is not None:
            lhs.append(LispExpr("declare", LispExpr("salience", self.salience)))
        lhs.extend(premise.build_str() for premise in sorted(self.premises.itervalues(), key=lambda p: p.var_name))
        lhs.extend(condition.replace_fields(self.variable_map).to_lisp() for condition in self.conditions)
        return "\n".join(str(x) for x in lhs)

    def _build_rhs(self):
        actions_lisp = [action.replace_fields(self.variable_map).to_lisp() for action in self.actions]
        self._postprocess_actions(actions_lisp)
        return "\n".join(map(str, actions_lisp))

    def _postprocess_actions(self, actions_lisp):
        # Placeholder for trace plugin
        pass

    def _make_field_map(self):
        return {}

    def _action(self, _engine, *facts):
        params = {}
        vars = [var for var, premise in self.premises.iteritems() if not premise.negative]
        for var, fact in zip(vars, facts):
            var._update_python_param(params, fact)
        if self.target is not None:
            params["assert_"] = lambda **kwargs: _engine.assert_(self.target, **kwargs)
        self._run_python_actions(**params)

    def _run_python_actions(self, **params):
        for func in self.python_actions:
            try:
                func(**params)
            except:
                logger.error("Exception while running {} in rule {}.".format(func, self.name))
                raise
Ejemplo n.º 5
0
Archivo: rule.py Proyecto: ubarkai/rulu
class Rule(object):
    def __init__(self):
        self.premises = defaultdict(RulePremise)
        self.name = None
        self.description = None
        self.target = None
        self.target_fields = None
        self.salience = None
        self.variable_map = {}
        self.variable_values = {}
        self.conditions = []
        self.actions = []
        self.python_actions = []
        self.aggregator_classes = []
        self.groupby = {}
        self.secondary_rules = []
        self.num_functions = 0
        self.comments = None

        self.premise_varnames = UniqueIdCounter('?fact')
        self.general_varnames = UniqueIdCounter('?var')

    def set_target(self, target=None):
        if self.target_fields is not None:
            raise RuleEngineError(
                'Cannot set both target template and target fields')
        self.target = target
        self.target_fields = target._fields

    def set_name(self, name):
        if self.name not in (None, name):
            raise RuleEngineError(
                'Tried to set target name to "{}", but it is already "{}"'.
                format(name, self.name))
        self.name = name

    def set_description(self, description):
        self.description = description

    def set_target_fields(self, **fields):
        if self.target is not None:
            raise RuleEngineError('Target already set')
        self.target_fields = {
            key: FieldExpr(_type=TYPE_MAP.get(_type, _type))
            for key, _type in fields.iteritems()
        }
        self.target = make_slotted_type(Fact, self.name, **self.target_fields)

    def get_target_fields(self):
        return self.target_fields

    def get_target_name(self):
        if self.target is not None and self.target._name is not None:
            return self.target._name
        else:
            return self.name

    def set_salience(self, salience):
        self.salience = salience

    def add_variable(self, *exprs):
        free_value = None
        matching_var_names = set()
        for expr in sorted(exprs, key=str):
            if isinstance(expr, (FieldExpr, FactExpr)):
                try:
                    matching_var_names.add(self.variable_map[expr].var_name)
                except KeyError:
                    pass
            else:
                expr = normalize_expr(expr)
                if free_value is None:
                    free_value = expr
                elif free_value != expr:
                    raise ValueError(
                        'Conflicting unbound values: {}, {}'.format(
                            free_value, expr))

        if not matching_var_names:
            var_name = self.general_varnames.next()
            if exprs[0].get_type() is Multifield:
                var_name = '$' + var_name
        else:
            var_names = sorted(matching_var_names)
            var_name = var_names[0]
            for prev_var_name in var_names[1:]:
                self._replace_variable(prev_var_name, var_name)

        for expr in exprs:
            if isinstance(expr, FactExpr):
                self.set_premise(expr.fact)
                if free_value is not None:
                    raise ValueError('Type mismatch: {}, {}'.format(
                        expr, free_value))
            elif isinstance(expr, FieldExpr):
                self.set_premise(expr.container).add(expr, var_name)
                self.variable_map[expr] = VariableExpr(var_name,
                                                       expr.get_type())
                if free_value is not None:
                    self.variable_values[var_name] = free_value.to_lisp()
        return var_name

    def add_condition(self, expr):
        condition = Condition(expr)
        for field in condition.all_fields:
            self.add_variable(field)
        self.conditions.append(condition)

    def add_action(self, action):
        for field in action.get_all_fields():
            self.add_variable(field)
        action.prepare_rule(self)
        self.actions.append(action)

    def add_python_action(self, function):
        if function.__name__ == '<lambda>':
            function.__name__ = 'lambda'
        self.python_actions.append(function)

    def add_aggregator_cls(self, aggregator_cls):
        self.aggregator_classes.append(aggregator_cls)

    def set_comments(self, comments):
        self.comments = comments

    def set_premise(self, container):
        premise = self.premises.get(container)
        if premise is None:
            var_name = self.premise_varnames.next()
            premise = RulePremise(container, var_name)
            self.premises[container] = premise
            self.variable_map[container._to_expr()] = VariableExpr(
                var_name, FactIndexType)
        return premise

    def set_negative_premise(self, container):
        self.set_premise(container).set_negative()

    def set_groupby(self, **kwargs):
        self.groupby.update(kwargs)

    def add_secondary_rule(self, rule):
        self.secondary_rules.append(rule)

    @wrap_clips_errors
    def build(self, engine):
        if self.target is not None:
            self._init_target(engine)
        self._init_aggregators(engine)
        if self.python_actions:
            target_name = self.target._name if self.target else None
            self._add_python_action(engine)
        if self.name is None:
            self.name = '_anonymous_rule'
        rule_num = sum(1 for rule_name in engine.get_rule_names()
                       if rule_name.startswith(self.name + '@'))
        self.clips_name = '{}@{}'.format(self.name, rule_num + 1)
        lhs = self._build_lhs()
        rhs = self._build_rhs()
        logger.getChild('rule').debug('Creating rule: %s\n<%s\n%s\n%s>\n%s',
                                      self.clips_name, '=' * 20, lhs, '=' * 20,
                                      rhs)
        self.clips_rule = engine.environment.BuildRule(self.clips_name, lhs,
                                                       rhs, self.comments)
        for secondary_rule in self.secondary_rules:
            secondary_rule.build(engine)

    def _init_target(self, engine):
        target_name = self.get_target_name()
        if target_name is None:
            raise RuleEngineError(
                'Cannot build rule, target name not set or multiple targets have the same name'
            )
        if self.name is None:
            self.name = target_name
        if self.target._name is None:
            self.target._name = target_name
        if target_name not in engine.clips_types:
            self.target._build(engine)

    def _init_aggregators(self, engine):
        # TODO: Move this to separate module with secondary rule
        self.finalize_rules = []
        for aggregator_cls in self.aggregator_classes:
            aggregator = aggregator_cls(engine=engine, template=self.target)
            engine.preprocess_funcs.append(aggregator.init)
            self.add_python_action(
                lambda assert_, **kwargs: aggregator.process_one(**kwargs))
            finalize_name = '{}_{}_finalize'.format(RULU_INTERNAL_PREFIX,
                                                    self.name)
            finalize_fact_cls = type(Fact)(finalize_name, (Fact, ), {
                'x': IntegerField()
            })
            finalize_fact_cls._build(engine)
            target = self.target
            self.target = finalize_fact_cls
            if self.salience is None: self.salience = -1000
            self.add_action(Assert(x=0))
            finalize_rule = Rule()
            finalize_rule.set_name(finalize_name)
            finalize_rule.set_premise(finalize_fact_cls)
            finalize_rule.set_salience(self.salience)
            finalize_rule.set_target(target)
            self.set_salience((self.salience) + 1)
            finalize_rule.add_python_action(
                lambda assert_, **kwargs: aggregator.finalize(assert_))
            finalize_rule.build(engine)
            self.finalize_rules.append(finalize_rule)

    def _add_python_action(self, engine):
        func = RuleFunc(partial(self._action, engine), Integer,
                        '_'.join(x.__name__ for x in self.python_actions))
        params = [
            premise.container for premise in self.premises.itervalues()
            if not premise.negative
        ]
        self.actions.append(func(*params).replace_fields(self.variable_map))

    def _build_lhs(self):
        lhs = []
        if self.salience is not None:
            lhs.append(LispExpr('declare', LispExpr('salience',
                                                    self.salience)))
        for premise in sorted(self.premises.itervalues(),
                              key=lambda p: p.var_name):
            premise.replace_values(self.variable_values)
            lhs.append(premise.build_str())
        lhs.extend(
            condition.replace_fields(self.variable_map).to_lisp()
            for condition in self.conditions)
        return '\n'.join(str(x) for x in lhs)

    def _build_rhs(self):
        actions_lisp = [
            action.replace_fields(self.variable_map).to_lisp()
            for action in self.actions
        ]
        self._postprocess_actions(actions_lisp)
        return '\n'.join(map(str, actions_lisp))

    def _postprocess_actions(self, actions_lisp):
        # Placeholder for trace plugin
        pass

    def _make_field_map(self):
        return {}

    def _action(self, _engine, *facts):
        params = {}
        vars = [
            var for var, premise in self.premises.iteritems()
            if not premise.negative
        ]
        for var, fact in zip(vars, facts):
            var._update_python_param(params, fact)
        if self.target is not None:
            params['assert_'] = lambda **kwargs: _engine.assert_(
                self.target, **kwargs)
        self._run_python_actions(**params)

    def _run_python_actions(self, **params):
        for func in self.python_actions:
            try:
                func(**params)
            except:
                logger.error('Exception while running {} in rule {}.'.format(
                    func, self.name))
                raise