Git Product home page Git Product logo

Comments (12)

rkaminsk avatar rkaminsk commented on July 27, 2024

Clingo programs are required to be modular, that is, you cannot redefine atoms in later steps. For the mastermind game, I see no need for this. If I had to solve the problem, I would try to implement something like this:

  • initially there are no constraints on the solution
  • repeat
    • guess a solution (1 { solution(X,C) : color(C) } 1 :- position(X).)
    • if correct stop, otherwise incorporate feedback
    • add the feedback in form of constraints
      • for example, if there are 2 correct positions and say 2 red and 2 green pegs in the solution, add constraints as indicated below (stronger constraints could be added, this is just an example)
      • the idea is that all check_feedback(N,S) atoms have to be satisfied
        • N is an identifier for the feedbacks you want to incorporate
        • S is the number of the current solution (this ensures that there are no redefinitions)
      • the whole thing could probably be implemented in an ASP program with very little python code by just passing the solution with an external function (say @solution(k)) to a program part of form #program add_feedback(k)
      • check the examples (examples/clingo/consequences is quite simple and incorporates feedback)
check_feedback(1,k) :- 2 { solution(P,red) }.
check_feedback(1,k) :- 2 { solution(P,green) }.
:- not check_feedback(1,k).

from clingo.

0x326 avatar 0x326 commented on July 27, 2024

@rkaminsk Thank you! I did not know that clingo programs must be modular. I do not want to pass the solution to clingo because the solution will not be known in the real-world scenarios to which we are trying to apply ASP. However, I am still struggling with not grounding enough.

Perhaps it would be easier if I were to give a precise example with the ASP model I am working with.

With Python

Consider the following files:

mastermind.lp:

%%%%%%%% ========================
#program main(positions, colors).
%%%%%%%% ========================

%% ================== %%
%% Generate solutions %%
%% ================== %%

color_choice(1..colors).
position_index(1..positions).

1 { goal_color(Position, Shade) : color_choice(Shade) } 1 :- position_index(Position).


%% =============== %%
%% Derive feedback %%
%% =============== %%

black_position(GuessNumber, Position) :-
    color_guess(GuessNumber, Position, Shade),
    goal_color(Position, Shade).
    
switch_position(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    PositionFalse != PositionTrue,
    not black_position(GuessNumber, PositionFalse),
    not black_position(GuessNumber, PositionTrue), 
    color_guess(GuessNumber, PositionFalse, Shade),
    goal_color(PositionTrue, Shade).
    
blocked(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue), 
    switch_position(GuessNumber, _, PositionFalse2, PositionTrue),
    PositionFalse != PositionFalse2.
    
withheld(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue),
    switch_position(GuessNumber, _, PositionFalse, PositionTrue2),
    PositionTrue != PositionTrue2.
    
independent(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue),
    not blocked(GuessNumber, Shade, PositionFalse, PositionTrue),
    not withheld(GuessNumber, Shade, PositionFalse, PositionTrue).
    
%% ====================== %%
%% Consolidate predicates %%
%% ====================== %%

blocked(GuessNumber, PositionTrue) :-
    blocked(GuessNumber, Shade, PositionFalse, PositionTrue).

withheld(GuessNumber, PositionTrue) :-
    withheld(GuessNumber, Shade, PositionFalse, PositionTrue),
    not blocked(GuessNumber, Shade, PositionFalse, PositionTrue).
    
independent(GuessNumber, PositionTrue) :-
    independent(GuessNumber, Shade, PositionFalse, PositionTrue).
    
%% ================= %%
%% Generate feedback %%
%% ================= %%

feedback(GuessNumber, Blacks, Whites) :-
    color_guess(GuessNumber, _, _),
    Blacks = #count { 1, Position : black_position(GuessNumber, Position) },
    Whites = #count { 1, PositionTrue : blocked(GuessNumber, PositionTrue);
                      2, PositionFalse : withheld(GuessNumber, PositionFalse);
                      1, PositionTrue : independent(GuessNumber, PositionTrue) }.
                      
:- truth_feedback(GuessNumber, Blacks, Whites), not feedback(GuessNumber, Blacks, Whites).

%% ======================= %%
%% Clingo #show directives %%
%% ======================= %%

#show goal_color/2.

