Ejemplo n.º 1
0
def convert(line):
    s = OrderedDict([
        ('==', 'Equal'),
        ('!=', 'NotEqual'),
        ('!~=', 'NotAlmostEqual'),
        ('~=', 'AlmostEqual'),
        (lambda line: bool(line.count('>') > 1), partial(_convert, '>', 'Greater')),
        (lambda line: bool(line.count('<') > 1), partial(_convert, '<', 'Less')),
        ('>=', 'GreaterEqual'),
        ('<=', 'LessEqual'),
        ('>', 'Greater'),
        ('<', 'Less'),
        ('raises', lambda line: ['with self.assertRaises(' + line.split('raises')[1].strip() + '):', '    ' + line.split('raises')[0].strip()]),
        (' is not instanceof ', 'NotIsInstance'),
        (' is instanceof ', 'IsInstance'),
        ('for ', lambda line: line),
        (' not in ', 'NotIn'),
        (' in ', 'In'),
        (' is not None', 'IsNotNone'),
        (' is None', 'IsNone'),
        (' is not ', 'IsNot'),
        (' is ', 'Is'),
    ])

    def to_lambda(i):
        def to_code(k, v, line):
            l, op, r = line.rpartition(k)
            params = ', '.join(map(strip, (l, r))).rstrip().rstrip(',')

            return 'self.assert' + v + '(' + params + ')'

        k, v = i
        new_v = v if callable(v) else partial(to_code, k, v)

        del s[k]

        if callable(k):
            s[k] = new_v
        else:
            s[lambda line: k in line] = new_v
    
    map(to_lambda, s.items())
    matches = filter(lambda k: k(line), s.keys())
    return s[matches[0]](line) if len(matches) else line
