[m-rev.] a proof tree monitor

Erwan Jahier Erwan.Jahier at irisa.fr
Thu Jul 12 21:45:11 AEST 2001


I wrote that a long time ago too, and I think it is worth including it in the
cvs repository as a more involved monitor example you can define with collect. 

I am not 100 % confident that it is the best/simple way to compute a proof
tree from the trace, but at least it works. Maybe Mark would have some comments
about that code if he wishes.

--
Estimated hours taken: 20
branches: main.

Add a monitor that computes the program execution proof tree.

extras/morphine/source/collect__proof_tree:
	A new monitor that computes the program execution proof tree
	(New file).

extras/morphine/source/control_flow.op:
	Add support for calling the proof_tree monitor from Morphine.

Index: extras/morphine/source/collect__proof_tree
===================================================================
RCS file: collect__proof_tree
diff -N collect__proof_tree
--- /dev/null	Wed Nov 15 09:24:47 2000
+++ collect__proof_tree	Thu Jul 12 21:39:55 2001
@@ -0,0 +1,398 @@
+%------------------------------------------------------------------------------%
+% Copyright (C) 2001 INRIA/IFSIC.
+% This file may only be copied under the terms of the GNU Library General
+% Public License - see the file License in the Morphine distribution.
+% 
+% Author : Erwan Jahier <jahier at irisa.fr>
+%
+% Computes the proof tree.
+%
+% Note that it only works if the internal events have been generated in 
+% the trace. Also, the Morphine parameter collect_arg should be set to `yes'.
+%
+% Bug: not/1 is not handled (I should do something at `neg_*' events to fix that).
+%
+ 
+:- import_module list, string, require, map, stack, term, std_util.
+
+:- type accumulator_type --->  
+	ct(
+		   % We need that stack to be able to get the 
+		   % call number of the caller.
+		   %
+	   stack(call_number),
+
+		   % We need the goal path and 2 stacks to be able 
+		   % to get the goal path at internal events.
+		   %
+	   goal_path_string,
+	   stack(goal_path_string),
+	   stack(goal_path_string),
+
+		   % Each goal is indexed by its call number. We use
+		   % that call number to build a table that
+		   % contains the list of the goal direct successors
+		   % that succeed. Each call number is associated with
+		   % a goal path so that we know in which branch each
+		   % success node was produced; this is necessary to be
+		   % able to remove success nodes that do not
+		   % contribute to the final solution.
+		   %
+	   goal_successors_table, 
+
+		   % list of call numbers corresponding to all solution 
+		   % predicates. Indeed, such predicates need to be handled
+		   % differently because the goal they search the solutions of
+		   % ends up to fail although it has produced solutions.
+	   list(call_number),
+
+		   % Table of proof trees indexed by call numbers.
+	   proof_tree_table
+	   ).
+
+:- type collected_type ---> pb(string) ; ct(proof_tree).
+	
+
+:- type call_path == pair(call_number, goal_path_string).
+
+:- type goal_successors_table == map(call_number, list(call_path)).
+
+:- type predicate ---> p(proc_name, arity, list(univ)).
+
+:- type proof_tree ---> 
+	leaf(call_number, predicate)
+    ;   tree(call_number, predicate, list(proof_tree)).
+
+:- type proof_tree_table == map(call_number, proof_tree).
+
+%-----------------------------------------------------------------------%
+
+initialize(ct(CallStack, "", GpDetStack, GpNonDetStack, GoalSuccTable, [],
+    ProofTable)) :-
+	stack__init(CallStack0),
+	stack__push(CallStack0, 0, CallStack),
+	stack__init(GpDetStack),
+	stack__init(GpNonDetStack),
+	map__init(GoalSuccTable0),
+	map__det_insert(GoalSuccTable0, 0, [], GoalSuccTable),
+	map__init(ProofTable).
+
+filter(Event, Acc0, Acc, continue) :-
+	Acc0 = ct(CallStack0, GoalPath0, GpDetStack0, GpNonDetStack0, 
+	        GoalSuccT0, AllSolList0, ProofT0),
+	Port = port(Event),
+	CurrentCN = call(Event),
+	Arg = arguments(Event),
+	CurrentPred = p(proc_name(Event), proc_arity(Event), Arg),
+	update_goal_path(Event, GoalPath0, GoalPath, GpDetStack0, GpDetStack, 
+	        GpNonDetStack0, GpNonDetStack),
+	update_call_stack(Port, CurrentCN, CallStack0, CallStack),
+	( 
+		Port = call
+	->
+		map__det_insert(GoalSuccT0, CurrentCN, [], GoalSuccT),
+		( is_a_all_solutions_predicate(proc_name(Event)) ->
+		      AllSolList = [CurrentCN | AllSolList0]
+		;
+		      AllSolList = AllSolList0
+		),
+		ProofT = ProofT0
+	;
+		Port = exit 
+	->
+		stack__top_det(CallStack, FatherCN),
+		map__det_remove(GoalSuccT0, FatherCN, CNListF0, GoalSuccT1),
+		list__merge_and_remove_dups([CurrentCN - GoalPath], CNListF0,
+		        CNListF),
+		map__det_insert(GoalSuccT1, FatherCN, CNListF, GoalSuccT),
+		%
+		% Update the proof tree Table. To do that, we retrieve the
+		% list of successors of the current goal, we retrieve their
+		% proof trees, and then we build the proof tree of the 
+		% current goal.
+		%
+		map__lookup(GoalSuccT, CurrentCN, ListCP),
+		%
+		% we remove the goal path from that list of call_path
+		%
+		list__map(remove_goal_path, ListCP, ListCN),
+		( ListCN = [] ->
+			( map__update(ProofT0, CurrentCN, 
+				leaf(CurrentCN, CurrentPred), ProofT1) ->
+				ProofT = ProofT1
+			;
+			        map__det_insert(ProofT0, CurrentCN, 
+				        leaf(CurrentCN, CurrentPred), ProofT)
+		        ) 
+		;
+			list__map(map__lookup(ProofT0), ListCN, ListTrees),
+			( map__update(ProofT0, CurrentCN, 
+				tree(CurrentCN, CurrentPred, ListTrees), 
+				ProofT1) ->
+				ProofT = ProofT1
+			;
+			        map__det_insert(ProofT0, CurrentCN, 
+				    tree(CurrentCN, CurrentPred, ListTrees), 
+				    ProofT)
+			)
+		),
+		AllSolList = AllSolList0
+	;
+		Port = fail
+	->
+		stack__top_det(CallStack, FatherCN),
+		( member(FatherCN, AllSolList0) -> 
+		        % for all solutions predicates (such as std_util__solutions/2)
+		        % we must not remove the previous solutions of the goal.
+		        % Indeed, the all solutions predicates call the goal they
+		        % search all the solution of until it fails; therefore that 
+		        % would mean that we would always remove those solutions.
+		        GoalSuccT = GoalSuccT0,
+			ProofT = ProofT0
+		;
+		        map__lookup(GoalSuccT0, CurrentCN, SuccList),
+			remove_proof_trees(ProofT0, SuccList, ProofT),
+			map__lookup(GoalSuccT0, FatherCN, SuccListFather0),
+			list__filter(remove_current_cn(CurrentCN), SuccListFather0, 
+		                SuccListFather),
+			map__det_update(GoalSuccT0, FatherCN, SuccListFather, 
+		                GoalSuccT)
+		),
+		AllSolList = AllSolList0
+	;
+	        ( Port = disj ; Port = ite_else )
+	->
+	        %
+		% We need to remove the solutions of the previous (failing
+		% branch) disjunction, i.e., we remove form the successors
+		% tables all the branches from the father goal to the 
+		% goal of the previous disjunction. We also have to remove
+		% the proof trees of those goals.
+		%
+		% We also need to remove the solutions produced in the if 
+		% branch for the same reasons as above (actually, it is 
+		% natural since `if A then B else C' <=> `A, !, B ; C'.).
+		%
+		( Port = disj ->
+		        remove_sol_failing_branch(GoalPath, CurrentCN,
+		                GoalSuccT0, GoalSuccT, ProofT0, ProofT)
+		;
+		        remove_sol_if_branch(GoalPath, CurrentCN,
+		                GoalSuccT0, GoalSuccT, ProofT0, ProofT)
+		),
+		AllSolList = AllSolList0
+	;
+                % Bug XXX I probably should do something at ports  
+	        % neg_enter, neg_success, and neg_failure.
+	        %
+ 		GoalSuccT = GoalSuccT0,
+		ProofT = ProofT0,
+		AllSolList = AllSolList0
+	),
+	Acc = ct(CallStack, GoalPath, GpDetStack, GpNonDetStack, 
+	        GoalSuccT, AllSolList, ProofT).
+
+
+:- pred is_a_all_solutions_predicate(string::in) is semidet.
+is_a_all_solutions_predicate("solutions").
+is_a_all_solutions_predicate("std_util__solutions").
+is_a_all_solutions_predicate("solutions_set").
+is_a_all_solutions_predicate("std_util__solutions_set").
+is_a_all_solutions_predicate("unsorted_solutions").
+is_a_all_solutions_predicate("std_util__unsorted_solutions").
+is_a_all_solutions_predicate("aggregate").
+is_a_all_solutions_predicate("std_util__aggregate").
+is_a_all_solutions_predicate("aggregate2").
+is_a_all_solutions_predicate("std_util__aggregate2").
+is_a_all_solutions_predicate("unsorted_aggregate").
+is_a_all_solutions_predicate("std_util__unsorted_aggregate").
+is_a_all_solutions_predicate("do_while").
+is_a_all_solutions_predicate("std_util__do_while").
+
+
+:- pred remove_goal_path(call_path, call_number).
+:- mode remove_goal_path(in, out) is det.
+remove_goal_path(CallNumber - _GoalPath, CallNumber).
+	
+
+:- pred remove_sol_failing_branch(goal_path_string, call_number, 
+	goal_successors_table, goal_successors_table,
+	proof_tree_table, proof_tree_table).
+:- mode remove_sol_failing_branch(in, in, in, out, in, out) is det.
+remove_sol_failing_branch(GoalPath, CurrentCN, GoalSuccT0, GoalSuccT, 
+	ProofT0, ProofT) :-
+	( if get_goal_path_previous_disj(GoalPath, GoalPathPrevDisj) then 
+                % Get the succesors list of the father of the current goal.
+                map__lookup(GoalSuccT0, CurrentCN, SuccList),
+		% From this list of successors, we want to remove all the 
+		% call_path that were in the previous disjunction; to do 
+		% that, we use the goal path information.
+		list__filter(remove_this_goal_path(GoalPathPrevDisj), 
+	                SuccList, NewSuccList),
+	        map__det_update(GoalSuccT0, CurrentCN, NewSuccList, GoalSuccT),
+		% We also remove the corresponding proof trees
+		list__delete_elems(SuccList, NewSuccList, FailList),
+		remove_proof_trees(ProofT0, FailList, ProofT)
+	  else
+                GoalSuccT = GoalSuccT0,
+		ProofT = ProofT0
+	).
+
+:- pred remove_sol_if_branch(goal_path_string, call_number, 
+	goal_successors_table, goal_successors_table,
+	proof_tree_table, proof_tree_table).
+:- mode remove_sol_if_branch(in, in, in, out, in, out) is det.
+remove_sol_if_branch(GoalPath, CurrentCN, GoalSuccT0, GoalSuccT, 
+	ProofT0, ProofT) :-
+	( if string__remove_suffix(GoalPath, "e;", GoalPathIfBranch) then
+                % Get the succesors list of the current of the current goal.
+                map__lookup(GoalSuccT0, CurrentCN, SuccList),
+		% From this list of successors, we want to remove all the 
+		% call_path that were in the previous disjunction; to do 
+		% that, we use the goal path information.
+		list__filter(remove_this_goal_path(GoalPathIfBranch), 
+	                SuccList, NewSuccList),
+	        map__det_update(GoalSuccT0, CurrentCN, NewSuccList, GoalSuccT),
+		% We also remove the corresponding proof trees
+		list__delete_elems(SuccList, NewSuccList, FailList),
+		remove_proof_trees(ProofT0, FailList, ProofT)
+	  else
+                error("Not an ite_else event")
+	).
+
+
+:- pred remove_proof_trees(proof_tree_table, list(call_path), proof_tree_table).
+:- mode remove_proof_trees(in, in, out) is det.
+remove_proof_trees(ProofT0, [], ProofT0).
+remove_proof_trees(ProofT0, [CN-_|Tail], ProofT) :-
+	map__det_remove(ProofT0, CN, _, ProofT1),
+	remove_proof_trees(ProofT1, Tail, ProofT).
+
+:- pred remove_this_goal_path(goal_path_string,  call_path).
+:- mode remove_this_goal_path(in, in) is semidet.
+% We check whether Path1 is a sub path of Path2
+remove_this_goal_path(Path1, _ - Path2) :-
+	string__append(Path1, _, Path2).
+
+:- pred remove_current_cn(call_number,  call_path).
+:- mode remove_current_cn(in, in) is semidet.
+remove_current_cn(N1, N2 - _) :-
+	not (N1 = N2).
+
+:- pred get_goal_path_previous_disj(goal_path_string, goal_path_string).
+:- mode get_goal_path_previous_disj(in, out) is semidet.
+% Takes a goal path of the form "<path>, di" (where i>0), and outputs the 
+% goal path "<path>, di-1" if i>1, fails otherwise
+get_goal_path_previous_disj(GoalPath, GoalPathPrev) :-
+	reverse_string(GoalPath, GoalPathRev),
+	( if string__sub_string_search(GoalPathRev, "d", Index0) then
+                Index = Index0 
+	  else
+                error("No disjunctions is find in this string")
+	),
+	string__split(GoalPathRev, Index, NRevStr0, GoalPathTailRev),
+	% remove the ";" 
+	string__append(";", NRevStr, NRevStr0),
+	% check that it is not the first disjunction
+	not (NRevStr = "1"),
+	reverse_string(GoalPathTailRev, GoalPathTail),
+	reverse_string(NRevStr, NStr),
+	( if string__to_int(NStr, N0) then
+                NPrev is N0 - 1
+	  else
+                error("Should be an integer")
+	),
+	string__int_to_string(NPrev, NPrevStr),
+	string__append_list([GoalPathTail, NPrevStr, ";"], GoalPathPrev).
+
+
+:- pred reverse_string(string, string).
+:- mode reverse_string(in, out) is det.
+reverse_string(String, StringRev) :-
+	% Is this the most efficient way to do that?
+	string__to_char_list(String, Char),
+	string__from_rev_char_list(Char, StringRev). 
+	
+
+:- pred update_goal_path(event::in, 
+	goal_path_string::in, goal_path_string::out, 
+						% To get rigth goal path
+						% at call ports.
+	stack(goal_path_string)::in, stack(goal_path_string)::out,
+						% To get rigth goal path
+						% at exit and fail ports.
+	stack(goal_path_string)::in, stack(goal_path_string)::out
+						% To get rigth goal path
+						% at redo ports.
+    ) is det.
+        % For efficiency, this should be interwined inside filter. I did not
+        % do that for the sake of clarity and reusability.
+        % Hopefully, the deforestration optimisation should be able to
+        % work here.
+update_goal_path(Event, GoalPath0, GoalPath, DetStack0, DetStack, 
+    NonDetStack0, NonDetStack) :-
+	Port = port(Event),
+	Det = determinism(Event),
+	(
+		Port = call
+	->
+		stack__push(DetStack0, GoalPath0, DetStack),
+		NonDetStack = NonDetStack0,
+		GoalPath = GoalPath0
+	;
+		Port = exit
+	->
+		stack__pop_det(DetStack0, GoalPath, DetStack),
+		( nondet_or_multi(Det) ->
+		        % Only nondet and multi proc have redo ports
+			stack__push(NonDetStack0, GoalPath, NonDetStack)
+		;
+			NonDetStack = NonDetStack0
+		)
+	;
+		Port = redo
+	->
+		stack__pop_det(NonDetStack0, GoalPath, NonDetStack),
+		stack__push(DetStack0, GoalPath, DetStack)
+	;
+		Port = fail
+	->
+		stack__pop_det(DetStack0, GoalPath, DetStack),
+		NonDetStack = NonDetStack0
+	;
+	        % At internal events, the goal path is available
+		GoalPath = goal_path(Event),
+		NonDetStack = NonDetStack0,
+		DetStack = DetStack0
+	).
+
+:- pred nondet_or_multi(determinism::in) is semidet.
+nondet_or_multi(3). % nondet
+nondet_or_multi(7). % multi
+
+
+:- pred update_call_stack(trace_port_type, call_number, stack(call_number), 
+        stack(call_number)).
+:- mode update_call_stack(in, in, in, out) is det.
+
+update_call_stack(Port, Current, CallStack0, CallStack) :-
+	( 
+		( Port = call ; Port = redo )
+	->
+		stack__push(CallStack0, Current, CallStack)
+	;
+		( Port = exit ; Port = fail )
+	->
+		stack__pop_det(CallStack0, _, CallStack)
+	;
+	        CallStack = CallStack0
+	).
+
+
+post_process(ct(_, _, _, _, _, _, ProofTreeTable), ProofTree) :-
+	( map__to_assoc_list(ProofTreeTable, [1 - ProofTree0 | _]) ->
+	        ProofTree = ct(ProofTree0)
+	    ;
+	        % Should not occur
+	        ProofTree = pb("map__to_assoc_list failed \n") 
+	).
Index: extras/morphine/source/control_flow.op
===================================================================
RCS file: /home/mercury1/repository/mercury/extras/morphine/source/control_flow.op,v
retrieving revision 1.5
diff -u -d -u -r1.5 control_flow.op
--- extras/morphine/source/control_flow.op	2001/07/12 07:32:09	1.5
+++ extras/morphine/source/control_flow.op	2001/07/12 11:39:55
@@ -257,3 +257,225 @@
 	).   
 
 