%%%%%%%% =============
#program i_optimality.
%%%%%%%% =============

%% ==================== %%
%% Establish optimality %%
%% ==================== %%

special(GuessNumber, Shade2 - Shade) :-
    color_guess(GuessNumber, Position, Shade2), goal_color(Position, Shade), Shade2 > Shade.
    
special(GuessNumber, Shade - Shade2) :-
    color_guess(GuessNumber, Position, Shade2), goal_color(Position, Shade), Shade2 <= Shade.
 
opt(GuessNumber, PositionTrue) :-
    color_guess(GuessNumber, _, _),
    PositionTrue = #count { Tt : special(GuessNumber, Tt) }.

#maximize { PositionTrue, GuessNumber : opt(GuessNumber, PositionTrue) }.

mastermind_player.py:

import logging
from collections import namedtuple

import clingo

logging.basicConfig(level=logging.DEBUG)
# blacks: correctly placed; correctly colored
# whites: incorrectly placed; correctly colored
Feedback = namedtuple('Feedback', 'blacks, whites')
ASPProgram = namedtuple('ASPProgram', 'name, arguments')
best_guess = {}


def play_mastermind(positions, colors):
    # type: () -> None
    past_guesses = []
    guess_feedback = []
    
    initial_guess = (2, 1, 3, 6)
    # Send random guess to computer with solution
    # Get feedback from computer
    initial_response = Feedback(blacks=0, whites=3)  # Feedback for guess (2, 1, 3, 6)
    past_guesses.append(initial_guess)
    guess_feedback.append(initial_response)
    
    # Prepare to get next guess from clingo
    clingo_control = clingo.Control(['0'])
    clingo_control.load('./mastermind.lp')
    
    # Add guess and feedback to clingo
    add_guess_to_clingo(past_guesses[-1], guess_number=1, clingo_control=clingo_control)
    add_feedback_to_clingo(guess_feedback[-1], guess_number=1, clingo_control=clingo_control)
    
    # Ground clingo
    asp_programs = [
        ASPProgram(name='main', arguments=(positions, colors)),
        ASPProgram(name='i_optimality', arguments=tuple()),
        ASPProgram(name='__assertions__1', arguments=tuple())
    ]
    logging.debug('Grounding: %s', asp_programs)
    clingo_control.ground(asp_programs)
    
    logging.info('Solving')
    clingo_control.solve(on_model=inspect_answer_set)
    
    # Extract guess
    second_guess = [best_guess[position_number + 1] for position_number in range(positions)]
    logging.info('Second guess: %s', second_guess)
    assert second_guess == [6, 6, 1, 3], 'For some reason, your clingo determined a different optimal guess' \
                                         'than what it did for me when I ran this'
    
    # Send guess to computer
    # Get feedback for computer
    second_guess_feedback = Feedback(blacks=1, whites=1)  # Feedback for guess (6, 6, 1, 3)
    past_guesses.append(second_guess)
    guess_feedback.append(second_guess_feedback)
    
    # Prepare to get next guess from clingo
    # Add guess and feedback to clingo
    add_guess_to_clingo(past_guesses[-1], guess_number=2, clingo_control=clingo_control)
    add_feedback_to_clingo(past_guesses[-1], guess_number=2, clingo_control=clingo_control)
    
    # Ground clingo
    asp_programs = [
        ASPProgram(name='__assertions__2', arguments=tuple())
    ]
    logging.debug('Grounding: %s', asp_programs)
    clingo_control.ground(asp_programs)
    
    logging.info('Solving')
    clingo_control.solve(on_model=inspect_answer_set)
    
    # Extract guess
    third_guess = [best_guess[position_number + 1] for position_number in range(positions)]
    logging.info('Third guess: %s', third_guess)
    
    
def add_guess_to_clingo(guess, guess_number, clingo_control):
    # type: ((int,), int, clingo.Control) -> None
    for guess_position, guess_color in enumerate(guess):  # type: int, int
        predicate_arguments = (guess_number, guess_position + 1, guess_color)
        asp_snippet = 'color_guess%s.' % str(predicate_arguments)
        logging.debug('Adding guess to clingo: %s', repr(asp_snippet))
        clingo_control.add('__assertions__%d' % guess_number, tuple(), asp_snippet)

        
