%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- use_module(library(clpfd)).
:- use_module(library(timeout)).
:- use_module(library(random)).
:- use_module(library(lists)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% PREDICATO PRINCIPALE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

minimizza(Soluzioni,Tempo,Tecnica):-
     vincola(Vars,Costo),
     retractall(initialtime(_)),
     statistics(runtime,[T,_]),
     assert(initialtime(T)), 
     ricerca(Vars,Costo,Soluzioni,Tempo,Tecnica).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% ESEMPIO DI PROBLEMA VINCOLATO con COSTO %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

vincola(Vars,Costo) :-
     Vars=[A,B,C,D,E,F],
     domain(Vars,-100,100),
     A-2*B+3*C-D*D+4*E-5*F #> 20,
     Costo #= 2*A - 3*B + 4*C - D - E + F. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Tre opzioni di ricerca: builtin, vincolata da noi,
%%% local search
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%% In b_in NON CI INTERESSANO il tempo massimo ed il 
%%% numero di soluzioni trovate.

ricerca(Vars,Costo,_,_, b_in ):-
     labeling([minimize(Costo)],Vars),!,
     %%% output
     write('Ottimo: '),write(Costo),nl,
     write('Tempo totale di computazione: '),  
     statistics(runtime,[_,F]),write(F),write('ms'),nl,
     write('Altre info: *******************************'),nl,  
     fd_statistics.
     
ricerca(Vars,Costo,Sol,Tempo, b_up):-
    (var(Sol),!,Sol=1;true),        %%% default number of solutions = 1
    (var(Tempo),!,Tempo=1000;true), %%% default time 1 second
     retractall(contate(_)),
     retractall(best(_,_)),
     assert(contate(0)),
     assert(best(1000000,_)),
     time_out(bottomup(Sol,Vars,Costo),Tempo,Out),
    (Out = time_out, write('Warning: terminato per timeout'),nl;
     Out = success),
     write('*********************************************************'),nl,
     fail.

%%% In loc NON CI INTERESSA il 
%%% numero di soluzioni trovate.

ricerca(Vars,Costo,_Sol,Tempo, loc) :-
    (var(Tempo),!,Tempo=1000;true), %%% default time 1 second
     retractall(contate(_)),  assert(contate(0)),
     retractall(best(_,_)),   assert(best(1000000,Faked)),
     length(Vars,N),
     length(Faked,N), 
     time_out(local(Vars,Costo),Tempo,_),
     write('*********************************************************'),nl,
     fail.

ricerca(_,_,_,_,_) :-
     best(1000000,_),!,
     write('Tempo insufficiente per la prima soluzione'),nl.

ricerca(_,_,_,_,_) :-
     retract(best(Val,_Sol)),
     write('Ottimo Trovato: '),write(Val),nl,
     write('Tempo totale di computazione: '),  
     statistics(runtime,[Tf,_]),
     initialtime(Ti),DT is Tf - Ti,write(DT),write('ms'),nl.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% BOTTOMUP (myconstraint)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

     
bottomup(0,_,_) :-
     write('Trovate tutte le soluzioni chieste'),nl.
bottomup(N,Vars,Costo) :-
     N > 0,
    (unasoluzione_bu(Vars,Costo), fail;
      N1 is N-1,
      bottomup(N1,Vars,Costo)).  
      
unasoluzione_bu(Vars,Costo) :-
     best(Lim,_),
     Costo #< Lim, 
     write('Sto cercando sol. con Costo < '), write(Lim),nl,
     labeling([ffc,down],Vars), !, %%% Il cut ferma la ricerca di
     retract(best(_,_)),           %%% altre sol con lo stesso limite
     retract(contate(N)),M is N + 1,
     assert(contate(M)),
     %%%% STAMPE
     write('Trovata soluzione #'), write(M), write(' di Costo = '),write(Costo),
     write(' ---  In tempo: '), 
     statistics(runtime,[Tf,_]),
     initialtime(Ti),DT is Tf - Ti,write(DT),write('ms'),nl,
     assert(best(Costo,Vars)).  

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% LOCAL SEARCH (local)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

   
local(Vars,Costo) :-
     \+ altra_sol(Vars,Costo),
     local(Vars,Costo).

altra_sol(Vars,Costo) :-
     best(Lim,BestSol),
     Costo #< Lim, 
     random_partial_assignment(Vars,BestSol),
     write('Sto cercando sol. con Costo < '), write(Lim),nl,
     %%% SE NON SIAMO NEL PRIMO CASO TROVIAMO L'OTTIMO LOCALE
     (ground(BestSol),labeling([ffc,down,minimize(Costo)],Vars);
      \+ ground(BestSol),labeling([ffc,down],Vars)),
     !,                            %%% Il cut ferma la ricerca di
     retract(best(_,_)),           %%% altre sol con lo stesso Lim
     retract(contate(N)),M is N + 1,
     assert(contate(M)),
     %%%% STAMPE
     write('Trovata soluzione #'), write(M), write(' di Costo = '),write(Costo),
     write(' ---  In tempo: '), 
     statistics(runtime,[Tf,_]),
     initialtime(Ti),DT is Tf - Ti,write(DT),write('ms'),nl,
     assert(best(Costo,Vars)),
     fail.  

%%%% Immagino di lasciare fissare ai valori precedenti
%%%   4 variabili alla volta (2 restano libere)

random_partial_assignment(Vars,BestSol) :-
     length(Vars,N),
     randseq(4,N,Random), %%% riordino random le N variabili
     member(A,Random),    %%% e seleziono le prime 4
     member(B,Random), A < B,
     member(C,Random), B < C,
     member(D,Random), C < D,
     nth(A,BestSol,VA), nth(A,Vars,VA),
     nth(B,BestSol,VB), nth(B,Vars,VB),
     nth(C,BestSol,VC), nth(C,Vars,VC),
     nth(D,BestSol,VD), nth(D,Vars,VD).
                         
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

