buguroo / pyknow

PyKnow: Expert Systems for Python
GNU Lesser General Public License v3.0
470 stars 141 forks source link

Got a AttributeError: 'Rule' object has no attribute '__name__' #39

Open wangyingsm opened 5 years ago

wangyingsm commented 5 years ago

Background, I am new to Pyknow. I want to build a expert system to auto calculate a cell quality score based on some parameters. The score algorithm should be loaded from a json data, such like:

    {
        "PN": [{
            "condition": "pn",
            "symbol": "=",
            "value": "2PN",
            "score": "20",
            "weight": "1.0"
        }, {
            "condition": "time",
            "symbol": "<=",
            "value": "12",
            "score": "20",
            "weight": "1.0"
        }],
        "4C": [{
            "condition": "cell",
            "symbol": "=",
            "value": "4C",
            "score": "20",
            "weight": "1.0"
        }, {
            "condition": "even",
            "symbol": "=",
            "value": true,
            "score": "20",
            "weight": "1.0"
        }, {
            "condition": "fragment",
            "symbol": "=",
            "value": "<5%",
            "score": "20",
            "weight": "1.0"
        }, {
            "condition": "fragment",
            "symbol": "=",
            "value": "5%-10%",
            "score": "15",
            "weight": "1.0"
        }, {
            "condition": "fragment",
            "symbol": "=",
            "value": "10%-20%",
            "score": "10",
            "weight": "1.0"
        }, {
            "condition": "time",
            "symbol": "<=",
            "value": "36",
            "score": "20",
            "weight": "1.0"
        }]
    }

Here are my codes for this,

    from pyknow import *
    from functools import partial

    class EmbryoScore(KnowledgeEngine):
        score = 0
        @classmethod
        def removeAllRules(cls):
            for m in [attr for attr in cls.__dict__ if attr.startswith('rule')]:
                delattr(cls, m)

    def parse_json_rules(rule_json):
        import json
        rules = json.loads(rule_json)
        sal = 100
        index = 0
        for stage in rules:
            for rule_item in rules[stage]:
                rule = {'condition': rule_item['condition']}
                rule['stage'] = stage
                if rule_item['symbol'] not in ('=', '<', '<=', '>', '>='):
                    continue
                def _cond(value, symbol):
                    if symbol == '=': return EQ(value)
                    if symbol == '<': return LT(value)
                    if symbol == '<=': return LE(value)
                    if symbol == '>': return GT(value)
                    if symbol == '>=': return GE(value)

                def _add(self, **kwargs):
                    if 'score' not in kwargs or 'weight' not in kwargs:
                        raise ValueError('No score or weight specified')
                    s, w = int(kwargs['score']), float(kwargs['weight'])
                    self.score += s * w

                R = Rule(AND(Fact(**rule, value=MATCH.value & 
                        _cond(rule_item['value'], rule_item['symbol']))),
                        salience=sal)(partial(_add, score=rule_item['score'], 
                        weight=rule_item['weight']))
                setattr(EmbryoScore, f'rule{index}', R)
                index += 1
                sal -= 1

    def init_engine(rule_json):
        EmbryoScore.removeAllRules()
        parse_json_rules(rule_json)
        engine = EmbryoScore()
        engine.reset()
        return engine

    import unittest

    class ScoreTest(unittest.TestCase):
        def test(self):
            engine = init_engine(rule_json)
            engine.declare(Fact(condition='pn', stage='PN', value='2PN'))
            engine.declare(Fact(stage='4C', condition='cell', value='4C'))
            engine.declare(Fact(stage='4C', condition='fragment', value='10%-20%'))
            engine.declare(Fact(stage='4C', condition='time', value='32'))
            engine.run()
            self.assertEqual(engine.score, 70)

    if __name__ == '__main__':
        unittest.main()

It raises a AttributeError: 'Rule' object has no attribute 'name' when engine.run() on engine.py line 162

    watchers.RULES.info(
         "FIRE %s %s: %s",
         execution,
         activation.rule.__name__,
         ", ".join(str(f) for f in activation.facts))

I can't find out what the problem is. Wonder if I can get some help here. THANKS.

nilp0inter commented 5 years ago

Hi,

the error you are receiving is due to the usage of partial functions in the RHS of the rule.

You can solve this using a closure instead of a partial function:

            def _add_partial(score, weight):
                def _add(self, **kwargs):
                    s, w = int(score), float(weight)
                    self.score += s * w
                return _add

            R = Rule(AND(Fact(**rule, value=MATCH.value & 
                    _cond(rule_item['value'], rule_item['symbol']))),
                    salience=sal)(_add_partial(score=rule_item['score'], 
                                               weight=rule_item['weight']))

Hope this help!

wangyingsm commented 5 years ago

Hi,

the error you are receiving is due to the usage of partial functions in the RHS of the rule.

You can solve this using a closure instead of a partial function:

            def _add_partial(score, weight):
                def _add(self, **kwargs):
                    s, w = int(score), float(weight)
                    self.score += s * w
                return _add

            R = Rule(AND(Fact(**rule, value=MATCH.value & 
                    _cond(rule_item['value'], rule_item['symbol']))),
                    salience=sal)(_add_partial(score=rule_item['score'], 
                                               weight=rule_item['weight']))

Hope this help!

Wow. It DOES WORK. THANKS.