def add_feedback_to_clingo(feedback, guess_number, clingo_control):
    # type: ((int,), int, clingo.Control) -> None
    predicate_arguments = (guess_number,) + tuple(feedback)
    asp_snippet = 'truth_feedback%s.' % str(predicate_arguments)
    logging.debug('Adding feedback to clingo: %s', repr(asp_snippet))
    clingo_control.add('__assertions__%d' % guess_number, tuple(), asp_snippet)
    
    
def inspect_answer_set(clingo_model):
    # type: (clingo.Model) -> None
    logging.info('New model!')
    best_guess.clear()
    for symbol in clingo_model.symbols(shown=True):
        if symbol.name == 'goal_color':
            position, color = symbol.arguments
            position, color = position.number, color.number
            best_guess[position] = color
    logging.info('Best guess so far: %s', best_guess)
    
if __name__ == '__main__':
    play_mastermind(positions=4, colors=8)

Execution

If I run the following command

$ python3 mastermind_player.py

The most-optimal third guess is [3, 6, 5, 2].

From commandline

However, consider the following files:

mastermind_by_commandline.lp:

%% =================== %%
%% Python setup script %%
%% =================== %%

#script (python)
from collections import namedtuple

import clingo

ASPProgram = namedtuple('ASPProgram', 'name, arguments')
asp_programs_to_ground = (
    ASPProgram(name='main', arguments=(4, 8)),
    ASPProgram(name='i_optimality', arguments=tuple()),
    ASPProgram(name='__assertions__1', arguments=tuple()),
    ASPProgram(name='__assertions__2', arguments=tuple())
)

def main(clingo_control):
    # type: (clingo.Control) -> None
    clingo_control.ground(asp_programs_to_ground)
    clingo_control.solve()
    
#end.

%%%%%%%% ========================
#program main(positions, colors).
%%%%%%%% ========================

%% ================== %%
%% Generate solutions %%
%% ================== %%

color_choice(1..colors).
position_index(1..positions).

1 { goal_color(Position, Shade) : color_choice(Shade) } 1 :- position_index(Position).


%% =============== %%
%% Derive feedback %%
%% =============== %%

black_position(GuessNumber, Position) :-
    color_guess(GuessNumber, Position, Shade),
    goal_color(Position, Shade).
    
switch_position(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    PositionFalse != PositionTrue,
    not black_position(GuessNumber, PositionFalse),
    not black_position(GuessNumber, PositionTrue), 
    color_guess(GuessNumber, PositionFalse, Shade),
    goal_color(PositionTrue, Shade).
    
blocked(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue), 
    switch_position(GuessNumber, _, PositionFalse2, PositionTrue),
    PositionFalse != PositionFalse2.
    
withheld(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue),
    switch_position(GuessNumber, _, PositionFalse, PositionTrue2),
    PositionTrue != PositionTrue2.
    
independent(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue),
    not blocked(GuessNumber, Shade, PositionFalse, PositionTrue),
    not withheld(GuessNumber, Shade, PositionFalse, PositionTrue).
    
%% ====================== %%
%% Consolidate predicates %%
%% ====================== %%

blocked(GuessNumber, PositionTrue) :-
    blocked(GuessNumber, Shade, PositionFalse, PositionTrue).

withheld(GuessNumber, PositionTrue) :-
    withheld(GuessNumber, Shade, PositionFalse, PositionTrue),
    not blocked(GuessNumber, Shade, PositionFalse, PositionTrue).
    
independent(GuessNumber, PositionTrue) :-
    independent(GuessNumber, Shade, PositionFalse, PositionTrue).
    
%% ================= %%
%% Generate feedback %%
%% ================= %%

feedback(GuessNumber, Blacks, Whites) :-
    color_guess(GuessNumber, _, _),
    Blacks = #count { 1, Position : black_position(GuessNumber, Position) },
    Whites = #count { 1, PositionTrue : blocked(GuessNumber, PositionTrue);
                      2, PositionFalse : withheld(GuessNumber, PositionFalse);
                      1, PositionTrue : independent(GuessNumber, PositionTrue) }.
                      
:- truth_feedback(GuessNumber, Blacks, Whites), not feedback(GuessNumber, Blacks, Whites).

%% ======================= %%
%% Clingo #show directives %%
%% ======================= %%

