summaryrefslogtreecommitdiff
path: root/prolog/problems/family_relations/grandparent_2/common.py
blob: 1665c182baec14ff0d0ded0e9b6b6ca19f191153 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
# coding=utf-8

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

id = 95
number = 3
visible = True
facts = 'family_relations'

solution = '''\
grandparent(X, Y) :-
  parent(X, Z),
  parent(Z, Y).
'''

hint_type = {
    'no_common_z': Hint('no_common_z'),
    'gender_is_irrelevant_markup': HintPopup('gender_is_irrelevant_markup'),
    'gender_is_irrelevant': Hint('gender_is_irrelevant'),
    'or_instead_of_and': Hint('or_instead_of_and'),
    'x_must_be_parent': Hint('x_must_be_parent'),
    'x_need_not_have_parent': Hint('x_need_not_have_parent'),
    'y_must_have_parent': Hint('y_must_have_parent'),
    'y_need_not_be_parent': Hint('y_need_not_be_parent'),
    'predicate_always_false': Hint('predicate_always_false'),
}

test_cases = [
    ('grandparent(X, _)',
        [{'X': 'aleksandr'}, {'X': 'ana'}, {'X': 'andrew'}, {'X': 'estelle'}, {'X': 'frank'},
         {'X': 'helen'}, {'X': 'jill'}, {'X': 'margaret'}, {'X': 'morty'}, {'X': 'patricia'},
         {'X': 'sally'}, {'X': 'thomas'}, {'X': 'tina'}, {'X': 'william'}]),
    ('grandparent(_, X)',
        [{'X': 'andrew'}, {'X': 'anna'}, {'X': 'daniela'}, {'X': 'joanne'}, {'X': 'john'},
         {'X': 'kramer'}, {'X': 'luana'}, {'X': 'melanie'}, {'X': 'michael'}, {'X': 'michelle'},
         {'X': 'patricia'}, {'X': 'steve'}, {'X': 'susan'}, {'X': 'vanessa'}]),
    ('grandparent(tina, X)',
        [{'X': 'vanessa'}, {'X': 'patricia'}]),
]

def test(code, aux_code):
    n_correct = 0
    engine_id = None
    try:
        engine_id, output = prolog.engine.create(code=code+aux_code, timeout=1.0)
        if engine_id is not None and 'error' not in map(itemgetter(0), output):
            # Engine successfully created, and no syntax error in program.
            for query, answers in test_cases:
                if prolog.engine.check_answers(engine_id, query=query, answers=answers, timeout=1.0):
                    n_correct += 1
    except socket.timeout:
        pass
    finally:
        if engine_id:
            prolog.engine.destroy(engine_id)

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

def hint(code, aux_code):
    tokens = prolog.util.tokenize(code)

    try:
        engine_id, output = prolog.engine.create(code=code+aux_code, timeout=1.0)

        # TODO: generic hint for:
        # TODO: parent(X, Z1), parent(Z2, Y), Z1 = Z2

        # gender testing is redundant
        # this is not necessarily wrong, but worth mentioning anyway
        targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female'),
                   prolog.util.Token('NAME', 'mother'), prolog.util.Token('NAME', 'father')]
        marks = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets]
        if marks:
            return [{'id': 'gender_is_irrelevant_markup', 'start': m[0], 'end': m[1]} for m in marks] + \
                   [{'id': 'gender_is_irrelevant'}]

        # target predicate seems to always be false
        if not prolog.engine.ask_truth(engine_id, 'grandparent(_, _)'):
            return [{'id': 'predicate_always_false'}]

        # broken "link" mistake -- no common parent/child Z
        # warning: due to speed considerations this (1024) is knowledge base dependent
        # independent: findall(_, (parent(X, Z1), parent(Z2, Y)), L)
        if prolog.engine.ask_truth(engine_id,
            'findall(_, grandparent(X, Y), L), length(L, 1024)'):
            return [{'id': 'no_common_z'}]

        # OR (;) instead of AND (,)
        # this hint has to be before the next two
        # as otherwise those two would always override it
        # and not convey the same (amount of) help/information
        # warning: due to speed considerations this (64) is knowledge base dependent
        if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id,
            'findall(_, grandparent(X, Y), L), length(L, 64)'):
            return [{'id': 'or_instead_of_and'}]

        # X must be a parent
        if prolog.engine.ask_truth(engine_id,
            'grandparent(X, _), \+ parent(X, _)'):
            return [{'id': 'x_must_be_parent'}]

        # Y must have a parent
        if prolog.engine.ask_truth(engine_id,
            'grandparent(_, Y), \+ parent(_, Y)'):
            return [{'id': 'y_must_have_parent'}]

        # stop further hint testing if target predicate is always true or always false
        if prolog.engine.ask_truth(engine_id,
            '\+ grandparent(_, _) ; findall(X/Y, grandparent(X, Y), [A/B]), var(A), var(B)'):
            return []

        # X does not necessarily need to have a parent
        if prolog.engine.ask_truth(engine_id, 'grandparent(X, _)') and \
           prolog.engine.ask_one(engine_id, 'grandparent(X, _), \+ parent(_, X)') == 'false':
            return [{'id': 'x_need_not_have_parent'}]

        # Y does not necessarily need to be a parent
        if prolog.engine.ask_truth(engine_id, 'grandparent(_, Y)') and \
           prolog.engine.ask_one(engine_id, 'grandparent(_, Y), \+ parent(Y, _)') == 'false':
            return [{'id': 'y_need_not_be_parent'}]

    except socket.timeout as ex:
        pass

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

    return []

# sudo mount -o ro /dev/sdaX /mnt
# things to discuss with Tim
#   1o can we make predicate_always_false a ghint?
#      with this in mind: how can we supply/know the correct target name and arity?
#   2x popraviti je potrebno spremni tekst glede na novo lokacijo gumba Namig
#   3x  what's the name of \= token? And what of =\= token?
#   4  final hint! This very much improves the user experience, adds knowledge, therefore needs to be implemented now!