+%------------------------------------------------------------------------------%
+%------------------------------------------------------------------------------%
+%------------------------------------------------------------------------------%
+opium_command(
+	name		: proof_tree,
+	arg_list	: [ProgramCall],
+	arg_type_list	: [is_mercury_program_call],
+	abbrev		: pt,
+	interface	: button,
+	command_type	: opium,
+	implementation	: proof_tree_Op,
+	parameters	: [ps_viewer],
+	message		:
+"Computes the program proof tree."
+	).
+
+proof_tree_Op(Program) :-
+	run(Program),
+	generate_filename(Program, ".pt.dot", ".pt.ps", DotFile, PsFile),
+	generate_proof_tree_graph(Graph),
+	display_graph(Graph, DotFile, PsFile).
+
+%
+%:- pred generate_dynamic_call_graph(graph).
+%:- mode generate_dynamic_call_graph(out) is det.
+%
+
+generate_proof_tree_graph(G) :-
+	getenv("MERCURY_MORPHINE_DIR", MorphineDir),
+	append_strings(MorphineDir, "/source/collect__proof_tree", 
+		CollectFile),
+	collect(CollectFile, Result),
+	( Result = ct(ProofTree), ! ; Result = pb(Msg), print(Msg), abort), 
+	tree_to_graph(0:toplevel, ProofTree, G).
+
+tree_to_graph(Father, leaf(CN, PredUniv), [arc(Father, CN:Pred)]) :-
+	remove_type_pred(PredUniv, Pred).
+tree_to_graph(Father, tree(CN, PredUniv, TreeList), Graph) :-
+	remove_type_pred(PredUniv, Pred),
+	maplist(tree_to_graph(CN:Pred), TreeList, GraphList),
+	list_of_list_to_list(GraphList, Graph0),
+	Graph = [arc(Father, CN:Pred) | Graph0].
+
+remove_type_pred(p(NameStr, _Arity, ArgUnivList), Pred) :-
+	maplist(remove_type_arg, ArgUnivList, ArgList),
+	( string(NameStr) -> atom_string(Name, NameStr) ; Name = NameStr),
+	append([Name], ArgList, NameArgList),
+	Pred =.. NameArgList.
+
+remove_type_arg(univ_cons(state('<<c_pointer>>')), '-'). % We don't display that
+remove_type_arg(univ_cons(Arg), Arg).
+
+
+list_of_list_to_list([], []).
+list_of_list_to_list([List], List).
+list_of_list_to_list([List1, List2 | Tail], LList) :-
+	append(List1, List2, List),
+	LList0 = [List | Tail],
+	list_of_list_to_list(LList0, LList).
+
+
-- 
R1.


--------------------------------------------------------------------------
mercury-reviews mailing list
post:  mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe:   Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------



More information about the reviews mailing list