#show goal_color/2.

%%%%%%%% =============
#program i_optimality.
%%%%%%%% =============

%% ==================== %%
%% Establish optimality %%
%% ==================== %%

special(GuessNumber, Shade2 - Shade) :-
    color_guess(GuessNumber, Position, Shade2), goal_color(Position, Shade), Shade2 > Shade.
    
special(GuessNumber, Shade - Shade2) :-
    color_guess(GuessNumber, Position, Shade2), goal_color(Position, Shade), Shade2 <= Shade.
 
opt(GuessNumber, PositionTrue) :-
    color_guess(GuessNumber, _, _),
    PositionTrue = #count { Tt : special(GuessNumber, Tt) }.

#maximize { PositionTrue, GuessNumber : opt(GuessNumber, PositionTrue) }.

guess_1.lp:

%%%%%%%% =================
#program __asssertions__1.
%%%%%%%% =================

color_guess(1, 1, 2).
color_guess(1, 2, 1).
color_guess(1, 3, 3).
color_guess(1, 4, 6).
truth_feedback(1, 0, 3).

guess_2.lp:

%%%%%%%% =================
#program __asssertions__2.
%%%%%%%% =================

color_guess(2, 1, 6).
color_guess(2, 2, 6).
color_guess(2, 3, 1).
color_guess(2, 4, 3).
truth_feedback(2, 1, 1).

Execution

When I run the following command:

clingo mastermind_by_commandline.lp guess_1.lp guess_2.lp 0

The optimum answer set is:

goal_color(1,1)
goal_color(2,6)
goal_color(3,5)
goal_color(4,2)

Question

The Python version and the command line version get different optimum answer sets (in the first position), though the predicates are the same. Do you know why this is?

from clingo.

0x326 avatar 0x326 commented on July 27, 2024

The solution in this case is [1, 8, 2, 3]

from clingo.

rkaminsk avatar rkaminsk commented on July 27, 2024

I do not want to pass the solution to clingo because the solution will not be known in the real-world scenarios to which we are trying to apply ASP.

That was not what I intended to say. The solution predicate in my example is just meant to capture candidate solutions. The example I pointed you to implements an easy and efficient way to add information from previous to later solving steps using external functions.

The Python version and the command line version get different optimum answer sets (in the first position), though the predicates are the same. Do you know why this is?

The solver is very sensitive to the order in which it receives rules. Even if you just shuffle rules, you typically get the answer sets in another order. You can use the --opt-mode=optN 0 option to enumerate all optimal solutions. If the programs are really equal, you should get the same solutions and the reported optimum value should be the same.

from clingo.

0x326 avatar 0x326 commented on July 27, 2024

@rkaminsk Thanks for the commandline option. I didn't see that before and it helps me in my debugging.

I think we're narrowing down on the problem here. I have one more example that should make the bug plain as day.

I stepped through my system as it was trying to solve mastermind and a kept a running ASP file with equivalent and identical predicates. When running clingo by commandline before the Python grounding and after the Python solving, I noticed that the Python program retrieved an answer set that did not exist. By the seventh guess, the command clingo mastermind_with_guesses.lp --opt-mode=optN 0 returns two models, both of which contain an identical answer set. However, the clingo-Python combo disagrees. It extracts a guess that is not contained in the answer set, guesses the solution wrongly, and continues playing Mastermind indefinetly.

Why does the Python script not finally guess the solution when commandline clingo guesses it as the only possible answer, when predicates are identical in both cases?

mastermind.lp

Same as before

new_dummy_guesser_script.py:

import logging
from collections import namedtuple

import clingo

logging.basicConfig(level=logging.DEBUG)
# blacks: correctly placed; correctly colored
# whites: incorrectly placed; correctly colored
Feedback = namedtuple('Feedback', 'blacks, whites')
ASPProgram = namedtuple('ASPProgram', 'name, arguments')
best_guess = {}


