summaryrefslogtreecommitdiff
path: root/prolog/problems/family_relations/cousin_2/common.py
blob: cd5cd38e9047808f5f62127b13780e3897596bfe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# coding=utf-8

from operator import itemgetter
import socket
import prolog.engine
import prolog.util
from server.hints import Hint, HintSequence
import server.problems

id = 99
group = 'family_relations'
number = 6
visible = True
facts = 'family_relations'

solution = '''\
sister99(X, Y) :-
  parent(P, X),
  parent(P, Y),
  female(X),
  X \== Y.

brother99(X, Y) :-
  parent(P, X),
  parent(P, Y),
  male(X),
  X \== Y.

cousin(X, Y) :-
  parent(PX, X),
  parent(PY, Y),
  ( brother99(PX, PY)
    ;
    sister99(PX, PY) ).
'''

hint_type = {
    'gender_is_irrelevant': Hint('gender_is_irrelevant'),
    'precedence_fail': Hint('precedence_fail'),
    'cousin_vs_sibling': Hint('cousin_vs_sibling'),
    'cousin_to_oneself': Hint('cousin_to_oneself'),
    'cousin_need_not_be_parent': Hint('cousin_need_not_be_parent'),
}

test_cases = [
    # TODO
]

def test(program, solved_problems):
    code = (program + '\n' +
            server.problems.solutions_for_problems('prolog', solved_problems) + '\n' +
            server.problems.load_facts('prolog', facts).facts)

    engine_id = None
    try:
        engine_id, output = prolog.engine.create(code=code, timeout=1.0)
        if not engine_id or 'error' in map(itemgetter(0), output):
            # Engine creation failed, or syntax error in code.
            return 0, len(test_cases)

        n_correct = 0
        for query, answers in test_cases:
            # Limit inferences for each solution to curb unbounded recursion.
            limited = 'call_with_inference_limit(({}), 100000, _)'.format(query)
            if prolog.engine.check_answers(engine_id, query=limited, answers=answers, timeout=1.0):
                n_correct += 1
    finally:
        if engine_id:
            prolog.engine.destroy(engine_id)

    passed = n_correct == len(test_cases)
    hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}]
    return passed, hints

def hint(program, solved_problems):
    tokens = prolog.util.tokenize(program)

    code = (program + '\n' +
            server.problems.solutions_for_problems('prolog', solved_problems) + '\n' +
            server.problems.load_facts('prolog', facts).facts)

    engine_id = None
    try:
        engine_id, output = prolog.engine.create(code=code, timeout=1.0)
        if not engine_id:
            raise Exception('Prolog engine failed to create.')

        # gender testing is redundant
        # this is not necessarily wrong, but worth mentioning anyway
        targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female')]
        pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets]
        if pos:
            # TODO: resolve how highlighting info is communicated
            return [{'id': 'gender_is_irrelevant', 'highlight': pos}]

        # precedence fail (AND block vs OR block)
        # case in point: parent(PX, X), parent(PY, Y), brother(PX, PY) ; sister(PX, PY)
        #  or this case: parent(PX, X), parent(PY, Y), sister(PX, PY) ; brother(PX, PY)
        # warning: knowledge base dependent
        if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id,
            'findall(_, cousin(X, Y), L), (length(L, 14) ; length(L, 16))'):
            return [{'id', 'precedence_fail'}]

        # cousin should not be brother or sister
        # common parent, probably solved via grandparent
        if prolog.engine.ask_truth(engine_id,
            'cousin(X, Y), (brother(X, Y) ; sister(X, Y))'):
            return [{'id', 'cousin_vs_sibling'}]

        # cousin to him/herself
        # just common grandparent is not enough
        if prolog.engine.ask_truth(engine_id, 'cousin(X, X)'):
            return [{'id', 'cousin_to_oneself'}]


        # X (or Y) does not necessarily need to be a parent
        # cousin is a symmetrical relation, so X and Y are covered by this
        if prolog.engine.ask_one(engine_id,
            'cousin(X, _), \+ parent(X, _)') == 'false':
            return [{'id': 'cousin_need_not_be_parent'}]

    except socket.timeout as ex:
        pass

    finally:
        if engine_id:
            prolog.engine.destroy(engine_id)

    return None