| 1 | | % (c) 20019-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(b_enumeration_order_analysis, [find_do_not_enumerate_variables/4]). |
| 6 | | |
| 7 | | :- use_module(module_information,[module_info/2]). |
| 8 | | :- module_info(group,typechecker). |
| 9 | | :- module_info(description,'This module computes DO_NOT_ENUMERATE variables.'). |
| 10 | | |
| 11 | | :- use_module(error_manager). |
| 12 | | :- use_module(debug). |
| 13 | | :- use_module(self_check). |
| 14 | | :- use_module(bsyntaxtree). |
| 15 | | |
| 16 | | :- use_module(library(lists)). |
| 17 | | :- use_module(library(ordsets)). |
| 18 | | |
| 19 | | :- use_module(library(ugraphs),[vertices_edges_to_ugraph/3 %, top_sort/2 |
| 20 | | ]). |
| 21 | | |
| 22 | | % disable this currently for SWI, as top_sort below calls internal predicate of ugraphs: fanin_counts |
| 23 | | :- if(current_prolog_flag(dialect, swi)). |
| 24 | | :- write('ENUMERATION ORDER ANALYISIS disabled for SWI Prolog'),nl. |
| 25 | | find_do_not_enumerate_variables(_,_,[],[]). |
| 26 | | :- endif. |
| 27 | | |
| 28 | | % try and detect variables which do not have to be enumerated: |
| 29 | | % Example: {x,y,z|z:1..10 & y=z*z & #v.(v:{0,1} & x=y+v)} |
| 30 | | % Here we want to annotate x and y as DO_NOT_ENUMERATE: y can be computed from z, x can be computed from y and v |
| 31 | | % Example 2: f<:d & a: 1..100 & b=a+1 & c<:0..b & d<:c & res=b+b |
| 32 | | % Here a has to be enumerated, b,res do not have to be enumerated, and c,d,f can be delayed in order |
| 33 | | % VariablesToAnnotate: variables which can be annotated with DO_NOT_ENUMERATE; they should need no enumeration at all |
| 34 | | % VariablesToDelay: variables whose enumeration can be delayed; they are not used to constrain solutions,... |
| 35 | | find_do_not_enumerate_variables(TIds,Body,VariablesToAnnotate,VariablesToDelayInOrderOfDelaying) :- |
| 36 | | TIds = [_,_|_], % we have at least two identifiers to enumerate |
| 37 | | get_texpr_ids(TIds,Ids),sort(Ids,SIds), |
| 38 | | % format('Detecting lambda_result / DO_NOT_ENUMERATE ~w : ',[SIds]),translate:print_bexpr(b(comprehension_set(TIds,Body),any,[])),nl, |
| 39 | | recursive_conjunction_to_list(Body,SIds,LBody), |
| 40 | | %translate:l_print_bexpr_or_subst(LBody),nl, |
| 41 | | % first find equalities of the form ID = Expr(UsedIds) with ID not in UsedIds |
| 42 | | find_lambda_equalities(LBody,SIds,[],Equalities,Rest), |
| 43 | | nontrivial(Equalities), |
| 44 | | % find the identifiers used in the the rest predicate |
| 45 | | % any variable used in the rest cannot be marked as DO_NOT_ENUMERATE |
| 46 | | find_identifier_uses_l(Rest,[],SortedRestIds), |
| 47 | | %format('Detecting lambda result over ~w with rest ids = ~w~n',[SIds,SortedRestIds]), |
| 48 | | remove_illegal_equalities(Equalities,SortedRestIds,RemainingEqualities,ResultUsedIds), |
| 49 | | nontrivial(RemainingEqualities), |
| 50 | | findall(Vertice,member(propagator(_,Vertice,_,_),RemainingEqualities),Vs), |
| 51 | | sort(Vs,SortedVs), |
| 52 | | findall(Vertice,member(propagator(in_equality,Vertice,_,_),RemainingEqualities),NEqVs), |
| 53 | | sort(NEqVs,SortedNEqVs), % variables which are not defined by equalities; i.e., may have more than one solution and still require enumeration |
| 54 | | debug_format(4,'Topological sorting of Vertices = ~w (non-det: ~w), rest ids=~w~n', |
| 55 | | [SortedVs,SortedNEqVs,ResultUsedIds]), |
| 56 | | findall(V-W,(member(V,SortedVs), |
| 57 | | member(propagator(_,W,UsedIds,_),RemainingEqualities), |
| 58 | | member(V,UsedIds)), Edges), |
| 59 | | %format('Edges = ~w~n',[Edges]), |
| 60 | | vertices_edges_to_ugraph(SortedVs,Edges,Graph), |
| 61 | | topological_sorting(Graph,OrderedVertices), |
| 62 | | OrderedVertices \= ['_lambda_result_'], |
| 63 | | !, |
| 64 | | sort(OrderedVertices,SOV), % vertices were sorted according to enumeration order |
| 65 | | ord_intersection(SOV,SIds,SOV2), % retain only those ids which are freshly quantified for annotation |
| 66 | | ord_intersection(SOV2,SortedNEqVs,VariablesToDelay), % Variables which should be delayed; but which still have multiple solutions |
| 67 | | include(is_ord_mem(VariablesToDelay),OrderedVertices,VariablesToDelayInOrderOfDelaying), |
| 68 | | ord_subtract(SOV2,VariablesToDelay,VariablesToAnnotate). % Variables which need no enumeration at all |
| 69 | | find_do_not_enumerate_variables(_,_,[],[]). % return empty list of variables to annotate |
| 70 | | |
| 71 | | is_ord_mem(SortedIds,Id) :- ord_member(Id,SortedIds). |
| 72 | | |
| 73 | | nontrivial(Equalities) :- |
| 74 | | Equalities \= [], |
| 75 | | Equalities \= [propagator(equality,'_lambda_result_',_,_)]. |
| 76 | | %(member(propagator(equality,_,_,_),Equalities) -> true). % currently we only perform DO_NOT_ENUMERATE analysis |
| 77 | | |
| 78 | | :- use_module(tools_printing,[format_with_colour_nl/4]). |
| 79 | | topological_sorting(Graph,OrderedVertices) :- |
| 80 | | top_sort(Graph,OrderedVertices,UnsortedNr), |
| 81 | | % allow cycles but do only return the vertices that can be topologically sorted |
| 82 | | % {x,y,z|z:1..10 & x=z+1 & y=x+1 & x=y-1} -> cycle over x and y |
| 83 | | % {x,y,r,z|z:1..10 & x=z+1 & y=x+1 & x=y-1 & r=z*z} -> cycle over x and y, but r can be annotated |
| 84 | | % see also test 1824 |
| 85 | | !, |
| 86 | | debug_format(4,'Topological Sorting successful = ~w (unsorted due to cycles: ~w)~n',[OrderedVertices,UnsortedNr]). |
| 87 | | topological_sorting(Graph,_) :- |
| 88 | | format_with_colour_nl(user_output,[red,bold],'Topological sorting failed for ~w~n',[Graph]), |
| 89 | | fail. |
| 90 | | |
| 91 | | % detect assignments of the Form ID = Expr, where ID does not occur in Expr and occurs in SIds |
| 92 | | is_lambda_equality(_SIds,Equality,ID,UsedIDs) :- |
| 93 | ? | identifier_equality(Equality,ID,TExpr), |
| 94 | | %ID \= '_lambda_result_', % include as equality, IDs in TExpr should not count |
| 95 | | %ord_member(ID,SIds), % if we find lambda equalities for other identifiers not in SIds it is useful to consider them too |
| 96 | | % Example: {x,y,z|z:1..10 & #(w,v).(x=z+z &w:1..9 & v=card({x,x+x,w}) & y=v+v )} |
| 97 | | % When analyzing #(w,v) it is also useful to consider the equalities over x and y in the process; otherwise we are not able to detect that v should not be enumerated |
| 98 | | % example is also identifier NV in operation caval__rule__1__compute in private_examples/ClearSy/2019_Nov/rule_perf4 |
| 99 | | find_identifier_uses(TExpr,[],UsedIDs), |
| 100 | | \+ ord_member(ID,UsedIDs). |
| 101 | | |
| 102 | | :- use_module(bsyntaxtree,[is_equality/3]). |
| 103 | | identifier_equality(b(not_equal(LHS,RHS),pred,_),Id,EqTerm) :- !, |
| 104 | | % detect boolean inequalities x/=y : ProB will deterministically set the value of x (TRUE/FALSE) when y is known |
| 105 | | get_texpr_type(LHS,boolean), % type has exactly two values |
| 106 | | ( get_texpr_id(LHS,Id), EqTerm = RHS |
| 107 | | ; get_texpr_id(RHS,Id), EqTerm = LHS). |
| 108 | | identifier_equality(TExpr,Id,EqTerm) :- is_equality(TExpr,LHS,RHS), |
| 109 | | ( get_texpr_id(LHS,Id), EqTerm = RHS |
| 110 | | ; get_texpr_id(RHS,Id), EqTerm = LHS). |
| 111 | | |
| 112 | | |
| 113 | | :- use_module(bsyntaxtree, [get_negated_operator_expr/2]). |
| 114 | | |
| 115 | | % TO DO: also detect other predicates where there is always a solution |
| 116 | | % Note: will require adapting sort_typed_values |
| 117 | | % example tf : domain --> ROUTES & tf <: rtbl & OCC1 <: domain & tf[OCC1]<: frm1 & A : OCC1 & A|->R5 /: tf |
| 118 | | % for public_examples/EventBPrologPackages/Abrial_Train_Ch17/Train_1_beebook_tlc.mch |
| 119 | | always_satisfiable(b(Pred,pred,_),ID,UsedIDs) :- |
| 120 | ? | always_satisfiable_aux(Pred,TID,TExprs), |
| 121 | | get_texpr_id(TID,ID), |
| 122 | | find_identifier_uses_l(TExprs,[],UsedIDs), |
| 123 | | \+ ord_member(ID,UsedIDs). |
| 124 | | always_satisfiable_aux(subset(E,TExpr),TID,[TExpr|Other]) :- |
| 125 | ? | allows_empty_sol(E,TID,Other). % Solution always E={} |
| 126 | | always_satisfiable_aux(subset(TExpr,TID),TID,[TExpr]). % Solution TID=TExpr |
| 127 | | always_satisfiable_aux(not_subset(TExpr,E),TID,[TExpr|Other]) :- |
| 128 | ? | allows_empty_sol(E,TID,Other). % Solution always E={} |
| 129 | | always_satisfiable_aux(not_member(TExpr,E),TID,[TExpr|Other]) :- |
| 130 | ? | allows_empty_sol(E,TID,Other). % Solution always E={} |
| 131 | | always_satisfiable_aux(member(TExpr,TID),TID,[TExpr]). % Solution TID={TExpr} |
| 132 | | always_satisfiable_aux(less(TID,TExpr),TID,[TExpr]). |
| 133 | | always_satisfiable_aux(less(TExpr,TID),TID,[TExpr]). |
| 134 | | always_satisfiable_aux(greater(TID,TExpr),TID,[TExpr]). |
| 135 | | always_satisfiable_aux(greater(TExpr,TID),TID,[TExpr]). |
| 136 | | always_satisfiable_aux(less_equal(TID,TExpr),TID,[TExpr]). |
| 137 | | always_satisfiable_aux(less_equal(TExpr,TID),TID,[TExpr]). |
| 138 | | always_satisfiable_aux(greater_equal(TID,TExpr),TID,[TExpr]). |
| 139 | | always_satisfiable_aux(greater_equal(TExpr,TID),TID,[TExpr]). |
| 140 | | always_satisfiable_aux(not_equal(TID,TExpr),TID,[TExpr]) :- |
| 141 | | expr_type_allows_at_least_two_values(TID). % then we not_equal can be satisfied |
| 142 | | always_satisfiable_aux(not_equal(TExpr,TID),TID,[TExpr]) :- expr_type_allows_at_least_two_values(TID). |
| 143 | | always_satisfiable_aux(negation(TExpr),TID,Other) :- get_negated_operator_expr(TExpr,NegExpr), |
| 144 | ? | always_satisfiable_aux(NegExpr,TID,Other). |
| 145 | | % TODO: other constraints which can always be satisfied |
| 146 | | % less_equal_real, less_real, subset_strict, not_subset_strict |
| 147 | | |
| 148 | | allows_empty_sol(TID,TID,[]). % Solution: TID={} |
| 149 | ? | allows_empty_sol(b(E,_,_),TID,Other) :- allows_empty_sol_aux(E,TID,Other). |
| 150 | | allows_empty_sol_aux(image(TID,Other),TID,[Other]). % Solution: TID[Other]={} --> TID={} |
| 151 | | allows_empty_sol_aux(image(Other,TID),TID,[Other]). % Solution: Other[TID]={} --> TID={} |
| 152 | | allows_empty_sol_aux(intersection(TID,Other),TID,[Other]). % Solution: TID={} |
| 153 | | allows_empty_sol_aux(intersection(Other,TID),TID,[Other]). % Solution: TID={} |
| 154 | | allows_empty_sol_aux(set_subtraction(TID,Other),TID,[Other]). % Solution: TID={} |
| 155 | | allows_empty_sol_aux(set_subtraction(Other,TID),TID,[Other]). % Solution: TID=Other |
| 156 | | |
| 157 | | :- use_module(typing_tools,[type_has_at_least_two_elements/1]). |
| 158 | | expr_type_allows_at_least_two_values(b(_,Type,_)) :- type_has_at_least_two_elements(Type). |
| 159 | | %expr_type_allows_at_least_one_value(b(_,Type,_)) :- non_empty_type(Type). |
| 160 | | |
| 161 | | % separate predicates into lambda equality candidates ID = Expr and other predicates |
| 162 | | % find_lambda_equalities(ListOfPreds,QuantifiedIDs,LambdaEqualities,ListOfRest) |
| 163 | | find_lambda_equalities([],_SIds,_,[],[]). |
| 164 | | find_lambda_equalities([TPred|Rest],SIds,EqIds,A,B) :- |
| 165 | ? | (is_lambda_equality(SIds,TPred,ID,UsedIDs), |
| 166 | | ord_nonmember(ID,EqIds) |
| 167 | | -> A=[propagator(equality,ID,UsedIDs,TPred)|AR], B=BR, |
| 168 | | ord_add_element(EqIds,ID,EqIds2) |
| 169 | | %, format('Equality ~w = Expr(~w)~n',[ID,UsedIDs]) |
| 170 | | ; pred_can_be_ignored(TPred) |
| 171 | | -> A=AR, B=BR, EqIds2=EqIds |
| 172 | ? | ; always_satisfiable(TPred,ID,UsedIDs), % only add if no other constraint already added! |
| 173 | | ord_nonmember(ID,EqIds) % no guaranteed that together with other constraints it is still always satisfiable |
| 174 | | -> A=[propagator(in_equality,ID,UsedIDs,TPred)|AR], B=BR, |
| 175 | | ord_add_element(EqIds,ID,EqIds2) |
| 176 | | %, format('InEquality ~w = Expr(~w) :: ~w~n',[ID,UsedIDs,EqIds]) |
| 177 | | ; A=AR, B=[TPred|BR], EqIds2=EqIds), |
| 178 | | find_lambda_equalities(Rest,SIds,EqIds2,AR,BR). |
| 179 | | |
| 180 | | % predicate can be safely ignored for enumeration order analysis: |
| 181 | | pred_can_be_ignored(b(E,_,_)) :- pred_can_be_ignored_aux(E). |
| 182 | | pred_can_be_ignored_aux(external_pred_call(EP,_)) :- ext_pred_can_be_ignored(EP). |
| 183 | | pred_can_be_ignored_aux(external_function_call(EP,_)) :- ext_pred_can_be_ignored(EP). |
| 184 | | pred_can_be_ignored_aux(equal(A,B)) :- |
| 185 | | ( bool_true(B) -> pred_can_be_ignored(A) % also deal with observe(a)=TRUE, e.g., in REPL |
| 186 | | ; bool_true(A) -> pred_can_be_ignored(B)). |
| 187 | | ext_pred_can_be_ignored('DO_NOT_ENUMERATE'). |
| 188 | | ext_pred_can_be_ignored('ENUMERATE'). |
| 189 | | ext_pred_can_be_ignored('FORCE'). |
| 190 | | ext_pred_can_be_ignored('printf'). % TO DO: debugging_only fact in external_functions |
| 191 | | ext_pred_can_be_ignored('fprintf'). |
| 192 | | ext_pred_can_be_ignored('printf1'). |
| 193 | | ext_pred_can_be_ignored('printf_opt_trace'). |
| 194 | | ext_pred_can_be_ignored('iprintf'). |
| 195 | | ext_pred_can_be_ignored('printf_two'). |
| 196 | | ext_pred_can_be_ignored('tprintf'). |
| 197 | | ext_pred_can_be_ignored('printf_nonvar'). |
| 198 | | ext_pred_can_be_ignored('observe'). |
| 199 | | ext_pred_can_be_ignored('observe_silent'). |
| 200 | | ext_pred_can_be_ignored('observe_indent'). |
| 201 | | ext_pred_can_be_ignored('tobserve'). |
| 202 | | ext_pred_can_be_ignored('observe_fun'). |
| 203 | | |
| 204 | | bool_true(b(boolean_true,boolean,_)). |
| 205 | | |
| 206 | | remove_illegal_equalities(Equalities,RestUsedIds,RemainingEqualities,ResultUsedIds) :- |
| 207 | ? | select(propagator(Style,ID,UsedIDs,_),Equalities,RestEqualities), |
| 208 | | ord_member(ID,RestUsedIds), |
| 209 | | debug_format(5,'Removing ~w over ~w, id used in rest ~w~n',[Style,ID,RestUsedIds]), |
| 210 | | !, |
| 211 | | ord_add_element(UsedIDs,ID,UsedIDs2), |
| 212 | | ord_union(RestUsedIds,UsedIDs2,NewRestUsedIds), |
| 213 | | remove_illegal_equalities(RestEqualities,NewRestUsedIds,RemainingEqualities,ResultUsedIds). |
| 214 | | remove_illegal_equalities(Eq,Used,Eq,Used). |
| 215 | | |
| 216 | | :- use_module(bsyntaxtree,[is_a_conjunct_or_neg_disj/3]). |
| 217 | | |
| 218 | | % a version of conjunction to list which lifts existential quantifier bodies to the top-level |
| 219 | | recursive_conjunction_to_list(C,BoundIds,L) :- var(C),!, |
| 220 | | add_error_fail(conjunction_to_list,'Internal error: var :',conjunction_to_list(C,BoundIds,L)). |
| 221 | | recursive_conjunction_to_list(C,BoundIds,List) :- |
| 222 | | rec_conjunction_to_list2(C,BoundIds,List,[]). |
| 223 | | rec_conjunction_to_list2(X,BoundIds,I,O) :- |
| 224 | | is_a_conjunct_or_neg_disj(X,LHS,RHS),!, |
| 225 | | rec_conjunction_to_list2(LHS,BoundIds,I,Inter), |
| 226 | | rec_conjunction_to_list2(RHS,BoundIds,Inter,O). |
| 227 | | rec_conjunction_to_list2(X,BoundIds,I,O) :- X=b(E,_,_), |
| 228 | | ( E = truth -> I=O |
| 229 | | ; E = exists(Ids,Body), |
| 230 | | check_clash(Ids,BoundIds,NewBoundIds) |
| 231 | | -> rec_conjunction_to_list2(Body,NewBoundIds,I,O) |
| 232 | | ; E = let_predicate(Ids,Exps,Body), |
| 233 | | check_clash(Ids,BoundIds,NewBoundIds) -> % ditto |
| 234 | | I = [b(let_predicate(Ids,Exps,b(truth,pred,[])),pred,[]) | Inter], |
| 235 | | rec_conjunction_to_list2(Body,NewBoundIds,Inter,O) |
| 236 | | ; I = [X|O]). |
| 237 | | |
| 238 | | |
| 239 | | :- use_module(performance_messages,[perf_format/2]). |
| 240 | | % check whether the new identifiers clash |
| 241 | | % Without this check we can get errors: See test 983, 1380 prop_COMPUTE_5, esatrack (detected by check_typed_ids in bsyntaxtree.pl) |
| 242 | | % TO DO: we could rename the identifiers for the analysis; but complex and is it worth it? |
| 243 | | check_clash(TIds,BoundIds,NewBoundIds) :- def_get_texpr_ids(TIds,Ids), |
| 244 | | sort(Ids,SIds), |
| 245 | | (ord_intersection(SIds,BoundIds,Clash), Clash \= [] |
| 246 | | -> perf_format('Not expanding quantifier for enumeration analysis due to clash with ~w~n',[Clash]), |
| 247 | | fail |
| 248 | | ; ord_union(SIds,BoundIds,NewBoundIds)). % we could use ord_disjoint_union |
| 249 | | |
| 250 | | % tests were detection works: 790, 799, 982, 1302, 1303, 1380, 1394, 1492, 1751 |
| 251 | | % 1751 slightly slower |
| 252 | | % 1307 639 |
| 253 | | % 1935, 1936, 1946, 1950, 1981, 1983 |
| 254 | | |
| 255 | | % TO DO: |
| 256 | | % can the analysis be counter-productive?? |
| 257 | | % what about {x | #v.(v:1..1000000 & x = bool(v=0))} in CLPFD FALSE mode |
| 258 | | % here {x | (x=TRUE or x=FALSE) & #v.(v:1..1000000 & x = bool(v=0))} is faster 2 vs 3.5 seconds |
| 259 | | % |
| 260 | | % more refined analysis, to detect issue in perf_3264/rule_186.mch |
| 261 | | % block:dom(ic___OPS_SDS_3264_OTHER_DP_IN_BLOCK |> {FALSE}) is enumerated before |
| 262 | | % signal : ran(SORT(dom(ic___OPS_SDS_3264_BBS))/|\186) |
| 263 | | % bad because from signal we can derive block, from the predicate |
| 264 | | % block : ic___OPS_SDS_3264_BBS(signal) |
| 265 | | |
| 266 | | % look at do_not_enumerate_binary_boolean_operator in kernel_propagation |
| 267 | | |
| 268 | | |
| 269 | | :- use_module(library(avl)). |
| 270 | | |
| 271 | | % a slight variation of top_sort from library(ugraphs) which allows the sorting to be not completed |
| 272 | | % top_sort(G,S,0) corresponds to top_sort in ugraphs |
| 273 | | top_sort(Graph, Sorted,UnsortedNr) :- |
| 274 | | ugraphs:fanin_counts(Graph, Counts), |
| 275 | | ugraphs:get_top_elements(Counts, Top, 0, I), |
| 276 | | ord_list_to_avl(Counts, Map), |
| 277 | | top_sort_aux(Top, I, Map, Sorted,UnsortedNr). |
| 278 | | |
| 279 | | top_sort_aux([], I, _, [], UnsortedNr) :- UnsortedNr = I. |
| 280 | | top_sort_aux([V-VN|Top0], I, Map0, [V|Sorted],UnsortedNr) :- |
| 281 | | ugraphs:dec_counts(VN, I, J, Map0, Map, Top0, Top), |
| 282 | | top_sort_aux(Top, J, Map, Sorted,UnsortedNr). |
| 283 | | |