def play_mastermind(positions, colors):
    # type: () -> None
    # When running on my setup, these are the most optimal guesses clingo suggested
    optimal_guesses = (
        (2, 1, 3, 6),
        (8, 8, 3, 3),
        (4, 8, 3, 3),
        (6, 6, 4, 6),
        (8, 8, 7, 6),
        (5, 1, 7, 4),
        (8, 6, 4, 6)
    )
    # For the above guesses, here is the feedback
    feedback_for_guesses = (
        Feedback(blacks=1, whites=0),
        Feedback(blacks=0, whites=0),
        Feedback(blacks=0, whites=0),
        Feedback(blacks=0, whites=0),
        Feedback(blacks=1, whites=0),
        Feedback(blacks=3, whites=0),
        Feedback(blacks=0, whites=0)
    )
    solution = (5, 1, 7, 1)
    
    clingo_control = None
    best_guess_from_clingo = optimal_guesses[0]
    for optimal_guess_number, (optimal_guess, feedback) in enumerate(zip(optimal_guesses, feedback_for_guesses)):  # type: int, (tuple, Feedback)
        optimal_guess_number += 1  # Count from 1 to len(optimal_guesses)
        
        logging.info('optimal_guess %d: %s', optimal_guess_number, best_guess_from_clingo)
        if best_guess_from_clingo != optimal_guess:
            logging.warning('For some reason, your clingo determined a different optimal guess ' 
                            'than what it did for me when I ran this')
        
        asp_programs_to_ground = []
        if optimal_guess_number == 1:
            clingo_control = clingo.Control(['0'])
            clingo_control.load('./mastermind.lp')
            
            asp_programs_to_ground.append(ASPProgram(name='main', arguments=(positions, colors)))
            asp_programs_to_ground.append(ASPProgram(name='i_optimality', arguments=tuple()))
            
        add_guess_to_clingo(optimal_guess, guess_number=optimal_guess_number, clingo_control=clingo_control)
        add_feedback_to_clingo(feedback, guess_number=optimal_guess_number, clingo_control=clingo_control)
        asp_programs_to_ground.append(ASPProgram(name='__assertions__%d' % optimal_guess_number, arguments=tuple()))

        logging.debug('Grounding: %s', asp_programs_to_ground)
        clingo_control.ground(asp_programs_to_ground)

        logging.info('Solving')
        solve_result = clingo_control.solve(on_model=inspect_answer_set)
        
        if solve_result.satisfiable:
            # Extract optimal_guess
            best_guess_from_clingo = tuple(best_guess[position_number + 1] for position_number in range(positions))
        else:
            raise RuntimeError('Solve is not satisfiable')
    assert best_guess_from_clingo == solution, 'clingo with Python is not able to find correct solution ' \
                                               'even though there is only one answer in answer set when running from commandline!'
    
    
def add_guess_to_clingo(guess, guess_number, clingo_control):
    # type: ((int,), int, clingo.Control) -> None
    for guess_position, guess_color in enumerate(guess):  # type: int, int
        predicate_arguments = (guess_number, guess_position + 1, guess_color)
        asp_snippet = 'color_guess%s.' % str(predicate_arguments)
        logging.debug('Adding guess to clingo: %s', repr(asp_snippet))
        clingo_control.add('__assertions__%d' % guess_number, tuple(), asp_snippet)

        
def add_feedback_to_clingo(feedback, guess_number, clingo_control):
    # type: ((int,), int, clingo.Control) -> None
    predicate_arguments = (guess_number,) + tuple(feedback)
    asp_snippet = 'truth_feedback%s.' % str(predicate_arguments)
    logging.debug('Adding feedback to clingo: %s', repr(asp_snippet))
    clingo_control.add('__assertions__%d' % guess_number, tuple(), asp_snippet)
    
    
def inspect_answer_set(clingo_model):
    # type: (clingo.Model) -> None
    logging.info('New model!')
    best_guess.clear()
    for symbol in clingo_model.symbols(shown=True):
        if symbol.name == 'goal_color':
            position, color = symbol.arguments
            position, color = position.number, color.number
            best_guess[position] = color
    logging.info('Best guess so far: %s', best_guess)
    
if __name__ == '__main__':
    play_mastermind(positions=4, colors=8)

mastermind_with_guesses.lp:

%% =================== %%
%% Python setup script %%
%% =================== %%

#script (python)
from collections import namedtuple

import clingo