Ejemplo n.º 2
0
class Engine(object):

    def __init__(self, ruleset_ids=None, var_list=None, test_ids=None):
        self.ruleset_ids = []
        self.test_ids = []
        self.fact_state =  OrderedDict()
        self.rec_nodes = []
        self.debug = False
        self.vars_tested = set()
        # restore state
        if ruleset_ids:
            for rsid in ruleset_ids:
                self.ruleset_ids.append(rsid)
        if var_list:
            self.add_vars(var_list, FACT_ASSERTED)
        if test_ids:
            for var_id in test_ids:
                self.test_ids.append(var_id)

    # list of ruleset to use
    def get_rulesets(self):
        return self.ruleset_ids

    # return list of variables which has been
    # tested/established/need to be tested
    def get_vars(self, need_state=False):
        var_list = []
        for key, state in self.fact_state.items():
            vid,value = self.decode_fact_key(key)
            if need_state:
                var_list.append((vid,value,state))
            else:
                var_list.append((vid,value))
        return var_list

    def get_tests(self):
        return self.test_ids

    def gen_fact_key(self, var_id, value):
        return '%d:%s' % (var_id, value)

    def decode_fact_key(self, key):
        idstr,value = key.split(':',1)
        var_id = int(idstr)
        return var_id,value

    def create_vnode(self, var_id, value):
        key = self.gen_fact_key(var_id, value)
        if key in self.fact_state:
            state = NODE_PASSED
        elif var_id in self.vars_tested:
            state = NODE_TESTED
        else:
            state = NODE_UNTESTED
        return FactNode(VAR_NODE, var_id, value, state)

    def get_unique_rnodes(self):
        # first get unique list of recommends nodes
        rdict = {}
        for rnode in self.rec_nodes:
            if rnode.node_id in rdict:
                # replace only if node has higher rank
                onode = rdict[rnode.node_id]
                if rnode.value > onode.value:
                    rdict[rnode.node_id] = rnode
            else:
                rdict[rnode.node_id] = rnode
        return rdict.values()

    def get_questions(self):
        questions = []
        for variable in Variable.objects.filter(id__in=self.test_ids):
            if variable.ask:
                questions.append(variable)
        return questions

    # return list of recommendation for rules that have fired
    # TODO fix views to use only get_reasons call
    def get_recommends(self):
        rnodes = self.get_unique_rnodes()
        recommends = []
        for rnode in rnodes:
            recommend = Recommend.objects.get(pk=rnode.node_id)
            # FIXME - this is a hack
            recommend.rank = rnode.value
            recommends.append(recommend)

        return sorted(recommends, key=lambda recommend: recommend.rank, reverse=True)

    def get_answers(self):
        answers = []
        for key, state in self.fact_state.items():
            if state == FACT_ANSWERED:
                vid,value = self.decode_fact_key(key)
                # TODO should we include questions not answered
                if value:
                    answers.append((vid,value))
        return answers

    # reverse climb the tree to the top node (we use node_set to prevent loops)
    def next_premises(self, search_premises, qa_list, node_set):
        if len(search_premises) == 0:
            return
        tnode_list = []
        for tnode in reversed(search_premises[0].get_nodes()):
            if tnode not in node_set:
                try:
                    var = Variable.objects.get(pk=tnode.node_id)
                except Variable.DoesNotExist:
                    continue
                # FIXME this really should be the fact name
                text = var.prompt if len(var.prompt) > 0 else var.name
                qa = (text, tnode.value)
                qa_list.append(qa)
                tnode_list.append(tnode)
                node_set.add(tnode)
        for tnode in tnode_list:
            self.next_premises(tnode.get_premises(), qa_list, node_set)

    def get_reasons(self):
        # first get unique list of recommends nodes
        rnode_list = self.get_unique_rnodes()
        # now build a list of the reasons that go with them
        reasons = []
        for rnode in rnode_list:
            qa_list = []
            node_set = set()
            self.next_premises(rnode.get_premises(), qa_list, node_set)
            recommend = Recommend.objects.get(pk=rnode.node_id)
            reasons.append(Reason(
                    recommend.id,
                    recommend.name,
                    recommend.text, 
                    rnode.value, 
                    qa_list))

        return sorted(reasons, key=lambda reason: reason.rank, reverse=True)

    # asserted fact = variable that has been assinged a value
    def add_var(self, var_id, value, state):
        # record that we've seen this variable
        self.vars_tested.add(var_id)
        # update fact state
        key = self.gen_fact_key(var_id, value)
        if key not in self.fact_state:
            self.fact_state[key] = state
        else:
            # ASSERTED < ANSWERED < INFERRED
            curr_state = self.fact_state[key]
            if state < curr_state:
                self.fact_state[key] = state

    def add_vars(self, facts, default_state):
        for item in facts:
            state = item[2] if len(item) > 2 else default_state
            self.add_var(item[0], item[1], state)

    def get_groups(self, tree, rule):
        group_list = []
        parser = PremiseParser()
        # parse ast
        plist = []
        for premise in rule.rulepremise_set.all():
            plist.append(premise)
        try:
            root = parser.parse(plist)
            root = wff_dnf(root)
        except PremiseException as e:
            if self.debug: print e, plist
            return group_list
        # now get each or group
        or_list = []
        grab_or_nodes(root, or_list)
        for or_group in or_list:
            premise_group = FactGroup()
            pnode_list = []
            flatten_node(or_group, pnode_list)
            for pnode in pnode_list:
                if pnode.ptype != PTYPE_VAR:
                    if self.debug:
                        print '!!PremiseNode not a var', pnode
                        continue
                variable_id = pnode.left
                value = pnode.right
                tnode = tree.get_fact(variable_id, value)
                if not tnode:
                    tnode = self.create_vnode(variable_id, value)
                    tree.add_fact(tnode)
                premise_group.add_node(tnode)
            group_list.append(premise_group)
        return group_list

    def build_tree(self, ruleset):
        tree = FactTree()
        for rule in ruleset.rule_set.all():
            group_list = self.get_groups(tree, rule)
            """
            premise_group = FactGroup()
            for premise in rule.rulepremise_set.all():
                node = tree.get_fact(premise.variable_id, premise.value)
                if not node:
                    node = self.create_vnode(premise.variable_id, premise.value)
                    tree.add_fact(node)
                premise_group.add_node(node)
            """
            for premise_group in group_list:
                conclusion_nodes = []
                for conclusion in rule.ruleconclusion_set.all():
                    node = tree.get_fact(conclusion.variable_id, conclusion.value)
                    if not node:
                        node = self.create_vnode(conclusion.variable_id, conclusion.value)
                        tree.add_fact(node)
                    node.add_premise(premise_group)
                    conclusion_nodes.append(node)
                recommend_nodes = [] 
                for rrecommend in rule.rulerecommend_set.all():
                    node = tree.get_rec(rrecommend.recommend_id, rrecommend.rank)
                    if not node:
                        node = FactNode(REC_NODE, 
                            rrecommend.recommend_id, 
                            rrecommend.rank, 
                            NODE_UNTESTED)
                        tree.add_rec(node)
                    node.add_premise(premise_group)
                    recommend_nodes.append(node)
                premise_group.add_children(conclusion_nodes)
                premise_group.add_children(recommend_nodes)
                tree.add_group(premise_group)
        return tree

    def forward_chain(self, tree):
        # now look for rules that have fired
        test_groups = tree.get_groups()
        new_facts = True
        while new_facts:
            new_facts = False
            next_test = []
            for group in test_groups:
                if group.all_passed():
                    for child in group.get_children():
                        child.set_state(NODE_PASSED)
                        if child.get_type() == VAR_NODE:
                            self.add_var(child.node_id, 
                                child.value, 
                                FACT_INFERRED)
                    new_facts = True
                else:
                    next_test.append(group)
            test_groups = next_test
        return test_groups

    def get_first(self, premise_list, node_set):
        if self.debug:
            print 'get_first premise', [ str(x) for x in premise_list]
        for premise in premise_list:
            num_loop = 0
            for node in premise.get_nodes():
                if self.debug: print 'node', node
                if node in node_set:
                    if self.debug: print '+++++++LOOP++++++++'
                    num_loop += 1
                    continue
                node_set.add(node)
                if node.check_state(NODE_UNTESTED):
                    leafp = self.get_first(node.get_premises(), node_set)
                    if leafp:
                        return leafp
            if num_loop == 0:
                return premise
        return None

    def find_backchains(self, node, node_set):
        if self.debug: print 'find_backchains', node
        backchains = []
        if node in node_set:
            if self.debug: print '+++++++LOOP++++++++'
            #print 'Node', node, 'premises', [ str(x) for x in node_set ]
            return True
        node_set.add(node)
        #print 'Node', node, 'premises', [ str(x) for x in node.get_premises() ]
        for premise in node.get_premises():
            if self.debug: print 'premise', premise
            untested = []
            num_tested = 0
            for pnode in premise.get_nodes():
                if pnode.check_state(NODE_UNTESTED):
                    untested.append(pnode)
                elif pnode.check_state(NODE_TESTED):
                    num_tested = num_tested + 1
            if num_tested == 0 and len(untested) > 0:
                num_backchain = 0
                for pnode in untested:
                    if self.find_backchains(pnode, node_set):
                        num_backchain += 1
                if self.debug: print 'num_backchain', num_backchain, len(untested)
                if num_backchain == len(untested):
                    if self.debug: print 'backchain', premise
                    backchains.append(premise)
        if self.debug: 
            print 'Node', node, 'backchains', [ str(x) for x in backchains ]
        premsies = node.get_premises()
        node.set_premises(backchains)
        return len(premsies) == 0 or len(backchains) > 0
    
    def find_goals(self, tree, unfired):
        test_premises = []
        for rec_node in tree.get_goals():
            if rec_node.check_state(NODE_PASSED):
                if self.debug: print 'Found goal!', rec_node
                self.rec_nodes.append(rec_node)
            else:
                if self.find_backchains(rec_node, set()):
                    premises = rec_node.get_premises()
                    test_premises.append(self.get_first(premises, set()))

        test_ids = []
        for premise in test_premises: 
            for node in premise.get_nodes():
                if node.state == NODE_UNTESTED:
                    try:
                        variable = Variable.objects.get(pk=node.node_id)
                    except Variable.DoesNotExist:
                        continue
                    if variable.ask:
                        test_ids.append(node.node_id)
        if len(test_ids) > 0:
            self.test_ids.append(test_ids[0])
                
    # given some answers update facts base and check if rules have fired
    # we use forward chaining here
    # answers = list of (var_id,value) tuples
    def next_state(self, answers=None):
        # first add asserted facts
        if answers:
            if self.debug: print 'Got answers', answers
            self.add_vars(answers, FACT_ANSWERED)
        # now add variables for which we did not get answers
        for var_id in self.test_ids:
            if var_id not in self.vars_tested:
                self.add_var(var_id, '', FACT_ANSWERED)

        # and reset testable node list
        self.test_ids = []
        # reset rule list
        self.fire_ids = []
        # this really needs to be fixed
        self.rec_nodes = []

        for ruleset in RuleSet.objects.filter(id__in=self.ruleset_ids):
            tree = self.build_tree(ruleset)
            unfired = self.forward_chain(tree)
            self.find_goals(tree, unfired)