1 % (c) 2020-2022 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
2 % Heinrich Heine Universitaet Duesseldorf
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5 :- module(optimizing_solver,[optimizing_solve_predicate/5,
6 optimizing_solve_predicate/7, % with extra optimizing expression
7 maxsolve_predicate/4,
8 minsolve_predicate/4,
9 b_set_up_maximally_valid_state/2
10 ]).
11
12 :- use_module(probsrc(module_information),[module_info/2]).
13 :- module_info(group,cbc).
14 :- module_info(description,'This module can be used to find optimal (maximal/minimal) solutions to a predicate.').
15
16
17 :- use_module(probsrc(b_interpreter),[b_test_boolean_expression/4, b_compute_expression/5,set_up_typed_localstate/6]).
18 :- use_module(probsrc(bsyntaxtree), [get_texpr_type/2]).
19 :- use_module(probsrc(kernel_waitflags)).
20 :- use_module(probsrc(error_manager)).
21 :- use_module(probsrc(b_enumerate), [b_tighter_enumerate_all_values/2]).
22
23
24 % ------------------
25
26 :- use_module(probsrc(state_space),[get_constants_state_for_id/2]).
27 :- use_module(probsrc(store),[normalise_store/2]).
28 :- use_module(probsrc(bmachine),[b_get_machine_constants/1,
29 b_get_machine_variables/1, b_get_invariant_from_machine/1]).
30
31 % find a maximal model to the Invariant based upon current constant valuation
32 b_set_up_maximally_valid_state(ID,ResultState) :-
33 (get_constants_state_for_id(ID,ConstantsState) -> true
34 ; b_get_machine_constants([]) -> ConstantsState=[]
35 ; add_error(optimizing_solver,'Illegal state, please set-up the constants first:',ID),
36 fail % TO DO: b_get_properties_from_machine
37 ),
38 % TO DO: treat case if ID=root and we have Constants !
39 b_get_machine_variables(Variables),
40 b_get_invariant_from_machine(Invariant),
41 % TO DO: use current variable values as first solution
42 maxsolve_predicate(ConstantsState,Variables,Invariant,LocalState),
43 normalise_store(LocalState,NormalisedState),
44 append(ConstantsState,NormalisedState,ResultState).
45
46 % ------------------
47
48 % find a maximal model for TypedPred with free variables Parameters in given expanded state EState
49 % return solution found for Parameters in LocalState
50 maxsolve_predicate(EState, Parameters,TypedPred,LocalState) :-
51 optimizing_solve_predicate(maximizing,EState, Parameters,TypedPred,LocalState).
52 % find a minimal model
53 minsolve_predicate(EState, Parameters,TypedPred,LocalState) :-
54 optimizing_solve_predicate(minimizing,EState, Parameters,TypedPred,LocalState).
55
56 :- use_module(probsrc(bsyntaxtree), [create_couple/2]).
57 % similar to test_bool_exists in eval_strings, but finds optimal solution:
58 optimizing_solve_predicate(OptMode,EState, Parameters,TypedPred,LocalState) :-
59 create_couple(Parameters,OptimizingExpr),
60 optimizing_solve_predicate(OptMode,EState, Parameters,TypedPred,OptimizingExpr,_,LocalState).
61
62 optimizing_solve_predicate(OptMode,EState, Parameters,TypedPred,OptimizingExpr,OptValue,LocalState) :-
63 %init_wait_flags(WF,[expansion_context(optimize_pred,Parameters)]),
64 init_quantifier_wait_flag(no_wf_available,optimize,Parameters,SolTuple,unknown,WF),
65 set_up_typed_localstate(Parameters,SolTuple,TypedVals,[],LocalState,positive),
66 b_tighter_enumerate_all_values(TypedVals,WF), % TODO: check if we can use b_tighter_enumerate_values_in_ctxt
67 b_test_boolean_expression(TypedPred,LocalState,EState,WF), % test predicate to satisfy
68 b_compute_expression(OptimizingExpr,LocalState,EState,OptValue,WF), % compute value to optimise
69 get_texpr_type(OptimizingExpr,OptType), % type of value to optimise
70 format('Starting ~w solver (~w)~n',[OptMode,LocalState]),
71 format('Optimizing: ',[]),translate:print_bexpr(OptimizingExpr),nl,
72 safe_ground_wait_flag0(WF), % avoid doing deterministic computations every time in ground_for_optimal_sol
73
74 % TO DO: we could select a subset of Parameters and SolTuple to optimize
75
76 catch(
77 ground_for_optimal_sol(1,OptMode,OptType,OptValue, WF,LocalState),
78 user_interrupt_signal,
79 (add_warning(optimizing_solver,'Search for optimal solution interrupted by user (CTRL-C): ',OptMode,Parameters),
80 fail)
81 ).
82 optimizing_solve_predicate(_, _,_,_,_,OptValue,LocalState) :-
83 get_solution(OptValue,LocalState).
84
85
86
87 % TO DO: check for enumeration warnings, see e.g. gol_v2
88 % TO DO: add version with time-out and maximal number of improvements?
89 % TO DO: allow to pass initial solution (which could be solved using partitioning into components)
90 % TO DO: try to improve by component ? or we simply assume partitioning is done beforehand.
91
92 :- dynamic non_det_flag/0.
93 safe_ground_wait_flag0(WF) :- retractall(non_det_flag),
94 ground_wait_flag0(WF),
95 (non_det_flag -> add_warning(optimizing_solver,'Non-determinsm during WF0 grounding: ',WF)
96 ; assertz(non_det_flag)).
97
98 :- dynamic best_sol_found/2.
99 % we store the best solution found
100 % we avoid setting up the constraints completely fresh, we just backtrack to before
101 % grounding waitflags and add the constraint that we should improve
102 ground_for_optimal_sol(Count,OptMode,OptType,OptValue,WF,LocalState) :-
103 ((ground_wait_flags(WF)
104 -> format('Found solution at step ~w: ~w~n',[Count,OptValue]),
105 retractall(best_sol_found(_,_)),
106 asserta(best_sol_found(OptValue,LocalState)),
107 fail % backtrack and undo ground_wait_flags above
108 % TO DO: we could experiment with also backtracking beyond last best_sol_found predicate
109 ; true % no more optimization possible
110 )
111 -> fail
112 ; C1 is Count+1,
113 %print(improve_upon(BestSolTuple)),nl,
114 ground_for_optimal_sol_with_reset(C1,OptMode,OptType,OptValue,WF,LocalState)
115 ).
116
117 :-if(1=2).
118 % a version which accumulates the improve constraints and does not remove them
119 ground_for_optimal_sol_with_reset(C1,OptMode,OptType,OptValue,WF,LocalState) :-
120 add_improve_constraint(OptMode,OptType,OptValue,WF),
121 ground_for_optimal_sol(C1,OptMode,OptType,OptValue,WF,LocalState).
122 :- else.
123 % a version which removes the previous improvement constraint and adds a new one
124 % requires transitivity of the improve constraints
125 ground_for_optimal_sol_with_reset(Count,OptMode,OptType,OptValue,WF,LocalState) :-
126 (( add_improve_constraint(OptMode,OptType,OptValue,WF),
127 ground_wait_flags(WF)
128 -> format('Found improved solution at step ~w: ~w~n',[Count,OptValue]),
129 retractall(best_sol_found(_,_)),
130 asserta(best_sol_found(OptValue,LocalState)),
131 fail % backtrack and undo ground_wait_flags and add_improve_constraint above
132 ; true % no more optimization possible
133 )
134 -> fail
135 ; C1 is Count+1,
136 ground_for_optimal_sol_with_reset(C1,OptMode,OptType,OptValue,WF,LocalState)
137 ).
138 :-endif.
139
140 :- use_module(probsrc(kernel_objects),[not_equal_object_wf/3]).
141 add_improve_constraint(OptMode,OptType,OptValue,WF) :-
142 best_sol_found(BestSolSoFar,_),!,
143 copy_wf_start(WF,improve_upon,CWF), % unset WF0 in CWF in case it is set in WF
144 not_equal_object_wf(OptValue,BestSolSoFar,CWF), % avoid finding same solution again
145 (OptMode = maximizing
146 -> better_or_equal(OptType,OptValue,BestSolSoFar,CWF) % find at least as good a solution
147 ; better_or_equal(OptType,BestSolSoFar,OptValue,CWF)
148 ),
149 copy_wf_finish(WF,CWF).
150
151 get_solution(OptVal,LocalState) :-
152 format('No more optimizing possible~n',[]),
153 retract(best_sol_found(OptVal,LocalState)).
154 %format('Solution=~w~n',[LocalState]).
155
156
157
158
159
160 % TO DO: provide possibility to provide custom optimization function, treat total functions, ... differently
161 % code related to LEQ_SYM_BREAK, LEQ_SYM
162
163 :- use_module(probsrc(kernel_objects),[greater_than_equal/2, less_than_equal_direct/2, check_subset_of_wf/3]).
164 better_or_equal(integer,NewVal,BestVal,_WF) :- greater_than_equal(NewVal,BestVal). % TODO: use absolute value?
165 better_or_equal(boolean,B1,B2,_WF) :- !, bool_geq(B1,B2).
166 better_or_equal(boolean,_,pred_false,_WF).
167 better_or_equal(couple(T1,T2),(N1,N2),(B1,B2),WF) :- better_or_equal(T1,N1,B1,WF), better_or_equal(T2,N2,B2,WF).
168 better_or_equal(set(_),NewVal,BestVal,WF) :- check_subset_of_wf(BestVal,NewVal,WF).
169 better_or_equal(seq(_),NewVal,BestVal,WF) :- check_subset_of_wf(BestVal,NewVal,WF).
170 better_or_equal(string,NewVal,BestVal,_WF) :- NewVal=BestVal. % possibly use length, prefix or @< order ?
171 better_or_equal(global(G),fd(V1,G),fd(V2,G),_WF) :- less_than_equal_direct(V2,V1). % use fd(_,_) numbering
172 better_or_equal(record(Fields),rec(F1),rec(F2),WF) :- better_or_equal_fields(Fields,F1,F2,WF).
173 better_or_equal(freetype(_),V,V,_). % TO DO: add support for freetypes
174
175
176
177 % one of the arguments is instantiated at least; otherwise we would need block declaration
178 bool_geq(B1,B2) :- var(B1),!, (B2=pred_true -> B1=pred_true ; true).
179 bool_geq(pred_true,_).
180 bool_geq(pred_false,pred_false).
181
182 better_or_equal_fields([],[],[],_).
183 better_or_equal_fields([field(Name,Type)|TT],[field(Name,V1)|T1],[field(Name,V2)|T2],WF) :-
184 better_or_equal(Type,V1,V2,WF),
185 better_or_equal_fields(TT,T1,T2,WF).
186
187 % examples for REPL:
188 % :max x:BOOL & y:BOOL & z:BOOL & (x/=y or y/=z)
189 % :max x <: 1..10 & y <: 1..10 & x /\ y = {} & 5 : x \/ y & card(x)=card(y)
190 % :max x:BOOL & y:BOOL & z:BOOL & (x=TRUE => y=TRUE) & (y=TRUE => z=TRUE)
191 % :max n=8 & n1=n-1 & q<:(1..n1)*(1..n1) & !(x,y).(x|->y:q => x/=y)
192