ASPProgram = namedtuple('ASPProgram', 'name, arguments')
asp_programs_to_ground = (
    ASPProgram(name='main', arguments=(4, 8)),
    ASPProgram(name='i_optimality', arguments=tuple()),
    ASPProgram(name='__assertions__1', arguments=tuple()),
    ASPProgram(name='__assertions__2', arguments=tuple()),
    ASPProgram(name='__assertions__3', arguments=tuple()),
    ASPProgram(name='__assertions__4', arguments=tuple()),
    ASPProgram(name='__assertions__5', arguments=tuple()),
    ASPProgram(name='__assertions__6', arguments=tuple()),
    ASPProgram(name='__assertions__7', arguments=tuple())
)

def main(clingo_control):
    # type: (clingo.Control) -> None
    clingo_control.ground(asp_programs_to_ground)
    clingo_control.solve()
    
#end.

%%%%%%%% ================
#program __assertions__1.
%%%%%%%% ================
color_guess(1,1,2).
color_guess(1,2,1).
color_guess(1,3,3).
color_guess(1,4,6).
truth_feedback(1,1,0).

%%%%%%%% ================
#program __assertions__2.
%%%%%%%% ================
color_guess(2,1,8).
color_guess(2,2,8).
color_guess(2,3,3).
color_guess(2,4,3).
truth_feedback(2,0,0).

%%%%%%%% ================
#program __assertions__3.
%%%%%%%% ================
color_guess(3,1,4).
color_guess(3,2,8).
color_guess(3,3,3).
color_guess(3,4,3).
truth_feedback(3,0,0).

%%%%%%%% ================
#program __assertions__4.
%%%%%%%% ================
color_guess(4,1,6).
color_guess(4,2,6).
color_guess(4,3,4).
color_guess(4,4,6).
truth_feedback(4,0,0).

%%%%%%%% ================
#program __assertions__5.
%%%%%%%% ================
color_guess(5,1,8).
color_guess(5,2,8).
color_guess(5,3,7).
color_guess(5,4,6).
truth_feedback(5,1,0).

%%%%%%%% ================
#program __assertions__6.
%%%%%%%% ================
color_guess(6,1,5).
color_guess(6,2,1).
color_guess(6,3,7).
color_guess(6,4,7).
truth_feedback(6,3,0).

%%%%%%%% ================
#program __assertions__7.
%%%%%%%% ================
color_guess(7,1,8).
color_guess(7,2,6).
color_guess(7,3,4).
color_guess(7,4,6).
truth_feedback(7,0,0).

%%%%%%%% ========================
#program main(positions, colors).
%%%%%%%% ========================

%% ================== %%
%% Generate solutions %%
%% ================== %%

color_choice(1..colors).
position_index(1..positions).

1 { goal_color(Position, Shade) : color_choice(Shade) } 1 :- position_index(Position).


%% =============== %%
%% Derive feedback %%
%% =============== %%

black_position(GuessNumber, Position) :-
    color_guess(GuessNumber, Position, Shade),
    goal_color(Position, Shade).
    
switch_position(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    PositionFalse != PositionTrue,
    not black_position(GuessNumber, PositionFalse),
    not black_position(GuessNumber, PositionTrue), 
    color_guess(GuessNumber, PositionFalse, Shade),
    goal_color(PositionTrue, Shade).
    
blocked(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue), 
    switch_position(GuessNumber, _, PositionFalse2, PositionTrue),
    PositionFalse != PositionFalse2.
    
withheld(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue),
    switch_position(GuessNumber, _, PositionFalse, PositionTrue2),
    PositionTrue != PositionTrue2.
    
independent(GuessNumber, Shade, PositionFalse, PositionTrue) :-
    switch_position(GuessNumber, Shade, PositionFalse, PositionTrue),
    not blocked(GuessNumber, Shade, PositionFalse, PositionTrue),
    not withheld(GuessNumber, Shade, PositionFalse, PositionTrue).
    
%% ====================== %%
%% Consolidate predicates %%
%% ====================== %%

blocked(GuessNumber, PositionTrue) :-
    blocked(GuessNumber, Shade, PositionFalse, PositionTrue).

withheld(GuessNumber, PositionTrue) :-
    withheld(GuessNumber, Shade, PositionFalse, PositionTrue),
    not blocked(GuessNumber, Shade, PositionFalse, PositionTrue).
    
independent(GuessNumber, PositionTrue) :-
    independent(GuessNumber, Shade, PositionFalse, PositionTrue).
    
%% ================= %%
%% Generate feedback %%
%% ================= %%

feedback(GuessNumber, Blacks, Whites) :-
    color_guess(GuessNumber, _, _),
    Blacks = #count { 1, Position : black_position(GuessNumber, Position) },
    Whites = #count { 1, PositionTrue : blocked(GuessNumber, PositionTrue);
                      2, PositionFalse : withheld(GuessNumber, PositionFalse);
                      1, PositionTrue : independent(GuessNumber, PositionTrue) }.
                      
:- truth_feedback(GuessNumber, Blacks, Whites), not feedback(GuessNumber, Blacks, Whites).

%% ======================= %%
%% Clingo #show directives %%
%% ======================= %%

#show goal_color/2.

%%%%%%%% =============
#program i_optimality.
%%%%%%%% =============

%% ==================== %%
%% Establish optimality %%
%% ==================== %%

special(GuessNumber, Shade2 - Shade) :-
    color_guess(GuessNumber, Position, Shade2), goal_color(Position, Shade), Shade2 > Shade.
    
special(GuessNumber, Shade - Shade2) :-
    color_guess(GuessNumber, Position, Shade2), goal_color(Position, Shade), Shade2 <= Shade.
 
opt(GuessNumber, PositionTrue) :-
    color_guess(GuessNumber, _, _),
    PositionTrue = #count { Tt : special(GuessNumber, Tt) }.

#maximize { PositionTrue, GuessNumber : opt(GuessNumber, PositionTrue) }.

from clingo.

rkaminsk avatar rkaminsk commented on July 27, 2024

You have not yet understood how incremental grounding works. Your code initially grounds something and then adds facts in later steps, which do not restrict the solutions of the initial program at all. Just take a look at what is added in each step by adding option --output-debug=text.

The correct way to encode this is to incrementally add integrity costraints restricting the solutions of the initial program (adding a maximize constraint works too but integrity constraints should do the job too (and more efficiently)).

from clingo.

0x326 avatar 0x326 commented on July 27, 2024

@rkaminsk Thank you for being so helpful!

I inserted the following into the subprogram main(positions, colors) to generate every possible guess and feedback:

1 { color_guess(GuessNumber, 1..positions, 1..colors) } 1 :- GuessNumber=1..7.
1 { feedback(GuessNumber, 0..positions, 0..positions) } 1 :- GuessNumber=1..7.

and I have changed the following in the Python script to constrain it:

def add_guess_to_clingo(guess, guess_number, clingo_control):
    # type: ((int,), int, clingo.Control) -> None
    for guess_position, guess_color in enumerate(guess):  # type: int, int
        predicate_arguments = (guess_number, guess_position + 1, guess_color)
        # asp_snippet = 'color_guess%s.' % str(predicate_arguments)
        asp_snippet = ':- not color_guess%s.' % str(predicate_arguments)
        logging.debug('Adding guess to clingo: %s', repr(asp_snippet))
        clingo_control.add('__assertions__%d' % guess_number, tuple(), asp_snippet)

        
def add_feedback_to_clingo(feedback, guess_number, clingo_control):
    # type: ((int,), int, clingo.Control) -> None
    predicate_arguments = (guess_number,) + tuple(feedback)
    # asp_snippet = 'truth_feedback%s.' % str(predicate_arguments)
    asp_snippet = ':- not feedback%s.' % str(predicate_arguments)
    logging.debug('Adding feedback to clingo: %s', repr(asp_snippet))
    clingo_control.add('__assertions__%d' % guess_number, tuple(), asp_snippet)

However, when I run this, I get an unsatisfiable solve by the first solve.

I tried removing the max of 1 in the generative contraint by writing:

1 { color_guess(GuessNumber, 1..positions, 1..colors) } :- GuessNumber=1..7.
1 { feedback(GuessNumber, 0..positions, 0..positions) } :- GuessNumber=1..7.

When I run this, by the seventh solve I get four models with identical goal_color's ([3, 7, 1, 1]) but is not the solution.

Oddly enough, when I run the same code with the flag '-t %d' % os.cpu_count() there are two different models by the seventh solve ([4, 8, 3, 1] and [4, 7, 1, 8]).

from clingo.

rkaminsk avatar rkaminsk commented on July 27, 2024

Adding the -t option introduces non-determinism. If there are multiple optimal solutions in a step, you might get different ones with each run. Also changing the encoding might change the order in which you get optimal solutions. Both possibly resulting in wrong feedback because you encoded this statically.

Further, it is certainly possible to initially add rules for all the possible feedbacks. This way, the solver should produce answers where arbitrary solutions and matching feedbacks are selected. Incrementally adding integrity constraints, you can restrict the solutions until only one is left. The problem can be solved like this but the way I indicated in my second comment is better.

Also, the rules you added in your last comment seem quite ad hoc to me. For example, the bounds make no sense.

from clingo.

rkaminsk avatar rkaminsk commented on July 27, 2024

I attached a simplified version of the mastermind game, where only correct pins are considered in the feedback. I hope this gives you the right idea.

import clingo


def match(atom, name, arity):
    return atom.name == name and len(atom.arguments) == arity


class Codemaker:
    def __init__(self):
        self.__solution = {1:2, 2:4, 3:4, 4:1}

    def feedback(self, candidate):
        # I simplify here considering only correct pegs
        correct = 0
        for peg, color in self.__solution.items():
            if candidate[peg] == color:
                correct+= 1
        return correct


class Codebreaker:
    def __init__(self):
        self.__n = 0
        self.__ctl = clingo.Control()
        self.__ctl.load("mastermind.lp")
        self.__ctl.ground([("base", [])])
        self.__last_candidate = None
        self.__last_correct = 0


    def __on_model(self, m):
        self.__last_candidate = {}
        for atom in m.symbols(atoms=True):
            if match(atom, "candidate", 2):
                peg, color = atom.arguments
                self.__last_candidate[peg.number] = color.number

    def candidate(self, n):
        assert(n.number == self.__n)
        return self.__last_candidate.items()

    def correct(self, n):
        assert(n.number == self.__n)
        return self.__last_correct

    def add_feedback(self, correct):
        self.__last_correct = correct
        self.__ctl.ground([("feedback", [self.__n])], self)

    def solve(self):
        self.__n += 1
        ret = self.__ctl.solve(on_model=self.__on_model)
        assert (ret.satisfiable)
        return self.__last_candidate

cm = Codemaker()
cb = Codebreaker()

while True:
    candidate = cb.solve()
    print "guessed candidate: {}".format(candidate)
    correct = cm.feedback(candidate)
    if correct != 4:
        cb.add_feedback(correct)
    else:
        print "the last candidate is correct"
        break
peg(1..4).
color(1..6).

% guess solution candidates

1 { candidate(P,C) : color(C) } 1 :- peg(P).

#program feedback(n).

% store the n-th candidate
candidate(n,P,C) :- (P,C) = @candidate(n).

% do not guess the same candidate again
:- candidate(P,C) : candidate(n,P,C).

% make sure that the candidate solution does not differ in more than correct places given by the feedback
eq(n,P) :- candidate(P,C), candidate(n,P,C).
:- { eq(n,P) : peg(P) } < @correct(n).

from clingo.

0x326 avatar 0x326 commented on July 27, 2024

@rkaminsk It makes so much sense now! Thank you very much!

The 'redefinition of atom' error was the redefinition of black_position for the first guess. The GuessNumber of my example should be replaced with a subprogram parameter constant so that it only derives predicates for each guess one time.

For future people that may encounter this error, it is possible to rewrite error messages of the form redefinition of atom <'_', 20> to something else such that the underscore is replaced by the predicate name and the number following is named according to its meaning? For example, could a RuntimeError subclass be made with a __repr__ returning a value such as AtomRefinitionError(predicate='black_position',line=20)

from clingo.

rkaminsk avatar rkaminsk commented on July 27, 2024

Glad I could help. The reason you just got an _ is that you hid the predicate. Without the show directive it would have been a black_position(G,N) for some G and N. It would be possible to generate a better error message in the former case, too. The way clingo is build makes this a little difficult though.

We are already planning to add a section about multi-shot solving to our guide. For now, maybe the blocksworld example in section 5.3 help and there is clingo's examples folder.

from clingo.

0x326 avatar 0x326 commented on July 27, 2024

I think a section for multi-shot solving is a good idea! Thanks again.

from clingo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.