Views
EtnaNova_ELEM_supplement.stl
- History
- 
        Action Performed by Date and Time Comment Publish Paul McJones 2021-02-26 20:21 No comments. 
package logic_syntax_analysis_pak2;		 	-- second part of logic_syntax_analysis_pak  
		-- This package is a supplement to the main package 'logic_syntax_analysis_pak', 
		-- separated out only because that other file was becoming uncomfortably large. 
		-- The processes found here handle monotone and algebraic inference, and several utilities
		-- as seen in the header below. 
		-- It is also used as a repository for obsolete versions of inference and related codes.
		-- This file also includes (at its end) the package 'algebraic_parser', an auxiliary package for simple algebraic parsing,
		-- useful for communicating with external provers which use only a simple algebraic language, but possibly
		-- with operator signs and precedences different from those meeting the standard SETL syntactic conventions.
	
	var monotonicity_props := {};				-- maps an operator into its declared monotonicity properties
	var var_generator := 0;						-- used to generate VRXn_ variables
	var alg_of := {}, alg_role_of := {};		-- globals used in algebraic standardization (made visible for testing only)
	var sub_from_addn := {},reversal_from_addn := {},zero_from_addn := {},one_from_addn := {},mult_from_addn := {};
	var ring_from_addn := {};				-- globals used in algebraic standardization (visible for testing only)
	
	var mismatched_args,mismatched_symbvars,mismatched_expn;		-- substitution mismatches for diagnosis
	
--								*********** Monotonicity-based inference ***********
	procedure monotone_infer(node);				-- prepare to deduce a formula by monotone inference
												-- called from 'check_a_monot_inf' in top-level package
	procedure test_monotone_infer; 	 			-- test of the monotone_infer procedure
--								*********** Algebraic inference ***********
	
	procedure find_algebraic_nodes(tree);		-- finds the algebraic nodes in a tree, and returns them
	procedure standardize_subtree(tree,plus_op);
	procedure re_nest(flattened_tree);			-- reconvert a standardized (flattened) tree to its ordinary 'nested' form
	procedure canorder_subtree(tree);
			-- sorts the terms and factors of a standardized (flattened) subtree and performs cancellations
	procedure generate_algebraic_identity(tree);	-- generates identity of tree with standardized form
	procedure find_ring_members(tree);				-- collects the ring membership assertions at the top conjuctive level of a tree
	procedure post_alg_roles(stg);				-- adds an algebraic set to alg_of and alg_role_of maps
--					*********** Miscellaneous tree searching and transformation routines ***********
	procedure find_setformers_and_quants(tree);		-- finds the setformer and quantifier nodes in a tree, and returns them
	procedure find_fnc_symbs(node); 			-- find the function symbols in a tree 
	procedure dequantify(tree,substitution_tup); 		-- general substitution for quantified variables 
	
	procedure replace_fcn_symbol(expn,symbol,symbvars,symbdef);
		-- at each occurrence of a symbol in expn, finds the arguments of symbol,
		-- and substitutes them in symbdef for the indicated free variables of symbdef
		-- replacing the symbol occurrence by this substituted form
--								*********** Test Collection ***********
	procedure test_find_fnc_symbs; 				-- test of the find_fnc_symbs procedure
	procedure test_replace_fcn_symbol; 	 		-- test of the replace_fcn_symbol procedure
	procedure test_dequantify; 					-- test of the dequantify procedure
--								*********** Obsolete code ***********
	
	procedure monotone_inference(node1,node2); 		-- obsolete
	procedure test_monotone_inference; 	 		-- test of the monotone_inference procedure 		-- obsolete
	 			-- calculates conditions for value defined  by node 1 to include value defined by node 2
end logic_syntax_analysis_pak2;
package body logic_syntax_analysis_pak2;				-- second part of logic_syntax_analysis_pak
	use string_utility_pak,parser,sort_pak; 			--	standard Setl utilities
	use logic_parser_globals;							-- global variables for logic parser and other basic packages
	use logic_parser_aux;								-- auxiliary routines for logic basic routines
	use logic_syntax_analysis_pak;						-- use the main package of inferencing routines
	var difference_quintuples := {},monot_var_ctr := 0; -- globals used by 'monotone_inference_in'			
	var all_fnc_symbs := {}; 							-- globals used in 'find_fnc_symbs'
	var debug_flag := false;							-- debugging print switch
	
	const builtin_fcns:= {"CAR", "CDR","ast_arb","ast_if_expr","ast_if", "ast_range", "ast_domain","ast_nelt"};
			-- the definitions of these are  considered to be 'built-in'
--								****************************************************
--								*********** Monotonicity-based inference ***********
--								****************************************************
	-- This group of routines handle prof by monotonicity. Inferencing of this kind assumes that the 
	-- monotonicity properties of some of the operators and other constructions found in the  
	-- formulae which are being processed are known, at least in part. The routines seen below are prepared to handle
	-- inferences involving formulae of the form e1 incs e2, e1 •incin e2, e1 •imp e2, e1 = e2, and e1 •eq e2.
	-- Analysis of a formula of one of these forms always produces a stronger collection of simpler clauses, which 
	-- can then replace the original clause in the ELEM step used to complete the desired inference.
	-- Examples of formulae which can easily be verified by monotone inference are 
	--			#pow(Un(a + b)) incs #pow(Un(c))     and  Un(a + b) = Un(a) +  Un(b)
	
	-- and 
	-- 			{e(x): x in a | P(x)} + {e(x): x in a | Q(x)} = {e(x): x in a | Q(x) or P(x)}
	-- The first two of these evidently depend on prior knowledge of the monotonicity properties of 
	-- pow, Un, and cardinality. These are made known to the Ref system by special declarations, which invoke the 
	-- 'post_monotone' routine seen below.
	
	-- The general action of the family of routines in this 'monotonicity' set are most easily understood
	-- by considering one of them, namely monotone_infer_ast_incs, whhich handles formulae of the form e1 incs e2.
	
	-- In this case, we traverse the syntax trees of e1 and e2 downward in parallel as long as the lead operators of the nodes encountered
	-- are identical, have known monotonicity properties, and none bind any variables.
	-- When there are identical lead operators, we propagate a incs a' to corresponding arguments of increasing
	-- monotonicity,  but a' incs a  to corresponding arguments of decreasing monotonicity.
	-- For operator arguments of indeterminate monotonicity, monotone_infer_ast_eq is called. 
	-- When there are different lead operators, we demand inclusion of the expression values represented by the subtrees, 
	-- except in the following cases:
		-- (i) a incs b + c is treated as the conjunct (a incs b) & (a incs c)
		-- (ii) a * b incs c is treated as the conjunct (a incs c) & (b incs c)
	-- Consider, for example the first inclusion seen above. Assuming that the monotonicity of the operators
	-- pow, Un, and cardinality have been declared, parallel traversal of the parse trees of 
	-- #pow(Un(a + b)) and #pow(Un(c)) reduces this inclusion to a + b incs c, which is therefore the clause generated 
	-- for handling by the concluding ELEM deduction. For more examples of formulae which can be handled by monotone inference, 
	-- see the routine 'test_monotone_infer' below.
procedure monotone_infer(node);			-- deduce a formula by monotone inference
	-- this converts a node of any of the special cases that can be handled into a collection
	-- of simpler clauses to be verified by ELEM reasoning. If the clause cannot be handled,
	-- node is returned.
--print("monotone_infer: ",node);
	if not is_tuple(node) then return node; end if;
	[n1,n2,n3] := node;
--print("monotone_infer: ",n1," ",unparse(n2)," ",unparse(n3));	
	case n1 
		when "ast_eq" => return disjoin_really(disjoin_many({node,monotone_infer_ast_eq(n2,n3)})(2)); 
		when "ast_incs" => return disjoin_really(disjoin_many({node,monotone_infer_ast_incs(n2,n3)})(2)); 
		when "DOT_INCIN" => return disjoin_really(disjoin_many({node,monotone_infer_ast_incs(n3,n2)})(2)); 
		when "DOT_IMP" => return disjoin_really(disjoin_many({node,monotone_infer_ast_imp(n2,n3)})(2)); 
		when "DOT_EQ" => return disjoin_really(disjoin_many({node,monotone_infer_ast_equiv(n2,n3)})(2)); 
		otherwise => return node;
	end case;
	
end monotone_infer;
procedure monotone_infer_ast_incs(n1,n2);		-- deduce an inclusion by monotone inference
	-- We traverse the two syntax trees downward as long as the lead operators of the nodes encountered
	-- are identical, have known monotonicity properties, and none bind any variables.
	-- When there are identical lead operators, we propagate a incs a' to corresponding arguments of increasing
	-- monotonicity,  but a' incs a  to corresponding arguments of decreasing monotonicity.
	-- For operator arguments of indeterminate monotonicity, monotone_infer_ast_eq is called. 
	-- When there are different lead operators, we demand inclusion of the expression values represented by the subtrees, 
	-- except in the following cases:
		-- (i) a incs b + c is treated as the conjunct (a incs b) & (a incs c)
		-- (ii) a * b incs c is treated as the conjunct (a incs c) & (b incs c)
		-- (iii) n1 and n2 are both setformer nodes, of forms like {e(x): x in s | P(x)}. In this case we form the conjunct 
		--  ((s incs s') or monotone_infer_ast_incs(s,s')) & (FORALL x in s | e(x) = e'(x)) & (FORALL x in s' | P'(x) •imp P(x)),
		-- omitting any conjunct which is evident by blobbing.
		-- If both templates apply, the disjunction of both these sets of clauses should be formed.
--print("monotone_infer_ast_incs: ",unparse(n1)," ",unparse(n2));
		if is_string(n1) and is_string(n2) then return if n1 = n2 then "TRUE" else ["ast_incs",n1,n2] end if; end if;
		
		[blob_1,blob_2] := blob_tree(["ast_of","BOTH_",["ast_list",n1,n2]]);			-- call special 'common blobbing' function
		if blob_1 = blob_2 then return "TRUE"; end if;
					-- check for more general equality evident by blobbing
		
		if is_string(n2) then
			if n1(1) = "ast_add" then 
				return disjoin_many({["ast_incs",n1,n2],monotone_infer_ast_incs(n1(2),n2),monotone_infer_ast_incs(n1(3),n2)});
			else
				return ["ast_incs",n1,n2];
			end if;
		end if;
			
		if is_string(n1) then
			if b1 = "ast_add" then 
				return disjoin_many({["ast_incs",n1,n2],conjoin_many({monotone_infer_ast_incs(a2,n2),monotone_infer_ast_incs(a3,n2)})});
			else
				return ["ast_incs",n1,n2];
			end if;
		end if;
	
		[a1,a2,a3,a4] := n1; [b1,b2,b3,b4] := n2; 	-- otherwise decompose the two trees
		
		
		if a1 = "ast_genset_noexp" then 			-- convert to form with expression
			iter_var := a2(2)(2);
			n1 := [a1,a2,a3,a4] := ["ast_genset",iter_var,a2,a3];
		end if;
		
		if b1 = "ast_genset_noexp" then 			-- convert to form with expression
			iter_var := b2(2)(2);
			n2 := [b1,b2,b3,b4] := ["ast_genset",iter_var,b2,b3];
		end if;
		if a1 = b1 then			-- identical lead operators
		
			case a1 
			
			when "ast_if_expr" =>				-- inclusion of conditional expressions
					cond := monotone_infer_ast_equiv(a2,b2); 
					return disjoin_many({["ast_incs",n1,n2],
							if cond /= "TRUE" then conjoin_many({cond,monotone_infer_ast_incs(a3,b3),monotone_infer_ast_incs(a4,b4)})
							else monotone_infer_ast_incs(a3,b3) end if}); 
					
			when "ast_add","ast_mult" =>  			-- commutative expressions
				
					return disjoin_many({["ast_incs",n1,n2],
						conjoin_many({monotone_infer_ast_incs(a2,b2),monotone_infer_ast_incs(a3,b3)}),
									conjoin_many({monotone_infer_ast_incs(a2,b3),monotone_infer_ast_incs(a3,b2)})});
				
			when "ast_sub" =>  			-- set difference
		
					return disjoin_many({["ast_incs",n1,n2],conjoin_many({monotone_infer_ast_incs(a2,b2),monotone_infer_ast_incs(b3,a3)})});
			when "ast_pow","ast_nelt" =>  							-- SETL builtin monadics; both of these are monotone
					return disjoin_many({monotone_infer_ast_incs(a2,b2),["ast_incs",n1,n2]});	-- test arguments for equality
			when "ast_genset" =>     		-- setformer
				-- setformer pairs are treated in a special way if their sequences itseq of iterators are of the same
				-- length. 
				if #(itlist1 := a3(2..)) /= #(itlist2 := b3(2..)) then return ["ast_eq",n1,n2];	end if;
				
				conds := {};			-- condition for inclusion of variable bounds; will be collected.
				varlist_1 := varlist_2 := genvarlist := iterlimit_list := [];
				
				for [itop1,itvar1,itlimit1] = itlist1(j) loop			-- work over the two iterator lists in parallel
					[itop2,itvar2,itlimit2] := itlist2(j); 
		 				
					freevars1 := find_free_vars(itlimit1);		-- get the free variables in the iterators
					freevars2 := find_free_vars(itlimit2); 
					subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
					subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
					if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit1 := substitute(itlimit1,subst_map1); 
					end if;
					if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit2 := substitute(itlimit2,subst_map2); 
					end if;
					varlist_1 with:= itvar1;			-- extend the lists of iterator and generated variables
					varlist_2 with:=  itvar2; 
					genvarlist with:= "VRX" + (var_generator +:= 1) + "_"; 
					
					if itop1 /= itop2 then
						
						if itop1 = "DOT_INCIN" then 			-- convert to member of powerset
							itop1 := "ast_in"; itlimit1 := ["ast_pow",itlimit1];
						end if;
						
						if itop2 = "DOT_INCIN" then 			-- convert to member of powerset
							itop2 := "ast_in"; itlimit2 := ["ast_pow",itlimit2];
						end if;
				
					end if;
--print("itlimits: ",itlimit1," ",itlimit2);
					conds with:= monotone_infer_ast_incs(itlimit1,itlimit2);
					iterlimit_list with:= itlimit1;				-- collect the (first) list of iteration limits
					
				end loop;
				
							-- now demand that the substituted expressions are equal, 
							-- and the second substituted condition implies the first
				subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j)};
				subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j)};
				conds with:=  monotone_infer_ast_eq(substitute(a2,subst_map1),substitute(b2,subst_map2));
--print("conds::: ",{unparse(cond): cond in conds});
				
				if a4(1) = "ast_null" then a4 := ["TRUE"]; end if;
				if b4(1) = "ast_null" then b4 := ["TRUE"]; end if;
--print("a4: ",a4," ",b4);
				conds with:=  monotone_infer_ast_imp(substitute(b4,subst_map1),substitute(a4,subst_map2));
--print("conds: ",{unparse(cond): cond in conds});					
					 
				return conjoin_many(conds); 
			when "ast_of" =>  			-- function application
					
					mprops_of_args := monotonicity_props(a2); 
					
					if mprops_of_args = OM then 			-- demand equality
						if a2 /= b2 or #(a_list := a3(2..)) /= #(b_list := b3(2..)) then return ["ast_incs",n1,n2]; end if;
						return disjoin_many({monotone_infer_ast_eq(n1,n2),["ast_incs",n1,n2]});		
					end if;
						-- otherwise check the argument lists for identity in positions which are not monotone, but for inclusion in such posns.
					a_list := a3(2..); b_list := b3(2..);
					incposns := {j: arg = a_list(j) | mprops_of_args(j) > 0};
					decposns := {j: arg = a_list(j) | mprops_of_args(j) < 0};
--print("incposns,decposns: ",incposns," ",decposns);	
					
					if incposns = {} and decposns = {} then 		-- arguments must be equal
						return disjoin_many({monotone_infer_ast_eq(n1,n2),["ast_incs",n1,n2]});	
					end if;
					
							-- but if there are increasing and/or arguments, then collect inclusions and reversed inclusions
							-- for them, and identities for the others
					conditions := {if j in incposns then monotone_infer_ast_incs(argj,b_list(j))
									elseif j in decposns then monotone_infer_ast_incs(b_list(j),argj) 
									else  monotone_infer_ast_eq(argj,b_list(j)) end if: argj = a_list(j)};
					
					return disjoin_many(conditions with ["ast_incs",n1,n2]);	
				
			otherwise =>  
				
						-- otherwise check the argument lists for identity in positions which are not monotone, but for inclusion in such posns.
					return disjoin_many({["ast_incs",n1,n2],
						conjoin_many({monotone_infer_ast_eq(a2,b2),monotone_infer_ast_eq(a3,b3)})});
			end case;
			
		end if;
							-- if one of the operators is a conditional then check for evaluability
		if b1 = "ast_if_expr" then 			-- interchange and redecompose
			[n1,n2] := [n2,n1];
			[a1,a2,a3,a4] := n1; [b1,b2,b3,b4] := n2;
		end if;
		if a1 = "ast_if_expr" and  monotone_infer_ast_equiv(a2,"TRUE") = "TRUE" then 			-- use first branch
			return monotone_infer_ast_incs(a3,n2);
		end if;
							-- if one of the operators is a union then see if either of its arguments includes the other
--print("different lead ops incs: ",[n1,n2]);
		if a1 = "ast_add" then 			-- interchange and redecompose to put union operator in the second position
			[n1,n2] := [n2,n1];
			[a1,a2,a3,a4] := n1; [b1,b2,b3] := n2;
		end if;
		if b1 = "ast_add" then 			-- have a union operator in the second position
--print("ast_add case incs");					
			conditions := {["ast_incs",n1,n2],monotone_infer_ast_incs(n1,b2),monotone_infer_ast_incs(n1,b3)};
					-- two possible conditions which would force inclusion
			if a1 = "ast_of" then		-- we have a function application; see if there is a position in which the function is additive
										-- and the corresponding argument is a sum, apply additivity
				mprops_of_args := monotonicity_props(a2);
				alist := a3(2..);			-- the list of arguments
				addposns := {j: arg = alist(j) | mprops_of_args(j) = 2};
							-- at such argument positions apply additivity  
				for arg = alist(j) | j in addposns and is_tuple(arg) and arg(1) = "ast_add" loop		-- loop over all qualifying positions
					a31 := a3; a31(j + 1) := arg(2);			-- transform using additivity: build new arguments 
					a32 := a3; a32(j + 1) := arg(3);
					newf1 := [a1,a2,a31];
					newf2 := [a1,a2,a32];
	 				newsum := ["ast_add",newf1,newf2];
--print("newf1 incs: ",unparse(newsum));	 
					conditions with:= monotone_infer_ast_incs(newsum,n2);		-- try this inclusion as an alternative
				end loop; 
				
				return disjoin_many(conditions);	
			end if;
				-- setformers are treated in a special way if their sequences itseq of iterators contain 
				-- a limiting expression which is a union, or if their condition part is a disjunction.
			-- such positions are treated as 'additive arguments'
			if a1 = "ast_genset" then	
				conds := {};			-- conditions for inclusion derived from additivity; will be collected.
				varlist := genvarlist := [];
				itlist := a3(2..);
				for [itop,itvar,itlimit] = itlist(j) | itlimit(1) = "ast_add" and itop = "ast_in" loop
							-- work over the iterator list finding the 'additive' positions
	
					[-,itlimit_a,itlimit_b] := itlimit;
					ilj_a := [itop,itvar,itlimit_a]; ilj_b := [itop,itvar,itlimit_b];
					iters_a := a3; iters_a(j + 1) := ilj_a;
					iters_b := a3; iters_b(j + 1) := ilj_b;
					setf_a := [a1,a2,iters_a,a4];
					setf_b := [a1,a2,iters_b,a4];
					newsum := ["ast_add",setf_a,setf_b];
--print("ast_genset incs: ",unparse(newsum));					
					conds with:= monotone_infer_ast_incs(newsum,n2);		-- apply additivity as an alternative
					
				end loop;
				
				if is_tuple(a4) and a4(1) = "ast_or" then				-- condition part of setformer is 
					[-,cond_a,cond_b] := a4;
 					setf_a := [a1,a2,a3,cond_a];
					setf_b := [a1,a2,a3,cond_b];
					newsum := ["ast_add",setf_a,setf_b];
					
					conds with:= monotone_infer_ast_incs(newsum,n2);		-- apply additivity as an alternative
 				end if;
				return conjoin_many(conds); 
			end if;
		end if;
		
							-- if one of the operators is a union then check the other for additivity and an argument which is a union
		return ["ast_incs",n1,n2];
			
end monotone_infer_ast_incs;
procedure monotone_infer_ast_eq(n1,n2);			-- deduce an equality by monotone inference
	-- We traverse the two syntax trees downward as long as the lead operators of the nodes encountered
	-- are identical and none bind any variables. 
	-- When there are different lead operators, we demand equality of the subtrees, 
	-- except in the following cases:
		-- when there is a + operator in one node n1 but not the other n2, and the nodes have 
		-- one of the following forms: 
		-- (i) f(a1,a2..) and b + c, where the operator f must be additive in at least one of its
		-- arguments (say the first), which must have the form a11 + a12. In this case, generate the 
		-- disjunction of conjunctions ((f(a11,a2,..) = b) & (f(a12,a2,..) = c)) or vice-versa. 
		-- Note: conjuncts should be tested for 'trivial equality by blobbing', and omitted if this is the case. 
		-- (ii) n1 and n2 are both setformer nodes, of forms like {e(x): x in s | P(x)}. In this case we form the conjunct 
		--   ((s = s') or monotone_infer_ast_eq(s,s')) & (FORALL x in s | e(x) = e'(x)) & (FORALL x in s | P(x) •eq P'(x)),
		-- again omitting any conjunct which is evident by blobbing.
		-- (iii) One of the nodes, say the second, has the form b + c, and the other is a setformer of the form 
		-- (a) {e(x): x in s + t | P(x)}, or (b) {e(x): x in s | P(x) or Q(x)} (or both). In these cases we form conjuncts
		-- Case (a) (({e(x): x in s | P(x)} = b) & ({e(x): x in t | P(x)} = c)) or vice-versa
		-- Case (b) (({e(x): x in s | P(x)} = b) & ({e(x): x in s | P(x)} = Q(x))) or vice-versa
		-- If both templates apply, the disjunction of both these sets of clauses should be formed.
	
--print("\nmonotone_infer_ast_eq: ",unparse(n1),"\nn2: ",unparse(n2));		
		if is_string(n1) or is_string(n2) then return if n1 = n2 then "TRUE" else ["ast_eq",n1,n2] end if; end if; 
--print("blob_tree([both_,n1,n2]): ",blob_tree(["ast_of","BOTH_",["ast_list",n1,n2]])," ",n1," ",n2);		
		[blob_1,blob_2] := blob_tree(["ast_of","BOTH_",["ast_list",n1,n2]]);			-- call special 'common blobbing' function
		
		if blob_1 = blob_2 then return "TRUE"; end if;
					-- check for more general equality directly evident by blobbing
--print("not by blobbing");		
		[a1,a2,a3,a4] := n1; [b1,b2,b3,b4] := n2; 	-- otherwise decompose the two trees
		
		if a1 = "ast_genset_noexp" then 			-- convert to form with expression
			iter_var := a2(2)(2);
			n1 := [a1,a2,a3,a4] := ["ast_genset",iter_var,a2,a3];
		end if;
		
		if b1 = "ast_genset_noexp" then 			-- convert to form with expression
			iter_var := b2(2)(2);
			n2 := [b1,b2,b3,b4] := ["ast_genset",iter_var,b2,b3];
		end if;
		if a1 = b1 then
		
			case a1 
			
				when "ast_if_expr" =>				-- equality of conditional expressions
					cond := monotone_infer_ast_equiv(a2,b2); 
					return disjoin_many({["ast_eq",n1,n2],
							if cond /= "TRUE" then conjoin_many({cond,monotone_infer_ast_eq(a3,b3),monotone_infer_ast_eq(a4,b4)})
							else monotone_infer_ast_eq(a3,b3) end if}); 
					
			when "ast_add","ast_mult" =>  			-- commutative expressions
				
					return disjoin_many({["ast_eq",n1,n2],
						conjoin_many({monotone_infer_ast_eq(a2,b2),monotone_infer_ast_eq(a3,b3)}),
									conjoin_many({monotone_infer_ast_eq(a2,b3),monotone_infer_ast_eq(a3,b2)})});
					
			when "ast_sub" =>  			-- set difference
		
					return disjoin_many({["ast_eq",n1,n2],conjoin_many({monotone_infer_ast_eq(a2,b2),monotone_infer_ast_eq(a3,b3)})});
	
			when "ast_genset" =>     		-- two setformers to be tested for equality
				-- setformer pairs are treated in a special way if their sequences itseq of iterators are of the same
				-- length. 
				if (nil1 := #(itlist1 := a3(2..))) /= #(itlist2 := b3(2..)) then return ["ast_eq",n1,n2];	end if;
				
				conds := {};			-- condition for equality of variable bounds; will be collected.
				varlist_1 := varlist_2 := genvarlist := iterlimit_list := [];
--print("itlist1(j): ",itlist1(1));		
				first_genvar := "VRX" + (var_generator + 1) + "_";
						
				for [itop1,itvar1,itlimit1] = itlist1(j) loop			-- work over the two iterator lists in parallel
					[itop2,itvar2,itlimit2] := itlist2(j); 
		 				
					freevars1 := find_free_vars(itlimit1);		-- get the free variables in the iterators
					freevars2 := find_free_vars(itlimit2); 
					subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
					subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
					if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit1 := substitute(itlimit1,subst_map1); 
					end if;
					if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit2 := substitute(itlimit2,subst_map2); 
					end if;
					varlist_1 with:= itvar1;			-- extend the lists of iterator and generated variables
					varlist_2 with:=  itvar2; 
					genvarlist with:= "VRX" + (var_generator +:= 1) + "_"; 
					
					if itop1 /= itop2 then
						
						if itop1 = "DOT_INCIN" then 			-- convert to member of powerset
							itop1 := "ast_in"; itlimit1 := ["ast_pow",itlimit1];
						end if;
						
						if itop2 = "DOT_INCIN" then 			-- convert to member of powerset
							itop2 := "ast_in"; itlimit2 := ["ast_pow",itlimit2];
						end if;
				
					end if;
					conds with:= monotone_infer_ast_eq(itlimit1,itlimit2);
					iterlimit_list with:= itlimit1;				-- collect the (first) list of iteration limits
					
				end loop;
				
							-- now demand that the substituted expressions are equal, and the substituted conditions are equivalent
							-- on all the sets 
				subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j)};
				subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j)};
				
--print("conds::: ",{unparse(cond): cond in conds});
				
				if a4(1) = "ast_null" then a4 := "TRUE"; end if;
				if b4(1) = "ast_null" then b4 := "TRUE"; end if;
--print("a4: ",a4," ",b4);
				if nil1 = 1 then 			-- if there is just 1 iterator, capture the conditon that the bound variable belongs to the limit set
					conds with:= ["DOT_IMP",["ast_in",first_genvar,iterlimit_list(1)],
										monotone_infer_ast_eq(substitute(a2,subst_map1),substitute(b2,subst_map2))];
					conds with:= ["DOT_IMP",["ast_in",first_genvar,iterlimit_list(1)],
										monotone_infer_ast_equiv(substitute(a4,subst_map1),substitute(b4,subst_map2))];
 --print("just 1 iterator: ");
 				else
					conds with:= monotone_infer_ast_eq(substitute(a2,subst_map1),substitute(b2,subst_map2));
					conds with:= monotone_infer_ast_equiv(substitute(a4,subst_map1),substitute(b4,subst_map2));
				end if;
--print("conds: ",{unparse(cond): cond in conds});					
				
				-- The iteration bounds which appear in itseq are treated roughly as additive arguments  
				-- if 'In' appears, but as monotone arguments as '•incin' appears.
				-- for the lead expressions we generate 
				-- for the conditions we generate 
					 
				return conjoin_many(conds); 
			when "ast_pow","ast_nelt" =>  							-- SETL bulitin monadics
--print("builtin: ",unparse(n1)," ",unparse(n2));
					return disjoin_many({monotone_infer_ast_eq(a2,b2),["ast_eq",n1,n2]});	-- test arguments for equality
			when "ast_of" =>  			-- "DOT_EQ"
				
					if a2 /= b2 or #(a_list := a3(2..)) /= #(b_list := b3(2..)) then return ["ast_eq",n1,n2]; end if;
--print("otherwise check: ",a_list," ",b_list," ",conjoin_many({monotone_infer_ast_eq(aj,b_list(j)): aj = a_list(j)}));					
							-- otherwise check the argument lists for identity
					return disjoin_many({conjoin_many({monotone_infer_ast_eq(aj,b_list(j)): aj = a_list(j)}),["ast_eq",n1,n2]});		
				otherwise =>  
				
					return ["ast_eq",n1,n2];
			end case;
			
		end if;
							-- if one of the operators is a conditional then check for evaluability
		if b1 = "ast_if_expr" then 			-- interchange and redecompose
			[n1,n2] := [n2,n1];
			[a1,a2,a3,a4] := n1; [b1,b2,b3,b4] := n2;
		end if;
		
		if a1 = "ast_genset_noexp" then 			-- convert to form with expression
			iter_var := a2(2)(2);
			n1 := [a1,a2,a3,a4] := ["ast_genset",iter_var,a2,a3];
		end if;
		
		if b1 = "ast_genset_noexp" then 			-- convert to form with expression
			iter_var := b2(2)(2);
			n2 := [b1,b2,b3,b4] := ["ast_genset",iter_var,b2,b3];
		end if;
						 			-- see if there is an iterator over an enumerated set and if so reduce
		if a1 = "ast_genset" and is_tuple(reduced := reduce_enum_iterators(n1)) then
			return disjoin_many({["ast_eq",n1,n2],monotone_infer_ast_eq(reduced,n2)});
		end if;
		
		if b1 = "ast_genset" and is_tuple(reduced := reduce_enum_iterators(n2)) then
			return disjoin_many({["ast_eq",n1,n2],monotone_infer_ast_eq(n1,reduced)});
		end if;
		if a1 = "ast_if_expr" and  monotone_infer_ast_equiv(a2,"TRUE") = "TRUE" then 			 			-- use first branch
			return monotone_infer_ast_eq(a3,n2);
		end if;
							-- if one of the operators is a union then check the other for additivity and an argument which is a union
--print("different lead ops: ",[n1,n2]);
		if a1 = "ast_add" then 			-- interchange and redecompose to put union operator in the second position
			[n1,n2] := [n2,n1];
			[b1,b2,b3] := n2; [a1,a2,a3,a4] := n1;
		end if;
		if b1 = "ast_add" then 			-- there is a union operator in the second position
--print("ast_add case: ",[a1,a2,a3,a4]);					
			if a1 = "ast_of" then		-- we have a function application; 
										-- see if there is a position in which the function is additive,
										-- and in which a union operator appears as the top argument operator 
				mprops_of_args := monotonicity_props(a2);
				a_list := a3(2..);
				addposns := {j: arg = a_list(j) | mprops_of_args(j) = 2 and arg(1) = "ast_add"};
				conds := {["ast_eq",n1,n2]};		-- will collect additional possibilities
				
				for j in addposns loop
					[-,arg_first_summand,arg_second_summand] := a_list(j);
					a3(j + 1) := arg_first_summand;
					new_n11 := [a1,a2,a3];
					a3(j + 1) := arg_second_summand;
					new_n12 := [a1,a2,a3];
					new_n1 := ["ast_add",new_n11,new_n12];
					conds with:= monotone_infer_ast_eq(new_n1,n2);
				end loop;
		
				return disjoin_many(conds);			-- any of the deduced conditions would force equality
			end if;
	
			if a1 = "ast_genset" then		-- we have a setformer 
				-- see if the main operator in any of the iterator limits is an addition. If so, this can be treated as an 'additive argument'
				-- also, if the main operator in the condition clause is a disjunction, this can be treated as an 'additive argument'
				
				conds := {["ast_eq",n1,n2]};
				
				if is_tuple(a4) then 			-- examine the condition clause
					[cond_op,cond_1,cond_2] := a4;
					if cond_op = "ast_or" then 		-- treat as 'additive argument'
						setformer_1 := [a1,a2,a3,cond_1];
						setformer_2 := [a1,a2,a3,cond_2];
						eq1 :=  monotone_infer_ast_eq(b2,setformer_1);
						eq2 :=  monotone_infer_ast_eq(b3,setformer_2);
						conds with:= conjoin_many({eq1,eq2});
						eq1 :=  monotone_infer_ast_eq(b3,setformer_1);
						eq2 :=  monotone_infer_ast_eq(b2,setformer_2);
						conds with:= conjoin_many({eq1,eq2});
					end if;
				end if;
				
						-- now scan over the iterators, looking for all those in which the operator is 'in' and the topmost operator is '+'
						-- any of these can be treated as an 'additive argument' 
				itlist := a3(2..);			-- the list of iterators
				varlist := genvarlist := iterlimit_list := [];
				for [itop,itvar,itlimit] = itlist(j) loop			-- work over the iterator list
--print("add-iter case: ",itop," ",itlimit);					
					if itop = "ast_in" and itlimit(1) = "ast_add" then
			 				
						new_iter_1 := a3; new_iter_1(j + 1) := ["in",itvar,itlimit(2)];
						new_iter_2 := a3; new_iter_2(j + 1) := ["in",itvar,itlimit(3)]; 
						new_setf_1 := ["ast_genset",a2,new_iter_1,a4];
						new_setf_2 := ["ast_genset",a2,new_iter_2,a4];
--print("new_setf_1: ",unparse(new_setf_1)," ",unparse(b2));						 
						eq1 :=  monotone_infer_ast_eq(b2,new_setf_1);
						eq2 :=  monotone_infer_ast_eq(b3,new_setf_2);
						conds with:= conjoin_many({eq1,eq2});
						eq1 :=  monotone_infer_ast_eq(b3,new_setf_1);
						eq2 :=  monotone_infer_ast_eq(b2,new_setf_2);
						conds with:= conjoin_many({eq1,eq2});
			
					end if;
				end loop;
--print("collected conds: ",{unparse(cond): cond in conds});				
				return disjoin_many(conds);
			end if;
		
			return ["ast_eq",n1,n2];	
			
		end if;
				-- next we treat setformer and expressionless setformers,,
				-- which are converted to standard setformers {e: iter | cond}
				-- these are treated  by first finding as many lead parts of 'iter' 
				-- as are independent of the  parts of 'iter' that come before.
				-- The iteration bounds which appear in se are treated roughly as additive arguments  
				-- if 'In' appears, but as monotone arguments as '•incin' appears.
				-- for the lead expressions we generate 
				-- for the conditions we generate 
		return ["ast_eq",n1,n2];				-- temporary
		
end monotone_infer_ast_eq;
procedure disjoin_many(set_of_clauses);		-- construct a 'lazy' disjunction
	collected_disjunction_set := {} +/ {lazy_disj(2): lazy_disj in set_of_clauses | lazy_disj(1) = "disjoined"};
	collected_conjunction_set := {conjoin_really(lazy_conj(2)): lazy_conj in set_of_clauses | lazy_conj(1) = "conjoined"};
	collected_raw_set := {raw: raw in set_of_clauses | raw(1) /= "disjoined" and raw(1) /= "conjoined"}; 
	
	return ["disjoined",collected_disjunction_set + collected_conjunction_set + collected_raw_set];
	
end disjoin_many;
procedure conjoin_many(set_of_clauses);		-- construct a 'lazy' conjunction
	collected_conjunction_set := {} +/ {lazy_disj(2): lazy_disj in set_of_clauses | lazy_disj(1) = "conjoined"};
	collected_disjunction_set := {disjoin_really(lazy_conj(2)): lazy_conj in set_of_clauses | lazy_conj(1) = "disjoined"};
	collected_raw_set := {raw: raw in set_of_clauses | raw(1) /= "disjoined" and raw(1) /= "conjoined"}; 
	
	return ["conjoined",collected_disjunction_set + collected_conjunction_set + collected_raw_set];
		
end conjoin_many;
procedure disjoin_really(set_of_clauses);		-- construct a disjunction
--print("disjoin_really: ",set_of_clauses);		
		if (clause from set_of_clauses) = "TRUE" then return "TRUE"; end if;
		
		for subclause in set_of_clauses loop
			if subclause = "TRUE" then return "TRUE"; end if;
			clause := ["ast_or",clause,subclause];
		end loop;
		
		return clause;
		
end disjoin_really;
procedure conjoin_really(set_of_clauses);		-- construct a conjunction
--print("conjoin_really: ",set_of_clauses);		
		clause := [];
		for subclause in set_of_clauses | subclause /= "TRUE" loop
		
			if clause = [] then clause := subclause; continue; end if;
			clause := ["ast_and",clause,subclause];
		end loop;
		
		return if clause = [] then "TRUE" else clause end if;
		
end conjoin_really;
procedure monotone_infer_ast_equiv(n1,n2);		-- deduce an equivalence by monotone inference
	-- We traverse the two syntax trees downward as long as the lead operators of the nodes encountered
	-- are identical and none bind any variables. 
	-- When there are different lead operators, demand equality of the subtrees, 
	-- except in the following cases:
		-- when there is an or operator in one node n1 but not the other n2, and the nodes have 
		-- one of the following forms: 
		-- (iii) One of the nodes, say the second, has the form 'b or c', and the other is an existential quantifier of the form 
		-- (a) (EXISTS x in s + t | P(x)), or (b) (EXISTS x in s | P(x) or Q(x)) (or both). In thes cases we form conjuncts
		-- Case (a) (EXISTS x in s | P(x)) •eq b) & (EXISTS x in t | P(x)) •eq c)) or vice-versa
		-- Case (b) (EXISTS x in s | P(x)) •eq & (EXISTS x in s | Q(x)) •eq c)) or vice-versa
		-- If both templates apply, the disjunction of both these sets of clauses should be formed.
--print("monotone_infer_ast_equiv: ",n1,"\nn2: ",n2);		
		if is_string(n1) or is_string(n2) then return if n1 = n2 then "TRUE" else ["DOT_EQ",n1,n2] end if; end if; 
		[blob_1,blob_2] := blob_tree(["ast_of","BOTH_",["ast_list",n1,n2]]);			-- call special 'common blobbing' function
		if blob_1 = blob_2 then return "TRUE"; end if;
					-- check for more general equality directly evident by blobbing
		[a1,a2,a3,a4] := n1; [b1,b2,b3,a4] := n2; 	-- otherwise decompose the two trees
		
		if a1 = b1 then
--print("equal ops in equiv");		
			case a1 
			
				when "ast_if_expr" =>				-- equality of conditional expressions
					cond := monotone_infer_ast_equiv(a2,b2); 
					return disjoin_many({["DOT_EQ",n1,n2],
							conjoin_many({monotone_infer_ast_equiv(a2,b2),monotone_infer_ast_eq(a3,b3),monotone_infer_ast_eq(a4,b4)})}); 
					
			when "ast_or","ast_and" =>  			-- commutative expressions
				
					return disjoin_many({["DOT_EQ",n1,n2],
						conjoin_many({monotone_infer_ast_eq(a2,b2),monotone_infer_ast_eq(a3,b3)}),
									conjoin_many({monotone_infer_ast_equiv(a2,b3),monotone_infer_ast_equiv(a3,b2)})});
			when "ast_forall", "ast_exists" =>     		-- two quantifiers to be tested for equality
				-- quantifier pairs are treated in a special way if their sequences itseq of iterators are of the same
				-- length. 
				if #(itlist1 := a2(2..)) /= #(itlist2 := b2(2..)) then return ["DOT_EQ",n1,n2];	end if;
				
				conds := {};			-- condition for equality of variable bounds; will be collected.
				varlist_1 := varlist_2 := genvarlist := iterlimit_list := [];
			
				for [itop1,itvar1,itlimit1] = itlist1(j) loop			-- work over the two iterator lists in parallel
					[itop2,itvar2,itlimit2] := itlist2(j); 
		 				
					freevars1 := find_free_vars(itlimit1);		-- get the free variables in the iterators
					freevars2 := find_free_vars(itlimit2); 
					subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
					subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
					if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit1 := substitute(itlimit1,subst_map1); 
					end if;
					if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit2 := substitute(itlimit2,subst_map2); 
					end if;
					varlist_1 with:= itvar1;			-- extend the lists of iterator and generated variables
					varlist_2 with:=  itvar2; 
					genvarlist with:= "VRX" + (var_generator +:= 1) + "_"; 
					
					if itop1 /= itop2 then
						
						if itop1 = "DOT_INCIN" then 			-- convert to member of powerset
							itop1 := "ast_in"; itlimit1 := ["ast_pow",itlimit1];
						end if;
						
						if itop2 = "DOT_INCIN" then 			-- convert to member of powerset
							itop2 := "ast_in"; itlimit2 := ["ast_pow",itlimit2];
						end if;
				
					end if;
					conds with:= monotone_infer_ast_eq(itlimit1,itlimit2);
					iterlimit_list with:= itlimit1;				-- collect the (first) list of iteration limits
					
				end loop;
				
							-- now demand that the substituted conditions are equivalent
				conds with:=  monotone_infer_ast_equiv(substitute(a3,subst_map1),substitute(b3,subst_map2));
--print("conds: ",{unparse(cond): cond in conds});					
					 
				return conjoin_many(conds); 
			when "ast_not" =>  							-- SETL bulitin monadic
					return disjoin_many({monotone_infer_ast_equiv(a2,b2),["DOT_EQ",n1,n2]});	-- test arguments for equality
			when "ast_of" =>  			-- function application
				
					if a2 /= b2 or #(a_list := a3(2..)) /= #(b_list := b3(2..)) then return ["DOT_EQ",n1,n2]; end if;
--print("otherwise check: ",a_list," ",b_list," ",conjoin_many({monotone_infer_ast_eq(aj,b_list(j)): aj = a_list(j)}));					
							-- otherwise check the argument lists for identity
					return disjoin_many({conjoin_many({monotone_infer_ast_eq(aj,b_list(j)): aj = a_list(j)}),["DOT_EQ",n1,n2]});		
				otherwise =>  
				
					return disjoin_many({["DOT_EQ",n1,n2],
						conjoin_many({monotone_infer_ast_equiv(a2,b2),monotone_infer_ast_equiv(a3,b3)})});
			end case;
			
		end if;
							-- if one of the operators is a conditional then check for evaluability
		if a1 = "ast_if_expr" and  monotone_infer_ast_equiv(a2,"TRUE") = "TRUE" then 			 			-- use first branch
			return monotone_infer_ast_eq(a3,n2);
		end if;
							-- if one of the operators is a disjunction then check the other for additivity and an argument which is a union
--print("different lead ops in equiv: ",[n1,n2]);
		if a1 = "ast_or" or a1 = "ast_and" then 			-- interchange and redecompose to put con/disjunction operator in the second position
			[n1,n2] := [n2,n1];
			[b1,b2,b3] := n2; [a1,a2,a3] := n1;
		end if;
		if b1 = "ast_or" then 			-- there is a union operator in the second position
	
			if a1 = "ast_exists" then		-- we have an existential 
--print("ast_or_exists case in equiv: ",[a1,a2,a3]);					
				-- see if the main operator in any of the iterator limits is an addition. If so, this can be treated as an 'additive argument'
				-- also, if the main operator in the condition clause is a disjunction, this can be treated as an 'additive argument'
				
				conds := {["DOT_EQ",n1,n2]};
				
				if is_tuple(a3) then 			-- examine the condition clause
					[cond_op,cond_1,cond_2] := a3;
					if cond_op = "ast_or" then 		-- treat as 'additive argument'
--print("additive in equiv: ",[a1,a2,a3]);
						exis_1 := [a1,a2,cond_1];
						exis_2 := [a1,a2,cond_2];
						eq1 :=  monotone_infer_ast_equiv(b2,exis_1);
						eq2 :=  monotone_infer_ast_equiv(b3,exis_2);
						conds with:= conjoin_many({eq1,eq2});
						eq1 :=  monotone_infer_ast_equiv(b3,exis_1);
						eq2 :=  monotone_infer_ast_equiv(b2,exis_2);
						conds with:= conjoin_many({eq1,eq2});
					end if;
				end if;
				
						-- now scan over the iterators, looking for all those in which the operator is 'in' and the topmost operator is '+'
						-- any of these can be treated as an 'additive argument' 
				itlist := a2(2..);			-- the list of iterators
				varlist := genvarlist := iterlimit_list := [];
				for [itop,itvar,itlimit] = itlist(j) loop			-- work over two iterator list
--print("add-iter case in equiv: ",itop," ",itlimit);					
					if itop = "ast_in" and itlimit(1) = "ast_add" then
			 				
						new_iter_1 := a2; new_iter_1(j + 1) := ["in",itvar,itlimit(2)];
						new_iter_2 := a2; new_iter_2(j + 1) := ["in",itvar,itlimit(3)]; 
						new_exi_1 := ["ast_exists",new_iter_1,a3];
						new_exi_2 := ["ast_exists",new_iter_2,a3];
						 
						eq1 :=  monotone_infer_ast_equiv(b2,new_exi_1);
						eq2 :=  monotone_infer_ast_equiv(b3,new_exi_2);
						conds with:= conjoin_many({eq1,eq2});
						eq1 :=  monotone_infer_ast_equiv(b3,new_exi_1);
						eq2 :=  monotone_infer_ast_equiv(b2,new_exi_2);
						conds with:= conjoin_many({eq1,eq2});
			
					end if;
				end loop;
--print("collected conds: ",{unparse(cond): cond in conds});				
				return disjoin_many(conds);
			end if;
		end if;
		if b1 = "ast_and" then 			-- there is a conjunction operator in the second position
	
			if a1 = "ast_forall" then		-- we have a universal 
--print("ast_or_exists case in equiv: ",[a1,a2,a3]);					
				-- see if the main operator in any of the iterator limits is an addition. If so, this can be treated as an 'additive argument'
				-- also, if the main operator in the condition clause is a disjunction, this can be treated as an 'additive argument'
				
				conds := {["DOT_EQ",n1,n2]};
				
				if is_tuple(a3) then 			-- examine the condition clause
					[cond_op,cond_1,cond_2] := a3;
					if cond_op = "ast_and" then 		-- treat as 'conjunctive argument'
--print("conjunctive in equiv: ",[a1,a2,a3]);
						univ_1 := [a1,a2,cond_1];
						univ_2 := [a1,a2,cond_2];
						eq1 :=  monotone_infer_ast_equiv(b2,univ_1);
						eq2 :=  monotone_infer_ast_equiv(b3,univ_2);
						conds with:= conjoin_many({eq1,eq2});
						eq1 :=  monotone_infer_ast_equiv(b3,univ_1);
						eq2 :=  monotone_infer_ast_equiv(b2,univ_2);
						conds with:= conjoin_many({eq1,eq2});
					end if;
				end if;
				
						-- now scan over the iterators, looking for all those in which the operator is 'in' and the topmost operator is '+'
						-- any of these can be treated as an 'additive argument' 
				itlist := a2(2..);			-- the list of iterators
				varlist := genvarlist := iterlimit_list := [];
				for [itop,itvar,itlimit] = itlist(j) loop			-- work over two iterator list
--print("add-iter case in equiv: ",itop," ",itlimit);					
					if itop = "ast_in" and itlimit(1) = "ast_add" then
			 				
						new_iter_1 := a2; new_iter_1(j + 1) := ["in",itvar,itlimit(2)];
						new_iter_2 := a2; new_iter_2(j + 1) := ["in",itvar,itlimit(3)]; 
						new_exi_1 := ["ast_forall",new_iter_1,a3];
						new_exi_2 := ["ast_forall",new_iter_2,a3];
						 
						eq1 :=  monotone_infer_ast_equiv(b2,new_exi_1);
						eq2 :=  monotone_infer_ast_equiv(b3,new_exi_2);
						conds with:= conjoin_many({eq1,eq2});
						eq1 :=  monotone_infer_ast_equiv(b3,new_exi_1);
						eq2 :=  monotone_infer_ast_equiv(b2,new_exi_2);
						conds with:= conjoin_many({eq1,eq2});
			
					end if;
				end loop;
--print("collected conds: ",{unparse(cond): cond in conds});				
				return disjoin_many(conds);
			end if;
		
		
			return ["DOT_EQ",n1,n2];	-- if nothing special can be done
			
		end if;
		return ["DOT_EQ",n1,n2];		
	
end monotone_infer_ast_equiv;
procedure reduce_enum_iterators(node);		
		-- look for iterators over enumerated sets in a setformer or quantifier, 
		-- and if found then replace by assignments of variable
		-- if not found then return OM
	return OM;			-- *********** REMOVE
	
	if node(1) = "ast_genset" then 
		[a1,a0,a2,a3] := node;
	else				-- must be quantifier
		[a1,a2,a3] := node;
	end if;
	iter_list := a2(2..);
	if not(exists iter in iter_list | iter(1) = "ast_in" and (itlim := iter(3))(1) = "ast_enum_set") then return OM; end if;
	
	case a1
	
		when "ast_genset" => print("reduce_enum_iterators: ",iter_list);		-- reduce to union
	
		when "ast_exists" => print("reduce_enum_iterators: ",iter_list);			-- reduce to disjunction
	
		when "ast_forall" => print("reduce_enum_iterators: ",iter_list);			-- reduce to conjunction
		
	end case;
	
end reduce_enum_iterators;
procedure monotone_infer_ast_imp(n1,n2);		-- deduce an implication by monotone inference
	-- We traverse the two syntax trees downward as long as the lead operators of the nodes encountered
	-- are identical and none bind any variables.
	-- When there are different lead operators, we forego processing, except a few two quantifier-related cases: 
	-- except in the following cases:
		-- (i) (FORALL x in s * t | ..) •imp (b & c) might be the conjunct (FORALL x in s | ..) •imp b) & (FORALL x in t | ..) •imp t)
		-- (ii) (FORALL x in s | P & Q) •imp (b & c) might be the conjunct (FORALL x in s | P) •imp b) & (FORALL x in s | Q) •imp t)
		-- (iii) (b or c) •imp (EXISTS x in s + t | ..) might be the disjunct (b •imp (EXISTS x in s | ..)) or (c  •imp (EXISTS x in t | ..))
		-- (iv) (b or c) •imp (EXISTS x in s | P or Q) might be the disjunct (b •imp (EXISTS x in s | P)) or (c  •imp (EXISTS x in s | Q))
		-- If both templates apply, the disjunction of both these sets of clauses should be formed.
--print("monotone_infer_ast_imp: ",unparse(n1)," ",unparse(n2));
		if is_string(n1) and is_string(n2) then return if n1 = n2 then "TRUE" else ["DOT_IMP",n1,n2] end if; end if;
		
		[blob_1,blob_2] := blob_tree(["ast_of","BOTH_",["ast_list",n1,n2]]);			-- call special 'common blobbing' function
		if blob_1 = blob_2 then return "TRUE"; end if;
					-- check for more general equality evident by blobbing
		
		if  n2 = "TRUE" then return "TRUE"; end if;		-- this case will be common since generated by other monot-inference routines
		
		[a1,a2,a3,a4] := n1; 
		
		if is_string(n2) then
			if a1 = "ast_or" then 
				return disjoin_many({["DOT_IMP",n1,n2],conjoin_many({monotone_infer_ast_imp(a2,n2),monotone_infer_ast_imp(a3,n2)})});
			elseif a1 = "ast_and" then 
				return disjoin_many({["DOT_IMP",n1,n2],monotone_infer_ast_imp(a2,n2),monotone_infer_ast_imp(a3,n2)});
			elseif a1 = "ast_if_expr" and monotone_infer(a2) = "TRUE" then 
				return disjoin_many({["DOT_IMP",n1,n2],monotone_infer_ast_imp(a3,n2)});
			else
				return ["DOT_IMP",n1,n2];
			end if;
		end if;
		
		[b1,b2,b3,b4] := n2; 	-- otherwise decompose the two trees
		
		if a1 = b1 then			-- identical lead operators
		
			case a1 
--print("monotone_infer_ast_imp same: ",unparse(n1)," ",unparse(n2));
			
			when "ast_if_expr" =>				-- implication of conditional expressions
					cond := monotone_infer_ast_equiv(a2,b2); 
					return disjoin_many({["DOT_IMP",n1,n2],
							if cond /= "TRUE" then conjoin_many({cond,monotone_infer_ast_imp(a3,b3),monotone_infer_ast_imp(b4,a4)})
							else monotone_infer_ast_imp(a3,b3) end if}); 
					
			when "ast_or","ast_and" =>  			-- commutative expressions
				
					return disjoin_many({["ast_incs",n1,n2],
						conjoin_many({monotone_infer_ast_imp(a2,b2),monotone_infer_ast_imp(a3,b3)}),
									conjoin_many({monotone_infer_ast_imp(a2,b3),monotone_infer_ast_imp(a3,b2)})});
					
			when "ast_not" =>  			-- two inverse implications
				
				return monotone_infer_ast_imp(b2,a2);
			when "ast_forall" =>     		-- universals
				-- pairs of universals are treated in a special way if their sequences itseq of iterators are of the same length. 
				if #(itlist1 := a2(2..)) /= #(itlist2 := b2(2..)) then return ["DOT_IMP",n1,n2];	end if;
				
				conds := {};			-- condition for inclusion of variable bounds; will be collected.
				varlist_1 := varlist_2 := genvarlist := iterlimit_list := [];
--print("universals: ",itlist1," ",itlist2);				
				for [itop1,itvar1,itlimit1] = itlist1(j) loop			-- work over the two iterator lists in parallel
					[itop2,itvar2,itlimit2] := itlist2(j); 
		 				
					freevars1 := find_free_vars(itlimit1);		-- get the free variables in the iterators
					freevars2 := find_free_vars(itlimit2); 
					subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
					subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
					if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit1 := substitute(itlimit1,subst_map1); 
					end if;
					if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit2 := substitute(itlimit2,subst_map2); 
					end if;
					varlist_1 with:= itvar1;			-- extend the lists of iterator and generated variables
					varlist_2 with:=  itvar2; 
					genvarlist with:= "VRX" + (var_generator +:= 1) + "_"; 
					
					if itop1 /= itop2 then			-- convert inclusion to powerset membership
						
						if itop1 = "DOT_INCIN" then 			-- convert to member of powerset
							itop1 := "ast_in"; itlimit1 := ["ast_pow",itlimit1];
						end if;
						
						if itop2 = "DOT_INCIN" then 			-- convert to member of powerset
							itop2 := "ast_in"; itlimit2 := ["ast_pow",itlimit2];
						end if;
				
					end if;
					conds with:= monotone_infer_ast_incs(itlimit1,itlimit2);		-- hypothesis must 
					iterlimit_list with:= itlimit1;				-- collect the (first) list of iteration limits
					
				end loop;
	 				
				freevars1 := find_free_vars(a3);		-- get the free variables in the iterators
				freevars2 := find_free_vars(b3); 
				subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
				subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
				if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
					a3 := substitute(a3,subst_map1); 
				end if;
				if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
					b3 := substitute(b3,subst_map2); 
				end if;
				conds with:=  monotone_infer_ast_imp(substitute(a3,subst_map1),substitute(b3,subst_map2));
							-- demand that the condition in the first existential imply that in the second
						 
				return disjoin_many({conjoin_many(conds),["DOT_IMP",n1,n2]}); 
			when "ast_exists" =>     		-- existentials
				-- pairs of existentials are treated in a special way if their sequences itseq of iterators are of the same length. 
				if #(itlist1 := a2(2..)) /= #(itlist2 := b2(2..)) then return ["DOT_IMP",n1,n2];	end if;
				
				conds := {};			-- condition for inclusion of variable bounds; will be collected.
				varlist_1 := varlist_2 := genvarlist := iterlimit_list := [];
--print("existentials: ",itlist1," ",itlist2);				
				for [itop1,itvar1,itlimit1] = itlist1(j) loop			-- work over the two iterator lists in parallel
					[itop2,itvar2,itlimit2] := itlist2(j); 
		 				
					freevars1 := find_free_vars(itlimit1);		-- get the free variables in the iterators
					freevars2 := find_free_vars(itlimit2); 
					subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
					subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
					if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit1 := substitute(itlimit1,subst_map1); 
					end if;
					if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
						itlimit2 := substitute(itlimit2,subst_map2); 
					end if;
					varlist_1 with:= itvar1;			-- extend the lists of iterator and generated variables
					varlist_2 with:=  itvar2; 
					genvarlist with:= "VRX" + (var_generator +:= 1) + "_"; 
					
					if itop1 /= itop2 then			-- convert inclusion to powerset membership
						
						if itop1 = "DOT_INCIN" then 			-- convert to member of powerset
							itop1 := "ast_in"; itlimit1 := ["ast_pow",itlimit1];
						end if;
						
						if itop2 = "DOT_INCIN" then 			-- convert to member of powerset
							itop2 := "ast_in"; itlimit2 := ["ast_pow",itlimit2];
						end if;
				
					end if;
					conds with:= monotone_infer_ast_incs(itlimit2,itlimit1);
								-- demand that limit from second existential include that from first 
					iterlimit_list with:= itlimit1;				-- collect the (first) list of iteration limits
					
				end loop;
	 				
				freevars1 := find_free_vars(a3);		-- get the free variables in the iterators
				freevars2 := find_free_vars(b3); 
				subst_map1 := {[v,genvarlist(j)]: v = varlist_1(j) | v in freevars1};
				subst_map2 := {[v,genvarlist(j)]: v = varlist_2(j) | v in freevars2};
				if subst_map1 /= {} then 			-- replace the indicated variables by their generated forms
					a3 := substitute(a3,subst_map1); 
				end if;
				if subst_map2 /= {} then 			-- replace the indicated variables by their generated forms
					b3 := substitute(b3,subst_map2); 
				end if;
				conds with:=  monotone_infer_ast_imp(substitute(a3,subst_map1),substitute(b3,subst_map2));
							-- demand that the condition in the first existential imply that in the second
--print("conds: ",{unparse(cond): cond in conds});					
					 
				return disjoin_many({conjoin_many(conds),["DOT_IMP",n1,n2]}); 
			when "ast_of" =>  			-- function application; equality is an alternative
					
					mprops_of_args := monotonicity_props(a2); 
					
					if mprops_of_args = OM then 			-- demand equality
						if a2 /= b2 or #(a_list := a3(2..)) /= #(b_list := b3(2..)) then return ["ast_incs",n1,n2]; end if;
						return disjoin_many({monotone_infer_ast_eq(n1,n2),["ast_incs",n1,n2]});		
					end if;
						-- otherwise check the argument lists for identity in positions which are not monotone, but for inclusion in such posns.
					a_list := a3(2..); b_list := b3(2..);
					incposns := {j: arg = a_list(j) | mprops_of_args(j) > 0};
					decposns := {j: arg = a_list(j) | mprops_of_args(j) < 0};
					
					if incposns = {} and decposns = {} then 		-- arguments must be equal
						return disjoin_many({monotone_infer_ast_eq(n1,n2),["ast_incs",n1,n2]});	
					end if;
--print("ast_of_imp: ",mprops_of_args," ",decposns);					
							-- but if there are increasing and/or arguments. then collect inclusions and reversed inclusions
							-- for them, and identities for the others
					conditions := {if j in incposns then monotone_infer_ast_incs(b_list(j),argj)
									elseif j in decposns then monotone_infer_ast_incs(argj,b_list(j)) 
									else  monotone_infer_ast_eq(argj,b_list(j)) end if: argj = a_list(j)};
					
					return disjoin_many(conditions with ["ast_imp",n1,n2]);	
					
			otherwise =>  	-- otherwise nothing to do
					return ["DOT_IMP",n1,n2];
			end case;
			
		end if;
--print("different ops imp: ",n1," ",n2);
							-- if one of the operators is a conditional then check for evaluability
		if b1 = "ast_if_expr" then 			-- interchange and redecompose
			[n1,n2] := [n2,n1];
			[a1,a2,a3,a4] := n1; [b1,b2,b3,b4] := n2;
		end if;
		if a1 = "ast_if_expr" and  monotone_infer_ast_equiv(a2,"TRUE") = "TRUE" then 			-- use first branch
print("ast_if_expr imp");
			return monotone_infer_ast_imp(a3,n2);
		end if;
		if b1 = "ast_or" then 			-- have a disjunction operator in the second position
					
			conds := {["DOT_IMP",n1,n2],monotone_infer_ast_imp(n1,b2),monotone_infer_ast_imp(n1,b3)};
					-- two possible conditions which would force implication
			
				-- existentials are treated in a special way if their sequences itseq of iterators contain 
				-- a limiting expression which is a union, or if their condition part is a disjunction.
				-- such positions are treated as 'additive arguments'
			if a1 = "ast_exists" then	-- transform using additivity and make recursive call to case of equal operators
					
				itlist := a2(2..);			-- get list of iterators
				
				for [itop,itvar,itlimit] = itlist(j) | itop = "ast_in" and itlimit(1) = "ast_add" loop
					a21 := a2; a21(j + 1) := [itop,itvar,itlimit(2)];			-- transform using additivity: build new arguments 
					a22 := a2; a22(j + 1) := [itop,itvar,itlimit(3)];
					newq1 := [a1,a21,a3];
					newq2 := [a1,a22,a3];
	 				newdis := ["ast_or",newq1,newq2];
					conds with:= monotone_infer_ast_imp(newdis,n2);		-- apply additivity as an alternative
					end loop;
					if a3(1) = "ast_or" then			-- the first condition is a disjunction
						newq1 := [a1,a2,a3(2)];
						newq2 := [a1,a2,a3(3)];
	 					newdis := ["ast_or",newq1,newq2];
						conds with:= monotone_infer_ast_imp(newdis,n2);		-- apply additivity as an alternative
					end if;
			
			end if;
			return disjoin_many(conds); 			-- any of the accumulated conditions would force implication
		end if;
		if a1 = "ast_or" then 			-- have a disjunction operator in the first position
					
			conds := {["DOT_IMP",n1,n2],conjoin_many({monotone_infer_ast_imp(a2,n2),monotone_infer_ast_imp(a3,n2)})};
					-- two conditions which would force implication
			
				-- existentials are treated in a special way if their sequences itseq of iterators contain 
				-- a limiting expression which is a union, or if their condition part is a disjunction.
				-- such positions are treated as 'additive arguments'
			if b1 = "ast_exists" then	-- transform using additivity and make recursive call to case of equal operators
					
				itlist := b2(2..);			-- get list of iterators
				
				for [itop,itvar,itlimit] = itlist(j) | itop = "ast_in" and itlimit(1) = "ast_add" loop
					b21 := b2; b21(j + 1) := [itop,itvar,itlimit(2)];			-- transform using additivity: build new arguments 
					b22 := b2; b22(j + 1) := [itop,itvar,itlimit(3)];
					newq1 := [b1,b21,b3];
					newq2 := [b1,b22,b3];
	 				newdis := ["ast_or",newq1,newq2];
					conds with:= monotone_infer_ast_imp(n1,newdis);		-- apply additivity as an alternative
				end loop;
				if b3(1) = "ast_or" then			-- the first condition is a disjunction
					newq1 := [b1,b2,b3(2)];
					newq2 := [b1,b2,b3(3)];
	 				newdis := ["ast_or",newq1,newq2];
					conds with:= monotone_infer_ast_imp(n1,newdis);		-- apply additivity as an alternative
				end if;
			
			end if;
			return disjoin_many(conds); 			-- any of the accumulated conditions would force implication
		end if;
		if b1 = "ast_and" then 			-- have a disjunction operator in the second position
					
			conds := {["DOT_IMP",n1,n2],conjoin_many({monotone_infer_ast_imp(n1,b2),monotone_infer_ast_imp(n1,b3)})};
					-- two possible conditions whose conjunction would force implication
			
				-- universals are treated in a special way if their sequences itseq of iterators contain 
				-- a limiting expression which is a union, or if their condition part is a conjunction.
				-- such positions are treated as 'additive arguments'
			if a1 = "ast_forall" then	-- transform using additivity and make recursive call to case of equal operators
					
				itlist := a2(2..);			-- get list of iterators
				
				for [itop,itvar,itlimit] = itlist(j) | itop = "ast_in" and itlimit(1) = "ast_add" loop
					a21 := a2; a21(j + 1) := [itop,itvar,itlimit(2)];			-- transform using additivity: build new arguments 
					a22 := a2; a22(j + 1) := [itop,itvar,itlimit(3)];
					newq1 := [a1,a21,a3];
					newq2 := [a1,a22,a3];
	 				newdis := ["ast_and",newq1,newq2];
					conds with:= monotone_infer_ast_imp(newdis,n2);		-- apply additivity as an alternative
					end loop;
					if a3(1) = "ast_and" then			-- the first condition is a disjunction
						newq1 := [a1,a2,a3(2)];
						newq2 := [a1,a2,a3(3)];
	 					newcon := ["ast_and",newq1,newq2];
						conds with:= monotone_infer_ast_imp(newcon,n2);		-- apply conjunction as an alternative
					end if;
			
			end if;
			return disjoin_many(conds); 			-- any of the accumulated conditions would force implication
		end if;
		if a1 = "ast_and" then 			-- have a conjunction operator in the first position
					
			conds := {["DOT_IMP",n1,n2],monotone_infer_ast_imp(a2,n2),monotone_infer_ast_imp(a3,n2)};
					-- two conditions either of which would force implication
			
				-- universals are treated in a special way if their sequences itseq of iterators contain 
				-- a limiting expression which is a union, or if their condition part is a conjunction.
				-- such positions are treated as 'additive arguments'
			if b1 = "ast_forall" then	-- transform using additivity and make recursive call to case of equal operators
					
				itlist := b2(2..);			-- get list of iterators
				
				for [itop,itvar,itlimit] = itlist(j) | itop = "ast_in" and itlimit(1) = "ast_add" loop
					b21 := b2; b21(j + 1) := [itop,itvar,itlimit(2)];			-- transform using additivity: build new arguments 
					b22 := b2; b22(j + 1) := [itop,itvar,itlimit(3)];
					newq1 := [b1,b21,b3];
					newq2 := [b1,b22,b3];
	 				newcon := ["ast_and",newq1,newq2];
					conds with:= monotone_infer_ast_imp(n1,newcon);		-- apply additivity as an alternative
				end loop;
				if b3(1) = "ast_and" then			-- the first condition is a disjunction
					newq1 := [b1,b2,b3(2)];
					newq2 := [b1,b2,b3(3)];
	 				newcon := ["ast_and",newq1,newq2];
					conds with:= monotone_infer_ast_imp(n1,newcon);		-- apply conjunction as an alternative
				end if;
			
			end if;
			return disjoin_many(conds); 			-- any of the accumulated conditions would force implication
		end if;
		
		return ["DOT_IMP",n1,n2];
		
end monotone_infer_ast_imp;
	procedure post_monotone(op_and_arg_string); 	-- note the monotonicity property of one or more function symbols
		-- this adds information concerning the monotonicity properties of an operator to the map 'monotonicity_props'
		-- for use by 'blob_to_monotone'. The parameter is a string of the form op_name,dep_1,...,dep_n, 
		-- where each dep is either '+' (increasing), '++' (additive), '-' (decreasing), '0' (mixed)
		-- these are stored in the map as 1,2,-1,0 respectively. Multiple semicolon-separated declarations of this kind can be supplied
		
		op_and_arg_tups := breakup(breakup(op_and_arg_string,";"),",");
		for op_and_arg_tup in op_and_arg_tups loop
			op := case_change(op_and_arg_tup(1),"lu"); 
			arg_qual := [if x = "+" then 1 elseif x = "++" then 2 elseif x = "--" then -2 elseif x = "-" then -1 else 0 end if: x in op_and_arg_tup(2..)];
			monotonicity_props(op) := arg_qual;
		end loop;
print("monotonicity_props: ",monotonicity_props);
		
	end post_monotone;
--								****************************************************
--								*************** Algebraic inference ****************
--								****************************************************
procedure find_algebraic_nodes(tree);	-- finds the algebraic nodes in a tree, and returns them
										-- as a map from the '+' operator defining an algebraic operator set
	var algebraic_nodes := {};	-- the value of this global is a map; see comment above.
				
		-- an algebraic node is one which is tagged by an algebraic operator belonging to some 
		-- algebraic operator set, but whose parent is either not algebraic or does not belong to
		-- the same algebraic operator set. 
		
	find_algebraic_nodes_in(tree,OM);		-- call the recursive workhorse
	
	return algebraic_nodes;					-- return the map produced
	procedure find_algebraic_nodes_in(tree,plus_op_of_parent);	-- internal recursive workhorse
--print("find_algebraic_nodes_in: ",plus_op_of_parent," ",unicode_unparse(tree));		
		if is_string(tree) then return; end if;		-- since this is not an algebraic expression
		
		if abbreviated_headers(tree(1)) in {"{}","{/}","EX","ALL"} then return; end if;
			-- we do not search below levels at which bound variables appear
		
		op := if (n1 := tree(1)) = "ast_of" then tree(2) else n1 end if; -- get the operator (infix, prefix, or fcn)
		args := if n1 = "ast_of" then tree(3)(2..) else tree(2..) end if;		-- get its arguments
		if (ao := alg_of(op)) /= OM and ao /= plus_op_of_parent then
					-- this is an algebraic node, so associate it with its plus_op
			algebraic_nodes(ao) := (algebraic_nodes(ao)?{}) with tree;
		end if;
					-- in any case, continue search down the tree, passing the algebraic type of the new parent
		for arg in args loop find_algebraic_nodes_in(arg,ao); end loop;
		
	end find_algebraic_nodes_in;
			
end find_algebraic_nodes;
-- Once having found all algebraic nodes in a tree we standardize them, and for each generate an
-- equality which states that the algebraic node is equal to its standardized form
--procedure algebraic_identities(tree);	-- generate algebraic identities from algebraic nodes
--	var all_identities := [];		-- will generate and return all algebraic identities from algebraic nodes
	
--	algebraic_nodes := find_algebraic_nodes(tree);		-- find the algebraic nodes
	
--	for [-,subtree_set] in algebraic_nodes, subtree in subtree_set loop
--		all_identities with:= generate_algebraic_identity(subtree);
					-- standardize the algebraic top of the tree and convert it into an identity
--	end loop;
	
--	return all_identities; 		-- return the generated list of algebraic identities
	
--end algebraic_identities;
procedure generate_algebraic_identity(tree);
			-- calculates the standard form of an algebraic tree and equates the original form with this
--print("generate_algebraic_identity: ",unparse(tree),"\n",unparse(canorder_subtree(tree)));
	return ["ast_eq",tree,canorder_subtree(tree)];		-- simply returns the equality generated by canorder_subtree
	
end generate_algebraic_identity;
procedure canorder_subtree(tree);
			-- sorts the terms and factors of a standardized (flattened) subtree and performs cancellations
			
	if is_string(tree) then return tree; end if;			-- nothing to do if tree is just a variable or constant
	
	plus_op := alg_of(topkind := if (tk := tree(1)) = "ast_of" then tree(2) else tk end if);
				-- get the topmost node and the corresponding addition operator, or the function sign if the top opearator is a function application
		-- sort the terms and factors ofthe standardized  subtree and performs cancellation; then return the re-nested result
	flattened_tree := re_nest(order_and_cancel(st := standardize_subtree(tree,plus_op),plus_op)); 
	flattened_tree := re_nest(order_and_cancel(standardize_subtree(flattened_tree,plus_op),plus_op));
								-- put the tree into standardized form; re-nest to allow for identical associative operators in sequence
								-- repeat twice to get simplifications 2 levels deep
				-- the topmost node can be a reversal, difference, sum, or product
--	res := re_nest(order_and_cancel(flattened_tree,plus_op));
	return flattened_tree;
end canorder_subtree;
procedure order_and_cancel(tree,plus_op);		-- debugging wrapper
	res := oorder_and_cancel(tree,plus_op);
--	print("order_and_cancel:::: ",tree,"\n",unparse(re_nest(tree))," returns: ",unparse(re_nest(res)));	
	return res;
end order_and_cancel;
procedure oorder_and_cancel(tree,plus_op);
	-- sorts the terms and factors of a standardized (flattened) subtree and performs cancellations (workhorse)
--if debug_flag then print("order_and_cancel: ",tree," ",plus_op); end if;	
					-- if the topmost operation in the tree is not algebraic, simply return it unchanged. 
					-- the 'ast_of' case is needed to hanldle unary minus ops, which are written as Rev(x).
					-- 'kind' is set to the topmos algebraic opearator of the teree, if it is algebraic
	if is_string(tree) or alg_of(kind := if (tk := tree(1)) = "ast_of" then tree(2) else tk end if) /= plus_op then return tree; end if;
	case alg_role_of(kind) 			-- handle the varrious kinds of algebraic operations separately
	
					-- monadic minus: process the argument recursively, and re-integrate into a monadic minus
		when "--" => provisional_result :=    ["ast_of",tree(2),["ast_list",sarg := order_and_cancel(tree(3)(2),plus_op)]];
							-- if the simplified argument of the monadic minus is now zero, return 0. Othersie return the constucted tree
					return if sarg = zero_from_addn(plus_op) then sarg else provisional_result end if;
	
		when "-" => 		-- subtraction; sort the positive and negative parts, and then cancel
		
			[sub_op,pos_part,neg_part] := tree;			-- get the two arguments of the subtraction
			
							-- get the lead operators of the positive and negative parts, allowing for monadic minuses
			pos_tk := if is_string(pos_part) or (pos_tk := pos_part(1)) = "ast_of" then OM else pos_tk end if; 
			neg_tk := if is_string(neg_part) or (neg_tk := neg_part(1)) = "ast_of" then OM else neg_tk end if;
			
			pos_reord := order_and_cancel(pos_part,plus_op);				-- process the first argument recursively
 			neg_reord := order_and_cancel(neg_part,plus_op);				-- process the second argument recursively
 						-- if at least one of these is a flattened sum, cancellation should be attempted
 						-- if both are flattened sums, we cancel matching terms of opposite signs.
 						-- if just one is a flattened sum, we add the other to it
--print(". ",pos_reord," neg_reord: ",neg_reord," plus_op: ",plus_op," pos_tk: ",pos_tk);
 					-- rewrite the positive and negative parts as a list of additive terms;
 					-- if either is not such a list, convert it to a singleton list
			pos_reord :=if pos_tk = plus_op then pos_reord(2..) else [pos_reord] end if;
			nnre := #(neg_reord := if neg_tk = plus_op then neg_reord(2..) else [neg_reord] end if);
					-- also, get the length of the negative list of terms
 			neg_posn := 1;	-- this is advanced in the loop which follows, which iterates in coodinated fashion over the two ordered lists
 			new_pos_reord := new_neg_reord := [];		-- these will be collected after cancellation
 			
			for pos_item in pos_reord loop		-- iterate over the list of positive terms
				stgform_pos := str(standardize_bound_vars(pos_item));		-- convert it into a string, for subsequent comparison
				
						-- advance in the negative list, searching for a matching term, until we have gone too far
				while neg_posn <= nnre and (stgform_neg := str(standardize_bound_vars(neg_item := neg_reord(neg_posn)))) < stgform_pos loop
		 			new_neg_reord with:= neg_item;			-- take the negative item and advance to the next negative item
		 			neg_posn +:= 1;
		 		end loop; 
		 				
		 				-- at this point stgform_neg is at least as large as stgform_pos; check for an identical term
		 		if neg_posn <= nnre and stgform_pos = (stgform_neg := str(standardize_bound_vars(neg_item := neg_reord(neg_posn)))) then
		 			neg_posn +:= 1; continue; 
		 		end if;
									-- omit any items which cancel
		 		
				new_pos_reord with:= pos_item;			-- take the positive item and advance to the next positive item
--print("nnre: ",nnre," new_pos_reord: ",new_pos_reord," new_neg_reord: ",new_neg_reord," neg_posn: ",neg_posn," stgform_pos: ",stgform_pos," stgform_neg: ",stgform_neg);
			end loop;
							-- here we must deal with any remaining negative and positive items
			if neg_posn <= nnre and stgform_neg > stgform_pos then new_neg_reord with:= neg_item; end if;
					-- take the last negative item examined if it is larger than the last positive item
			
			if neg_posn <= nnre then new_neg_reord +:= neg_reord(neg_posn + 1..); end if;
					-- take the unexamined negative items if any
			pos_tagged := if new_pos_reord = [] then [] 	-- tag positive and negative parts as sums if they involve more than one term
					elseif #new_pos_reord = 1 then new_pos_reord(1) else [plus_op] + new_pos_reord end if;
			
			neg_tagged := if new_neg_reord = [] then [] 	-- tag positive and negative parts as sums if they involve more than one term
					elseif #new_neg_reord = 1 then new_neg_reord(1) else [plus_op] + new_neg_reord end if;
--print("new_pos_reord: ",new_pos_reord," new_neg_reord: ",new_neg_reord,"  pos_tagged: ",pos_tagged," neg_tagged: ",neg_tagged);			
						
						-- if no terms remain, reeturn the appropraite zero
						-- if only a positive term remains, return it
						-- if only a negative term remains, return its reverse
						-- otherwise return the difference of the two terms
						
			return if pos_tagged = [] and neg_tagged = [] then zero_from_addn(plus_op)
						elseif neg_tagged = [] then pos_tagged 
							elseif pos_tagged = [] then ["ast_of",sub_from_addn(plus_op),["ast_list",neg_tagged]]
								else [sub_op,pos_tagged,neg_tagged] end if;
						
 		when "*" => return [tk] + [order_and_cancel(arg,plus_op): arg in sort_by_stgs(tree(2..))];
							-- products are handled just by processing their factors recursively
					
 		when "+" => return [tk] + [order_and_cancel(arg,plus_op): arg in sort_by_stgs(tree(2..))];
							-- sums are handled just by processing their terms recursively
							-- but with a little more work we could look for opposing terms and cancel them
		otherwise => return tree;
	end case;
end oorder_and_cancel;
procedure sort_by_stgs(tree_tup); 	-- sorts a tuple of formula trees into alphabetical order of their standardized string form forms
	return [y: [x,y] in merge_sort([[str(standardize_bound_vars(tree)),tree]: tree in tree_tup])];
end sort_by_stgs;
procedure standardize_subtree(tree,plus_op);		-- debugging wrapper
	res := sstandardize_subtree(tree,plus_op);
--	print("standardize_subtree:::: ",unparse(tree)," returns: ",unparse(res));	
	
	return res;
	
end standardize_subtree;
procedure sstandardize_subtree(tree,plus_op);
			-- reduces the top of an algebraic subtree to standardized form
			-- returns sequence of identical associative operator combinations as concatenated longer list of arguments, to allow sorting
--if debug_flag then  end if;
	
	reversal_op := reversal_from_addn(plus_op);		-- find the reversal operator for the algebraic opearator family of this tree
	subtraction_op := sub_from_addn(plus_op);		-- find the subtraction operator for the algebraic opearator family of  this tree
	zero := zero_from_addn(plus_op);				-- find the zero constant for the algebraic opearator family of  this tree
	one := one_from_addn(plus_op);					-- find the unit constant for the algebraic opearator family of  this tree
	
	if is_string(tree) then return tree; end if;	-- for variables there is nothing to do
	[n1,n2,n3] := tree;								-- unpack the tree into its lead operator and two (or fewer) arguments
	if (ao := alg_of(node_kind := if n1 = "ast_of" then n2 else n1 end if)) /= plus_op or alg_role_of(node_kind) = "OM" then return tree; end if;
		-- if the tree is not consistent with the indicated plus_op, simply return it
	args := if n1 = "ast_of" then n3(2..) else tree(2..) end if;
				-- allow for case of monadic reversal operator, in which case 'args' is abtained from the template 
				-- ["ast_of","rev",[ast_list,arg]]
		
	standardized_args := [standardize_subtree(arg,plus_op): arg in args]; 
		-- standardize the operator arguments recursively. Each standardized argument returned will be either a string
		-- representing a zero or unit constant, the topmost node of a subtree not belonging to the same 
		-- algebraic set as 'tree', or a flattened product, flattened sum of products, difference of
		-- flattened sums, or reversed flattened sum
--print("standardize_subtree: ",tree," ao: ",ao," standardized_args: ",standardized_args);	
				-- get the leading operator of the first argument
	opk := if (a1 := standardized_args(1))(1) = "ast_of" then a1(2) elseif a1 = zero or a1 = one then a1 else a1(1) end if;	
	
	first_op_kind := if alg_of(opk) /= plus_op then "OM" else alg_role_of(opk) end if;
		-- if the op_kind is OM, or the first argument does not belong to the same algebraic set as its parent,
		-- convert it to the string OM
	
	if a1(1) = "ast_of" and a1(2) = reversal_op then a1 := a1(3)(2); end if;			-- extract the value of argument if it is a reversal
			-- if here is a second argument, get its lead operator 
	if (a2 := standardized_args(2)) /= OM then 
		opk := if a2(1) = "ast_of" then a2(2) elseif a2 = OM then "OM" elseif a2 = zero or a2 = one then a2  else a2(1) end if;
		second_op_kind := if alg_of(opk) /= plus_op then "OM" else alg_role_of(opk) end if;
		if a2(1) = "ast_of"  and a2(2) = reversal_op then a2 := a2(3)(2); end if;			-- extract the value of argument if it is a reversal
	end if; 
--print("standardize_subtree: ",tree," first_op_kind: ",first_op_kind," second_op_kind: ",second_op_kind," zero: ",zero," a1: ",a1);		
--print("reversal_op: ",reversal_op,"  alg_role_of(n1): ", alg_role_of(n1));	
				-- process the separate cases that arise, accodring to the algebraic nature of the topmost
				-- tree operator, and then the nature of the lead operators of its first and second argument
--if debug_flag then print("alg_role_of(node_kind):::: ",alg_role_of(node_kind)); end if;	
	case alg_role_of(node_kind)  -- node_kind is used so that Rev operators, which are function aplications, are teated correctly 
	
		when "+","+nc" => 				-- TOP LEVEL CASE: addition or non-commutative addition
--print("addition: ",a1," ",a2);	
		  case first_op_kind			-- process the various kinds of first operands, case by case
		
			when "OM","1","*","*nc" => 				-- FIRST OPERAND CASE: addition: first op is non-algebraic or a flattened product
	
			case second_op_kind
			
				when "OM","1","*" => 			-- SECOND OPERAND CASE: second op is non-algebraic or a flattened product; 
												-- just return a sum after putting the two operands into standardized order
					return [n1] + sort_by_stgs([a1,a2]);
					
				when "0" => 				-- second op is zero; na + 0
					
					return a1;				-- return the first op
			
				when "+" => 				-- second op is a flattened sum; prepend first arg
					return conc_em(a1,a2,plus_op);		-- conc_em builds and returns a flattened sumflattened sum
			
				when "-" =>  				-- second op is a difference; prepend its first arg to the first argument, and return a difference
					[sub_op,a22,a23] := a2;
--print("addition then subtraction: ",first_op_kind," ",second_op_kind," a1: ",a1," a2: ",a2," result: ",[sub_op,conc_em(a1,a22,plus_op),a23]);			
					return [sub_op,conc_em(a1,a22,plus_op),a23];
					
				when "--" =>   			-- second op is a reversal; return the difference of the first op and the second op's argument
										-- note that here a2 is already the value of the reversal argument
					return [subtraction_op,a1,a2];
				
			end case;
		
			when "0" => 				-- FIRST OPERAND CASE: addition: first op is zero; return second op
--print("first op is zero: ",if second_op_kind = "--" then ["ast_of",reversal_op,["ast_list",a2]] else a2 end if);					
					return if second_op_kind = "--" then ["ast_of",reversal_op,["ast_list",a2]] else a2 end if;
		
			when "+","+nc" =>  				-- FIRST OPERAND CASE:  addition: first op is a sum
	
			case second_op_kind
			
				when "OM","1","*" => 				-- second op is non-algebraic or a product
			
					return conc_em(a1,a2,a1(1));
			
				when "0" => 				-- second op is zero
						
					return a1;				-- return the first op
			
				when "+" => 				-- second op is a flattened sum
					return conc_em(a1,a2,a1(1));
			
				when "-" =>  				-- second op is a difference; return concatenatated sum minus second arg of second arg
			
					[sub_op,a22,a23] := a2;
					return [sub_op,conc_em(a1,a22,a1(1)),a23];
			
				when "--" =>   			-- second op is a reversal this is a1 + (-a2)
										-- note that here a2 is already the value of the reversal argument
					
					return [subtraction_op,a1,a2];		-- return a difference
				
			end case;
		
			when "-" =>  				-- FIRST OPERAND CASE: subtraction: first op is a difference
	
			case second_op_kind
			
				when "OM","1","*" => 				-- second op is non-algebraic or a product
			
					[sub_op,a12,a13] := a1;
					return [sub_op,conc_em(a12,a2,n1),a13];
			
				when "0" => 				-- second op is zero
						
					return a1;				-- return the first op
			
				when "+" => 				-- second op is a flattened sum; return concatenatated sum minus second arg of first arg
			
					[sub_op,a12,a13] := a1;
					return [sub_op,conc_em(a12,a2,a2(1)),a13];		-- use the addition operator a2(1) from the second operand
			
				when "-" =>  				-- second op is a difference
			
					[sub_op,a12,a13] := a1; [-,a22,a23] := a2;
					return [sub_op,conc_em(a12,a22,n1),conc_em(a13,a23,n1)];
			
				when "--" =>   			-- second op is a reversal this is (a - b) - a2, becomes a - (b + a2) 
										-- note that here a2 is already the value of the reversal argument
					[sub_op,a12,a13] := a1; 
					
					conced_subs := conc_em(a13,a2,n1);
					
					return [sub_op,a12,conced_subs];			-- return a difference with a concatenated subtrahend
				
			end case;
		
			when "--" =>   			-- FIRST OPERAND CASE: addition: first op is a reversal
	
			case second_op_kind
	
				when "--" =>   			-- second op is also reversal. This is (-a) + (-b), becomes -(a + b)
					return ["ast_of",reversal_op,["ast_list",conc_em(a1,a2,plus_op)]];		-- JACK
								-- reversals are pushed to the outside, so this case becomes -(a + b)
			
				otherwise => return tree;  			-- no change in this case
				
			end case;
			
		end case;
	
		when "-" =>  				-- TOP LEVEL CASE: subtraction
--print("subtraction: ",a1," ",a2," ",first_op_kind," ",second_op_kind);	
		case first_op_kind
		
			when "OM","1","*" => 				-- subtraction: first op is non-algebraic, or a product
	
			case second_op_kind
			
				when "OM","1","*","+" => 				-- second op is non-algebraic, or a product, or a flattened sum
					return [n1,a1,a2];			-- build into difference
				when "0" => 				-- second op is zero
					return a1;				-- return the first op
			
				when "-" =>  				-- second op is a difference. this is the case (a - (b - c)) becomes a + c - b
					
					[-,a22,a23] := a2;		-- decompose second op
					return [n1,conc_em(a1,a23,plus_op),a22];			-- reverse second op and build into difference
			
				when "--" =>   			-- second op is a reversal; convert to a sum: (a - (-b)) becomes (a + b)
										-- note that here a2 is already the value of the reversal argument
					return conc_em(a1,a2,plus_op);
				
			end case;
		
			when "0" => 				-- subtraction: first op is zero
	
			case second_op_kind
			
				when "OM","1","+","*" => 				-- second op is non-algebraic
					return ["ast_of",reversal_op,["ast_list",a2]];		-- convert to a reversal
			
				when "0" => 				-- second op is zero
					return zero;
			
				when "-" =>  				-- second op is a difference: this is the case 0 - (a - b), becomes (b - a) 
					
					[sub_op,a22,a23] := a2;		-- decompose second op
					return [sub_op,a23,a22];			-- reverse the subtraction
			
				when "--" =>   			-- second op is a reversal; this is the case 0 - (-a) becomes a; just return the value being reversed
										-- note that here a2 is already the value of the reversal argument
					return a2;			-- reverse the difference
				
			end case;
		
			when "+" => 				-- subtraction: first op is a flattened sum
	
			case second_op_kind
			
				when "OM","1","+","*" => 				-- second op is non-algebraic, or involves no subtraction
					return [n1,a1,a2];			-- just build into subtraction
			
				when "0" => 				-- second op is zero
					return a1;				-- return the first op
			
				when "-" =>  				-- second op is a difference
					
					[-,a22,a23] := a2;		-- decompose second op
					return [n1,conc_em(a1,a23,plus_op),a22];			-- reverse the subtraction
			
				when "--" =>   			-- second op is a reversal: this is a1 - (-b))
										-- note that in this case a2 is already the value being reversed
					return conc_em(a1,a2,plus_op);
				
			end case;
		
			when "-" =>  				-- subtraction: first op is a difference
				[-,a12,a13] := a1;		-- unpack it
	
			case second_op_kind
			
				when "OM","1","*","+" => 				-- second op is non-algebraic, or involves no subtraction
					return [n1,a12,conc_em(a13,a2,plus_op)];			-- just build into subtraction
		
				when "0" => 				-- second op is zero
					return a1;				-- return the first op
			
				when "-" =>  				-- second op is a difference
					[-,a22,a23] := a2;		-- decompose second op
					return [n1,conc_em(a12,a23,plus_op),conc_em(a13,a22,plus_op)];			-- reverse the subtraction
			
				when "--" =>   			-- second op is a reversal
					return [n1,conc_em(a12,a2,plus_op),a13];			-- reverse and add
				
			end case;
		
			when "--" =>   			-- subtraction: first op is a reversal
									-- note that in this case a1 is already the value being reversed
--print("subtraction first op is a reversal: ",a1," ",a2," ",[n1,a1,a2]);	
			case second_op_kind
			
				when "OM","1","+","*" => 				-- second op is non-algebraic, or involves no subtraction. 
														-- This is (-a1) - b becomes -(a  +  b), since the reversal cannot be pushed into b
					return ["ast_of",reversal_op,["ast_list",standardize_subtree([plus_op,a1,a2],plus_op)]];
			
				when "0" => 				-- second op is zero
					return ["ast_of",reversal_op,["ast_list",a1]];				-- return the rebuilt first op
			
				when "-" =>  				-- second op is a difference
					[-,a22,a23] := standardize_subtree([n1,a1,a2],plus_op);		-- handle recursively
					return [n1,a23,a22];			-- build reversed arguments into subtraction
			
				when "--" =>   			-- second op is a reversal: this is (-a1) - (-a2), becomes (a2 - a1)
					return [n1,a2,a1];			-- build reversed arguments into subtraction
						
				end case;
			
		end case;
	
		when "--" =>   			-- TOP LEVEL CASE: additive inverse; there is just one argument
--print("additive inverse: ",a1);	
		case first_op_kind
		
			when "+" => 				-- Inversion: argument is an addition; push reversals outward so -(a + b) remains the same
				
				return ["ast_of",reversal_op,["ast_list",a1]];			-- standardize the argument, and return as Inversion
		
			when "OM","1","*" => 				-- Inversion: first op is non-algebraic or involves no subtraction
				return ["ast_of",reversal_op,["ast_list",a1]];
		
			when "0" => 				-- Inversion: first op is zero
				return zero;			-- result is 0
		
			when "-" =>  				-- Inversion: first op is a difference
				[sub_op,a12,a13] := a1;		-- decompose first op
				return [sub_op,a13,a12];			-- reverse the subtraction
		
			when "--" =>   			-- Inversion: first op is a reversal
				return a1;			-- double reversal is original
			
		end case;
	
		when "*","*nc" =>   				-- TOP LEVEL CASE: commutative or non-commutative multiplication
	
		case first_op_kind
		
			when "OM" => 				-- FIRST OPERAND CASE:  multiplication: first op is non-algebraic
	
			case second_op_kind
			
				when "OM" => 				-- second op is non-algebraic
					return [n1,a1,a2];
			
				when "0" => 				-- second op is zero
					return zero;
			
				when "1" => 				-- second op is unity
					return a1;
			
				when "+" => 				-- second op is a flattened sum; distribute the product
--print("second op is a flattened sum: ",a2(2..)," ",distribute_mult([a1],a2(2..),n1));
							-- this routine expects to be passed tuples of sums to be multiplied out
					return distribute_mult([a1],a2(2..),n1);
			
				when "-" =>  				-- second op is a difference; distribute the product
					
					[sub_op,a22,a23] := a2;		-- decompose second op
--print("second op is a difference: a2: ",a2," a1: ",a1," n1: ",n1);
							-- this routine expects to be passed tuples of sums to be multiplied out
					return [sub_op,distribute_mult([a1],[a22],n1),distribute_mult([a1],[a23],n1)];			-- distribute the subtraction
			
				when "--" =>   			-- second op is a reversal; return a reversed concatenated product
					unreversed_prod := conc_em(a1,a2,n1);
							-- concatenate the nonalgebraic argument with the argument of the reversal
--print("unreversed_prod: ",unreversed_prod," ",a2);
					return ["ast_of",reversal_op,["ast_list",unreversed_prod]];		-- build into a top-level reversal and return
			
				when "*" =>   				-- second op is a flattened product
--print("second op is a flattened product: ",conc_em(a1,a2,a2(1))," ",a2(1));
					return conc_em(a1,a2,a2(1));		-- use the product operation from the second operand
				
			end case;
		
			when "0" => 				-- FIRST OPERAND CASE:  multiplication: first op is zero
	
				return zero;
		
			when "1" => 				-- FIRST OPERAND CASE:  multiplication: first op is unity
				return if second_op_kind = "--" then ["ast_of",reversal_op,["ast_list",a2]] else a2 end if;	-- return the actual second argument
		
			when "+" => 				-- FIRST OPERAND CASE: addition: first op is a flattened sum
--print("top level is multiplication: first op is a flattened sum, second_op_kind is: ",second_op_kind," a1: ",unparse(a1)," a2: ",unparse(a2));
			case second_op_kind
			
				when "OM","*" => 				-- second op is non-algebraic or is a product
--print("distribute_mult: ",unparse(distribute_mult(a1(2..),[a2],n1)));
					return distribute_mult(a1(2..),[a2],n1);
							-- this routine expects to be passed tuples of sums to be multiplied out
			
				when "0" => 				-- second op is zero
					return zero;
			
				when "1" => 				-- second op is unity
					return a1;				-- return the first op
			
				when "+" => 				-- second op is a flattened sum; form double distribution of sums
--print("double distribution: ",a1(2..)," ",a2(2..)," ",distribute_mult(a1(2..),a2(2..),n1));
					return distribute_mult(a1(2..),a2(2..),n1);
							-- this routine expects to be passed tuples of sums to be multiplied out
			
				when "-" =>  				-- second op is a difference; form double distribution of difference
					[sub_op,a22,a23] := a2;		-- decompose second op
--print("sum times difference: ",a1," ",a2," ",distribute_mult(a1(2..),a22(2..),n1)," ",distribute_mult(a1(2..),a23(2..),n1));
					return [sub_op,distribute_mult(a1(2..),if is_string(a22)  or alg_of(a22(1)) /= plus_op then [a22] else a22(2..) end if,n1),
					distribute_mult(a1(2..),if is_string(a23)  or alg_of(a23(1)) /= plus_op then [a23] else a23(2..) end if,n1)];			
									-- distribute the subtraction									
									-- distribute_mult expects to be passed tuples of sums to be multiplied out
			
				when "--" =>   			-- second op is a reversal; form reversed result
--print("second op is a reversal: ",ao," ",a1," ",a2);
					return ["ast_of",reversal_op,["ast_list",standardize_subtree([n1,a1,a2],ao)]];		-- this operation is executed recursively
				
			end case;
		
			when "-" =>  				-- FIRST OPERAND CASE:  multiplication: first op is a difference
				
			[-,a12,a13] := a1; 	-- decompose first op; this is a12 - a13
			case second_op_kind
			
				when "OM" => 				-- second op is non-algebraic. This case is (a12 - a13) • a2, where a12 and a13 can be flattened sums
											-- we distribute the multiplication over the subtraction and return a difference
					first_arg_pos_terms := if is_string(a12) or alg_of(a12(1)) /= plus_op or alg_role_of(a12(1)) /= "+"
										then [a12] else a12(2..) end if;
					first_arg_neg_terms := if is_string(a13) or alg_of(a13(1)) /= plus_op or alg_role_of(a13(1)) /= "+"
										then [a13] else a13(2..) end if;
										
--print("first arg of a product is a difference, second arg is non-algebraic: a1: ",unparse(a1)," a2: ",unparse(a2)," first_dist: ",unparse(distribute_mult(first_arg_pos_terms,[a2],n1))," second_dist: ",unparse(distribute_mult(first_arg_neg_terms,[a2],n1)));
					return [a1(1),distribute_mult(first_arg_pos_terms,[a2],n1), distribute_mult(first_arg_neg_terms,[a2],n1)];
								-- distribute the multiplications and return a difference			
								-- distribute_mult expects to be passed tuples of sums to be multiplied out
				when "0" => 				-- second op is zero
					return zero;
			
				when "1" => 				-- second op is unity
					return a1;
			
				when "+" => 				-- second op is a flattened sum
					return [a1(1),distribute_mult(if is_string(a12) or alg_of(a12(1)) /= plus_op  or alg_role_of(a12(1)) /= "+"
										then [a12] else a12(2..) end if,[a2],n1),
						distribute_mult(if is_string(a13)  or alg_of(a13(1)) /= plus_op  or alg_role_of(a13(1)) /= "+"
											then [a13] else a13(2..) end if,[a2],n1)];			-- distribute the subtraction			
			
				when "-" =>  				-- second op is a difference; product of two differences
					[-,a12,a13] := a1; 	-- decompose first op
					a12ta22 := distribute_mult(if is_string(a12) or alg_of(a12(1)) /= plus_op  or alg_role_of(a12(1)) /= "+"
							then [a12] else a12(2..) end if,if is_string(a22) or alg_of(a22(1)) /= plus_op then [a22] else a22(2..) end if,n1);
					a13ta23 := distribute_mult(if is_string(a13) or alg_of(a13(1)) /= plus_op  or alg_role_of(a13(1)) /= "+"
							then [a13] else a13(2..) end if,if is_string(a23) or alg_of(a23(1)) /= plus_op then [a23] else a23(2..) end if,n1);
					pos_part := conc_em(a12ta22,a13ta23,ao);
					a12ta23 := distribute_mult(if is_string(a12) or alg_of(a12(1)) /= plus_op  or alg_role_of(a12(1)) /= "+"
							then [a12] else a12(2..) end if,if is_string(a23) or alg_of(a23(1)) /= plus_op then [a23] else a23(2..) end if,n1);
					a13ta22 := distribute_mult(if is_string(a13) or alg_of(a13(1)) /= plus_op  or alg_role_of(a13(1)) /= "+"
								then [a13] else a13(2..) end if,if is_string(a22) or alg_of(a22(1)) /= plus_op then [a22] else a22(2..) end if,n1);
					neg_part := conc_em(a12ta23,a13ta23,ao);
					return [a1(1),pos_part,neg_part];
			
				when "--" =>   			-- second op is a reversal
					[-,a12,a13] := a1; 	-- decompose first op
					pos_prod := distribute_mult(if is_string(a12) or alg_of(a12(1)) /= plus_op  or alg_role_of(a12(1)) /= "+"
								then [a12] else a12(2..) end if,if is_string(a2) or alg_of(a2(1)) /= plus_op then [a2] else a2(2..) end if,n1);
					neg_prod := distribute_mult(if is_string(a13) or alg_of(a13(1)) /= plus_op  or alg_role_of(a13(1)) /= "+"
										then [a13] else a13(2..) end if,if is_string(a2) or alg_of(a2(1)) /= plus_op then [a2] else a2(2..) end if,n1);
	
					return [a1(1),neg_prod,pos_prod];
		
				when "*" =>   	-- second op is a flattened product; prepend the first argument to the second and return as a flattened product
									-- the case here is (a12 - a13) • a2, a2 being a product
					return [a1(1),conc_em(a12,a2,n1),conc_em(a13,a2,n1)];		-- return the distributed difference
--					return something;
				
			end case;
		
			when "--" =>   			-- FIRST OPERAND CASE:  multiplication: first op is a reversal
	
			case second_op_kind
			
				when "OM","+","*" => 				-- second op is non-algebraic (times reversal or sum; handle recursivey)
					unreversed_prod := [n1,a1,a2];
					return ["ast_of",reversal_op,["ast_list",standardize_subtree(unreversed_prod,ao)]];
			
				when "0" => 				-- second op is zero
					return zero;
			
				when "1" => 				-- second op is unity
					return ["ast_of",reversal_op,["ast_list",a1]];		-- restore the initially removed reversal
			
				when "-" =>  				-- second op is a difference
					[r0,r1,r2] := standardize_subtree([n1,a1,a2],ao);
					return [r0,r2,r1];
			
				when "--" =>   			-- second op is a reversal (drop reversals and handle recursively)
					unreversed_prod := [n1,a1,a2];
					return standardize_subtree(unreversed_prod,ao);
				
			end case;
		
			when "*" =>   				-- FIRST OPERAND CASE:  multiplication: first op is a flattened product
	
			case second_op_kind
			
				when "OM" => 				-- second op is non-algebraic
--print("multiplication: first op is a flattened product second op is non-algebraic: ",conc_em(a1,a2,n1));
					return conc_em(a1,a2,n1);		-- concatenate the products
			
				when "0" => 				-- second op is zero
					return zero;
			
				when "1" => 				-- second op is unity
					return a1;
			
				when "+" => 				-- second op is a flattened sum; distribute
					return distribute_mult([a1],a2(2..),n1);
			
				when "-" =>  				-- second op is a difference; distribute over difference
					[sub_op,a22,a23] := a2; 	-- decompose second op
					pos_part := distribute_mult([a1],if is_string (a22) then a22 else a22(2..) end if,n1);
					neg_part := distribute_mult([a1],if is_string (a23) then a23 else a23(2..) end if,n1);
					return [sub_op,pos_part,neg_part];
			
				when "--" =>   			-- second op is a reversal; handle recursively
					unreversed_prod := [n1,a1,a2];
					return ["ast_of",reversal_op,["ast_list",standardize_subtree(unreversed_prod,ao)]];
			
				when "*" =>   				-- second op is a flattened product
--print("product of products: ",a1," ",a2);
					return conc_em(a1,a2,n1);
				
			end case;
		
			otherwise => return tree;
			
		end case;
		
		otherwise => return tree;
	end case;
	
end sstandardize_subtree;
procedure conc_em(a1,a2,conc_op);
		-- concatenates two flattened sequences, at least one of which begins with conc_op
	
	a1 := if is_tuple(a1) and a1(1) = conc_op then a1(2..) else [a1] end if;
	a2 := if is_tuple(a2) and a2(1) = conc_op then a2(2..) else [a2] end if;
	
	return [conc_op] + a1 + a2;
end conc_em;
procedure re_nest(flattened_tree);	-- recursively reconvert a standardized (flattened) tree to its ordinary 'nested' form
--if debug_flag then print("tree to unflatten: ",flattened_tree);	end if;
	if is_string(flattened_tree) then return flattened_tree; end if;
	
	[n1,n2,n3] := flattened_tree;		-- unpack the flattened tree 
	
	op_kind := if n1 = "ast_of" then alg_role_of(n2) else alg_role_of(n1) end if;
--if debug_flag then print("re_nest: ",op_kind," ",n1," ",flattened_tree);	end if;
	if op_kind = OM or op_kind notin {"+","+nc","*","*nc","-","--"} then return flattened_tree; end if;
					-- handle non-algebraic nodes
	
	if op_kind = "--" then  		-- put reversal into standard form, dropping double reversals
		if is_tuple(revarg := n3(2)) and revarg(1) = n1 and revarg(2) = n2 then 			-- double reversal, so drop (recursively)
			return re_nest(revarg(3)(2));
		end if;
		return [n1,n2,["ast_list",re_nest(n3(2))]];
	end if;
	if op_kind = "-" then 					-- flatten the subtraction arguments
		[sub_op,a1,a2] := flattened_tree; return [sub_op,re_nest(a1),re_nest(a2)]; 
	end if;
				-- if the operation is commutative, and is either a multiplication of an addition, sort its arguments
	if op_kind = "+" or op_kind = "*" then 
--if debug_flag then print("flattened_tree before: ",flattened_tree); end if;
		flattened_tree := [n1] + sort_by_stgs([re_nest(comp): comp = flattened_tree(2..)(j)]);
--if debug_flag then print("flattened_tree after: ",flattened_tree); end if;
	else			-- ovoid sorting since not known to be commutative
		flattened_tree := [if j = 1 then comp else re_nest(comp) end if: comp = flattened_tree(j)];		-- re-nest the tree arguments
	end if;
	
	nested := flattened_tree(1..3); for j in [4..#flattened_tree] loop nested := [n1,nested,flattened_tree(j)]; end loop;
--print("re_nesting: ",op_kind," ",n1," ",flattened_tree," ",nested);	
	return nested; 
	
end re_nest;
procedure distribute_mult(tup1,tup2,mult_op);			-- distribute one tuple of sums over another
		-- this routine expects to be passed tuples of sums to be multiplied out
--print("distribute_mult tup1: ",tup1,"  tup2: ",tup2," mult_op: ",mult_op);	
	plus_op := alg_of(mult_op);
--print("unstandardized result: ",[plus_op] + [conc_em(x,y,mult_op): x in tup1,y in tup2]);		
	res := if #tup1 = 1 and #tup2 = 1 then conc_em(tup1(1),tup2(1),mult_op) 		-- if the argumets are both monomials, just multiply them
				-- otherwise mutiply their separate terms and return the sum of these products
--		else re_nest(standardize_subtree(unstand := [plus_op] + [re_nest(conc_em(x,y,mult_op)): x in tup1,y in tup2],plus_op)) end if;
		else re_nest(standardize_subtree(re_nest(unstand := [plus_op] + [re_nest(conc_em(x,y,mult_op)): x in tup1,y in tup2]),plus_op)) end if;
--print("distribute_mult tup1: ",unparse(tup1),"  tup2: ",unparse(tup2)," mult_op: ",mult_op," res: ",unparse(res)," unstand: ",unstand," standardize_subtree(unstand,plus_op): ",standardize_subtree(unstand,plus_op));	
	return res;
	
end distribute_mult;
-- *********************************************************************
-- ********************* Algebraic standardization *********************
-- *********************************************************************
-- If addition, subtraction, and multiplication, and both zero and unit constants are available,
-- we reduce formulae by distributing multiplication over addition and subtraction, until
-- a formula is fully reduced to a sum/difference of products. If multiplication is commutative
-- all the bottom-level products are then rearranged in alphabetical order of their blobs. Pure 
-- nested runs of additions and subtractions are first rearranged in alphabetical order of their blobs
-- so that cancellaion of terms having identical blobs can be performed. These terms are then rearranged
-- in alphabetical order. Hence the standardized form of an algebraic node is either: (i) one of
-- the constants 0 or 1; (ii) a non-algebraic term; (iii) a product of non-algebraic terms,
-- arranged in alphabetical order of their blobs if multiplication is commutaive; 
-- (iv) a sum of such products, arranged in alphabetical order of their blobs; (v) a difference
-- of such sums; or (vi) the reverse of such a sum.
-- Each algebraic node in a tree is associated with an algebraic operator set, which contains at least 
-- an associative addition operator '+', and perhaps also and additive unit '0', an additive inversion
-- operator '-' which can also be used in infix form as a subtraction operator, 
-- a multiplication operator '*' (in which case the addition operator is required to be commutative),
-- and a multiplicative unit constant '1'.  The map alg_of op sends each of these operators into the
-- associated '+' operator (which is always used as the representative of the algebraic operator set
-- to which it belongs). The map alg_role_of(op_sign), whose value can be '0', '1', '+', 
-- '+nc' (non-commutative addition), '-', '--' (additive inverse), '*' (multiplication), or 
-- '*nc' (non-commutative multiplication).
-- (additive inverse), 
procedure post_alg_roles(stg);		-- adds an algebraic set to alg_of and alg_role_of maps
									-- also builds the auxiliary maps sub_from_addn,reversal_from_addn
									-- the string contains the comma-separated operator names in 
									-- any order, each followed by its role
-- The string supplied should consit of ring operator names in alll the odd positions, followed by standard names
-- for the algebraic roles of each operator and constant in the following even position. The standard 
-- operator names to be used are + - -- (monadic minus) *; the standard constant signs are 0 1
-- an example is post_alg_roles("•S_PLUS,+,•S_ZERO,0,•S_ONE,1,•S_MINUS,-,•S_REV,--,•S_TIMES,*,Za,ring");
	data := breakup(stg,",;");		-- cut the string into a tuple
				-- check that the string supplied has an even number of components and contains a plus operator
				-- and has ony known standard operator signs in its even positions
	if (not even(#data)) or (not (exists plusop = data(j) | (odd(j) and data(j + 1) = "+"))) or 
				(exists opsign = data(j) | (even(j) and data(j) notin {"0","1","+","+nc","-","--","*","*nc","OM","ring"})) then 
		print("******* Error: algebraic operator set declaration ",stg," is faulty"); return;
	end if;
	
	for opsign = data(j) | odd(j) loop		-- iterate over the tuple, noting the algebraic role of each operator sign supplied
		alg_of(opsign) := plusop;						-- maps all algebraic operators into the plus operator of their ring
		alg_role_of(opsign) := djp := data(j + 1);  	-- but then correct
					-- extend supplementary maps sub_from_addn, reversal_from_addn, zero_from_addn, one_from_addn, mult_from_addn, etc.
		if djp = "-" then sub_from_addn(plusop) := opsign; elseif djp = "--" then reversal_from_addn(plusop) := opsign; end if;
		if djp = "0" then zero_from_addn(plusop) := opsign; end if;
		if djp = "1" then one_from_addn(plusop) := opsign; end if;
		if djp = "*" then mult_from_addn(plusop) := opsign; end if;
		if djp = "ring" then ring_from_addn(plusop) := opsign; end if;
		
	end loop;
--print("alg_of: ",alg_of," ",alg_role_of," ",zero_from_addn);
	
end post_alg_roles;
procedure find_ring_members(tree);	-- collects the ring membership assertions at the top conjunctive level of a tree
	
	var memb_assertions := 
		{[x,{["ast_in",y,ring := ring_from_addn(x)],["ast_in",zero_from_addn(x),ring]}]: [x,y] in one_from_addn};
					-- intialize to known members (0 and 1) this maps each known addition operator for a ring R into a pair of assertions
					-- of the abstract form {'0 in R', '1 in R'}
		-- recursive workhorse will collect and arrange by algebraic set
	
	find_ring_members_in(tree);		-- collects the ring membership assertions by recursive treewalk
--print("find_ring_members return: ",memb_assertions);
	return memb_assertions;			-- return the full set of ring membership assertions found at top level
	
	procedure find_ring_members_in(tree);		-- collects the ring membership assertions
		if is_string(tree) then return; end if;		-- nothing to do if string
		
		[n1,n2,n3] := tree;							-- unpack tree
				
				-- process top-level conjuncts by walkig recursively dwn the lis of conjuctions at the top level
		if n1 = "ast_and" or n1 = "AMP_" then find_ring_members_in(n2); find_ring_members_in(n3); return; end if;
	
--print("find_ring_members_in: ",tree," alg_of(n3): ",alg_of(n3));				
					-- otherwise, if we have a membership statement in a node whose opearator is algebraic, then collect it
		if n1 = "ast_in" and (plus_op := alg_of(n3)) /= OM and n3 = ring_from_addn(plus_op) then
						-- we have a conjunct of the form x in R; collect this ring membership assertion
			memb_assertions(plus_op) := (memb_assertions(plus_op)?{}) with tree;	
		end if;
				
	end find_ring_members_in;
end find_ring_members;
--					********************************************************************************
--					*********** Miscellaneous tree searching and transformation routines ***********
--					********************************************************************************
	procedure find_fnc_symbs(node); 		-- find the function symbols in a tree (main entry)
		all_fnc_symbs := {}; find_fnc_symbs_in(node);
		return all_fnc_symbs - builtin_fcns;			-- use the recursive workhorse and a global variable
	end find_fnc_symbs;
	
	procedure find_fnc_symbs_in(node); 		-- find the function symbols in a tree (recursive workhorse)
--print("find_fnc_symbs_in: ",node," ",is_string(node)," ",all_fnc_symbs);
		if is_string(node) then return;  end if; 			-- down to a variable or constant
		case (ah := abbreviated_headers(n1 := node(1)))?n1
			when "()" => 				-- this is the case of functional and predicate application; the second variable is a reserved symbol, not a set
				all_fnc_symbs with:= node(2); for sn in node(3..) loop find_fnc_symbs_in(sn); end loop;
			when "and","or","==","+","-","{-}","in","notin","/==","=","/=","[]","[-]","ast_if_expr","ast_if", 
						"{.}","itr","Etr","incs","incin","imp","*","->","not","null","{}","{/}","EX","ALL" =>
						-- various builtins
				for sn in node(2..) loop find_fnc_symbs_in(sn); end loop;
				-- now collect free variables in args
			otherwise => 		-- additional infix, prefix, and ordinary operators; functional application
				all_fnc_symbs with:= node(if #node = 2 then 1 else 1 end if); 
				for sn in node(2..) loop find_fnc_symbs_in(sn); end loop; 
						-- now collect free variables in args
		
		end case;
		
	end find_fnc_symbs_in;
	procedure dequantify(tree,substitution_tup);
	 	-- makes substitutions for specified quantified variables of a formula. (main entry; uses workhorse)
		-- this routine works from left-to right through a quantified formula, progressively using elements from the 
		-- indicated tuple to make day_total for the bound variables encountered. It descends thru quantifiers,
		-- the boolean operators and, or, not, and •imp (but not equivalences), keeping track of the boolean sign
		-- of the terms encountered, and replacing quantified variables where possible. day_total of non-strings
		-- for existential quantified variables are refused.
		-- A triple [substituted_formula,set_of_strings_skolemized,unused_] is returned.
		-- non-empty unused_substitutions list probably represents an error.
		
		-- to allow specified quantifiers or setformers which would otherwise attract substitutions to be bypassed
		-- selectively, we allow OM elements to be included in the susbstitution tup passed to this routine.
		-- when such an OM is matched to a substitution-candidate explciitly or implicitly quantified subexpression,
		-- it terminates substitution, either before it started or at the quantifier reached.  
		return dequantify_in(tree,substitution_tup,{},true,{}); print("returned: ",res); 
		 			-- call the workhorse with an initially null list of bound variables
	end dequantify;
	procedure dequantify_in(tree,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);	 
--print("dequantify_in_trace: ",unparse(tree));
res := dequantify_in_trace(tree,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);	
--print("returned: ",res); 
return res;
	end dequantify_in;
	procedure dequantify_in_trace(tree,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
	 	-- inner recursive workhorse of substitution routine. substitution_tup is passed down from the initial
	 	-- call, with progressive deletion of the elements of substitution_tup as they are used and 
	 	-- pass into substitution_map, which is used so that every occurrence of a bound variable
		-- will be replaced by its assigned expression, if it has not been bypassed  
		if is_string(tree) and tree in bound_vars then 	-- replace bottom-level names as specified
				return substitution_map(tree)?tree; 
		end if;
				
		[n1,n2,n3] := tree;			-- unpack the node
		
		case abbreviated_headers(n1)?n1		-- handle quantifiers and setformers in a special way, to detect bound variables 
			when "and","or" => 	-- 'positive' boolean operators
				[n2,skolemized_in_a1,substitution_tup] := 
					dequantify_in(n2,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);		-- continue recursively
				[n3,skolemized_in_a2,substitution_tup] := 	
					dequantify_in(n3,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);		-- continue recursively
				return [[n1,n2,n3],skolemized_in_a1 + skolemized_in_a2,substitution_tup];
						-- since substitutions may have been done in either of the arguments, substitution_tup may have shortened
 
 			when "not" => 		-- 'negative' boolean operator
				[n2,skolemized_in_a1,substitution_tup] := 		-- continue recursively, reversing the virtual sign of the subformula
					dequantify_in(n2,substitution_tup,substitution_map,not bool_sign,bound_vars_not_replaced);
	
				return [[n1,n2],skolemized_in_a1,substitution_tup];
						-- since substitutions may have been done in either of the arguments, substitution_tup may have shortened
			when "imp" => 		-- 'mixed' boolean operator; this is like 'or', but the first argument reverses sign
				[n2,skolemized_in_a1,substitution_tup] := 
					dequantify_in(n2,substitution_tup,substitution_map,not bool_sign,bound_vars_not_replaced);		-- continue recursively
--print("imp: ",n3," substitution_tup: ",substitution_tup," substitution_map: ",substitution_map);
				[n3,skolemized_in_a2,substitution_tup] := 	
					dequantify_in(n3,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);		-- continue recursively
				return [[n1,n2,n3],skolemized_in_a1 + skolemized_in_a2,substitution_tup];
						-- since substitutions may have been done in either of the arguments, substitution_tup may have shortened
			when "EX" => 		-- existential. The second node is an iterator
				-- a syntactic example is: ["ast_iter_list", ["ast_in", "X", "S"], ["DOT_INCIN", "Y", "T"]].
				
				[n1,n2,n3] := tree;		-- unpack the parts of the syntax node
	 				
				limiting_clauses := [];		-- the limiting_clauses will be conjoined into a final conjunction, which will appear in 
											-- result formula as (clause1 & clause2 & ...) •imp substituted_body
				leftover_iteration_clauses := [];		-- to accumulate iteration_clauses whose bound variable is not replaced
				
				iter_tag := n2(1); 			-- get the list tag for the iterator
				replacement_stopped := false;		-- flag indicating wheter replacement was stopped either explicitly or implicitly
				if not bool_sign then
							-- the sign has reversed, so this is an implicit universal. all substitutions will be accepted
					
	 				for [iter_op,bv,limiting_expn] in n2(2..) loop		-- process all the clauses of the iterator
	 
	 					if substitution_tup /= [] and not replacement_stopped then		-- there are still more substitutions available, so make one more
	 	
	 						limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
	 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
							
							replacement_expression fromb substitution_tup;		-- get the next replacement expression to use
		
							if replacement_expression = OM then		-- replacement is being stopped manually 
							
								replacement_stopped := true;	-- only prior replacements will be used
								substitution_tup := [replacement_expression] + substitution_tup;	-- return the unused replacement expression
								
							else	-- note the replacement to be made henceforth
					
								substitution_map(bv) := replacement_expression;		-- this will replace the bound variable henceforth
	
							end if;
							
							limiting_clauses with:= [iter_op,replacement_expression,limiting_expn];		-- in the new clause, the bound variable is replaced
										-- accumulate the substituted limiting_clause, for later use, as explained above
	
						else			-- there are no more substitutions available or replacement has been stopped, 
										-- so the remaining pieces of the iterator  are  'leftovers'
	
							limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
	 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
							leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];	-- the substituted clause will be used in a quantifier
	
						end if;
		
					end loop;		-- once all the clauses of the iterator have been processed, we process the quantified predicate
									-- this is the existential case, reversed
	 
	 				bound_vars_not_replaced +:= {b: [op,bv,limit] in leftover_iteration_clauses};
							-- there may be additional bound variables not being replaced
	 				
	 				if leftover_iteration_clauses = [] then		-- just return the substituted body as an implication involving all the limiting_clauses 
 		 			
	 					[n3,skolemized_in_pred,substitution_tup] := dequantify_in(n3,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
	 							-- make substitutions in the predicate
				 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
				 		return [if limiting_clauses = [] then n3 else ["ast_and",conj,n3] end if,skolemized_in_pred,substitution_tup];			-- return the said implication
	 					 
	 				else 		-- return the substituted body as a quantified implication involving all the limiting_clauses
	 	
	 					n3 := substitute_in(n3,substitution_map,bound_vars_not_replaced); -- make a simple substitution
	 					n3 := [n1,[iter_tag] + leftover_iteration_clauses,n3];			-- reattach the quantifier and remaining iterators
	 					
				 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
				 		return [if limiting_clauses = [] then n3 else ["ast_and",conj,n3] end if,[],substitution_tup];			-- return the said implication. nothing is skolemized
	 					
	 				end if;
	 
	 			else		-- this is really an existential, so only constant substitutions will be accepted. These will be
							-- noted as having been skolemized, i.e. they receive their definitions by replacing bound variables in an existential
			
					skolemized_here := [];		-- start collecting variables which are skolemized by this existential
			
	 				for [iter_op,bv,limiting_expn] in n2(2..) loop		-- process the clauses of the iterator until the first that gives trouble
	 
	 					if substitution_tup /= [] and not replacement_stopped then
			-- there are still more substitutions available, so see if we can make one more
	 	
	 						limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
	 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
	
							replacement_expression fromb substitution_tup;		-- get the next replacement expression to use
	
							if replacement_expression = OM or not is_string(replacement_expression) then
								-- replacement is being stopped manually or implicitly
--print("really an existential replacement_stopped:");							
								replacement_stopped := true;	-- only prior replacements will be used
								leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];		-- this iteration clause is bypassed
								substitution_tup := [replacement_expression] + substitution_tup;	-- return the unused replacement expression
		
							else			-- skolemization by this existential is possible
							 
								substitution_map(bv) := replacement_expression;		-- this string will replace the bound variable henceforth
							
								limiting_clauses with:= [iter_op,replacement_expression,limiting_expn];
										-- in the new clause, the bound variable is replaced.
										-- accumulate the substituted limiting_clause, for later use, as explained above
	 							skolemized_here with:= replacement_expression;		-- note that the replacement_expression	(a string)
	 																				-- is being defined here				
	
							end if;
							
						else			-- there are no more susbstitutions available  or replacement has been stopped,
										-- so the remaining pieces of the iterator  are  'leftovers'
	
							limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
	 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
							leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];	-- the substituted clause will be used in a quantifier
	
						end if;
		
					end loop;		-- once all the clauses of the iterator have been processed, we process the quantified predicate
									-- this is the existential case, not reversed
	 				if leftover_iteration_clauses = [] then		-- just return the substituted body as an implication involving all the limiting_clauses 
	 					
		 				[n3,skolemized_in_pred,substitution_tup] := dequantify_in(n3,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
				 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
				 		return [if limiting_clauses = [] then n3 else ["ast_and",conj,n3] end if,skolemized_here + skolemized_in_pred,substitution_tup];			-- return the said implication
	 					 
	 				else 		-- just return the substituted body as a quantified implication involving all the limiting_clauses
	 	
	 					n3 := substitute_in(n3,substitution_map,bound_vars_not_replaced);
	 							-- make substitutions in the predicate. Here, only a simple substitution is made
	 					n3 := [n1,[iter_tag] + leftover_iteration_clauses,n3];			-- reattach the quantifier and remaining iterators
--print("really an existential leftover: ",n3," ",leftover_iteration_clauses);	 					
				 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
				 		return [if limiting_clauses = [] then n3 else ["ast_and",conj,n3] end if,skolemized_here,substitution_tup];
				 					-- return the said implication, noting all  the variables that have been skolemized here and in the predicate
	 					
	 				end if;
				
				end if;
		when "ALL" => 		-- universal. This is similar to the existential case, but with reversal of the case to skolemize 
			
			[n1,n2,n3] := tree;		-- unpack the parts of the syntax node
 				
			limiting_clauses := [];		-- the limiting_clauses will be conjoined into a final conjunction, which will appear in 
										-- result formula as (clause1 & clause2 & ...) •imp substituted_body
			leftover_iteration_clauses := [];		-- to accumulate iteration_clauses whose bound variable is not replaced
			
			iter_tag := n2(1); 			-- get the list tag for the iterator
			replacement_stopped := false;		-- flag indicating whether replacement was stopped either explicitly or implicitly
	
			if bool_sign then
						-- the sign has not reversed, so this is a true universal. all substitutions will be accepted
				
 				for [iter_op,bv,limiting_expn] in n2(2..) loop		-- process all the clauses of the iterator
 
 					if substitution_tup /= [] and not replacement_stopped then		-- there are still more substitutions available, so make one more
 	
 						limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
						
						replacement_expression fromb substitution_tup;		-- get the next replacement expression to use
	
						if replacement_expression = OM then		-- replacement is being stopped manually 
						
							replacement_stopped := true;	-- only prior replacements will be used
							leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];		-- this will not be used, so become a leftover_iteration_clause
--print("replacement_stopped: ");							
						else	-- note the replacement to be made henceforth
				
							substitution_map(bv) := replacement_expression;		-- this will replace the bound variable henceforth
							limiting_clauses with:= [iter_op,replacement_expression,limiting_expn];
									-- in the new clause, the bound variable is replaced
									-- accumulate the substituted limiting_clause, for later use, as explained above
						end if;
					else			-- there are no more susbstitutions available or replacement has been stopped, 
									-- so the remaining pieces of the iterator  are  'leftovers'
						limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
						leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];	-- the substituted clause will be used in a quantifier
					end if;
	
				end loop;		-- once all the clauses of the iterator have been processed, we process the quantified predicate
								-- this is the universal case, not reversed
 
 				bound_vars_not_replaced +:= {b: [op,bv,limit] in leftover_iteration_clauses};
						-- there may be additional bound variables not being  replaced
--print("leftover_iteration_clauses: ",leftover_iteration_clauses);			
  				if leftover_iteration_clauses = [] then		-- just return the substituted body as an implication involving all the limiting_clauses 
 					
					[n3,skolemized_in_pred,substitution_tup] := dequantify_in(n3,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
 							-- make substitutions in the predicate
		 			conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
			 		return [if limiting_clauses = [] then n3 else ["DOT_IMP",conj,n3] end if,skolemized_in_pred,substitution_tup];			-- return the said implication
					 
 				else 		-- return the substituted body as a quantified implication involving all the limiting_clauses
	
 					n3:= substitute_in(n3,substitution_map,bound_vars_not_replaced);
					n3 := [n1,[iter_tag] + leftover_iteration_clauses,n3];			-- re-attach the quantifier and remaining iterators
 					
			 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
			 		return [if limiting_clauses = [] then n3 else ["DOT_IMP",conj,n3] end if,[],substitution_tup];			-- return the said implication. nothing is skolemized
 					
 				end if;
 
			else		-- this is really an implicit existential, so only constant substitutions will be accepted. These will be
						-- skolemized
				
				skolemized_here := [];		-- start collecting variables which are skolemized by this existential
		
 				for [iter_op,bv,limiting_expn] in n2(2..) loop		-- process the clauses of the iterator until the first that gives trouble
 
 					if substitution_tup /= [] and not replacement_stopped then
		-- there are still more sustitutions available, so see if we can make one more
 	
 						limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
						replacement_expression fromb substitution_tup;		-- get the next replacement expression to use
						if replacement_expression = OM or not is_string(replacement_expression) then
							-- replacement is being stopped manually or implicitly
						
							replacement_stopped := true;	-- only prior replacements will be used
							leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];		-- this iteration clause is bypassed
							substitution_tup := [replacement_expression] + substitution_tup;	-- return the unused replacement expression
							
						else			-- skolemization by this existential is possible
						 
							substitution_map(bv) := replacement_expression;		-- this string will replace the bound variable henceforth
						
							limiting_clauses with:= [iter_op,replacement_expression,limiting_expn];
									-- in the new clause, the bound variable is replaced.
									-- accumulate the substituted limiting_clause, for later use, as explained above
 							skolemized_here with:= replacement_expression;		-- note that the replacement_expression	(a string)
 																				-- is being defined here				
						end if;
						
					else			-- there are no more susbstitutions available  or replacement has been stopped,
									-- so the remaining pieces of the iterator  are  'leftovers'
						limiting_expn := substitute_in(limiting_expn,substitution_map,bound_vars_not_replaced);
 							 		-- make the accumulated substitution in the limiting_expn; use the inner workhorse directly
						leftover_iteration_clauses with:= [iter_op,bv,limiting_expn];	-- the substituted clause will be used in a quantifier
					end if;
	
				end loop;		-- once all the clauses of the iterator have been processed, we process the quantified predicate
  				
 				if leftover_iteration_clauses = [] then		-- just return the substituted body as an implication involving all the limiting_clauses 
 					
					[n3,skolemized_in_pred,substitution_tup] := dequantify_in(n3,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
 							-- make substitutions in the predicate
			 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
			 		return [if limiting_clauses = [] then n3 else ["DOT_IMP",conj,n3] end if,skolemized_here + skolemized_in_pred,substitution_tup];			-- return the said implication
 					 
 				else 		-- just return the substituted body as a quantified implication involving all the limiting_clauses
 --print("leftover_iteration_clauses: ",leftover_iteration_clauses);	
 					n3 := [n1,[iter_tag] + leftover_iteration_clauses,n3];			-- reattach the quantifier and remaining iterators
 					
 					n3 := substitute_in(n3,substitution_map,bound_vars_not_replaced);
 							-- make substitutions in the predicate. Here, only a simple substitution is made
			 		conj := conjoin(limiting_clauses); 		-- conjoin a collection of clauses
			 		return [if limiting_clauses = [] then n3 else ["DOT_IMP",conj,n3] end if,skolemized_here,substitution_tup];
			 					-- return the said implication, noting all the variables that have been skolemized here and in the predicate
 					
 				end if;
			
			end if;
			when "in" => 		-- this can only be processed when the second argument is a setformer
				if (n31 := n3(1)) /= "ast_genset" and  n31 /= "ast_genset_noexp" then  -- cannot be dequantified 
						-- just perform a simple substitution, and return the triple 
						-- [substituted_result,nothing_skolemized,remaining_substitution_tup] 
--print("cannot be dequantified: ",tree," substitution_map: ",substitution_map);					
					return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
				
				elseif n31 = "ast_genset" then 				-- we have a set-former with an expression
						-- convert it to an existential, and process as such
						-- here the subnode is of length 4, m2 being the tree for the expr and m3 the iterator
					[m1,m2,m3,m4] := n3;			-- unpack the setformer subnode
					corresponding_existential := ["ast_exists",m3,["ast_and",["ast_eq",n2,m2],m4]];
					return dequantify_in(corresponding_existential,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
							-- continue recursively, treating this as an existential
				
				else  				-- we have a set-former with no expression. 
									-- The membership relation x in {y in u | P(y)} simplifies to (x in u and P(x)), but not here	
					return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
				
				end if;
				
			when "notin" => 	-- this is much like 'in', but with a reversal of sign
				if (n31 := n3(1)) /= "ast_genset" and  n31 /= "ast_genset_noexp" then  -- cannot be dequantified 
						-- just perform a simple substitution, and return the triple 
						-- [substituted_result,nothing_skolemized,remaining_substitution_tup] 
					
					return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
				
				elseif n31 = "ast_genset" then 				-- we have a set-former with an expression
						-- convert it to a related existential, and process as such. we use the 'FORALL ... not ...' form
						-- here the subnode is of length 4, m2 being the tree for the expr and m3 the iterator
					[m1,m2,m3,m4] := n3;			-- unpack the setformer subnode
					corresponding_universal := ["ast_forall",m3,["ast_not",["ast_and",["ast_eq",n2,m2],m4]]];
--print("'notin ast_genset' case: ",corresponding_universal); print(unparse(corresponding_universal));
					return dequantify_in(corresponding_universal,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
							-- continue recursively, treating this as a universal
				
				else  				-- we have a set-former with no expression. 
									-- The membership relation x notin {y in u | P(y)} simplifies to not(x in u and P(x)), but not here	
 
 					return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
				
				end if;
--->working dequantify
			when "incs" =>				-- set inclusion. we handle this in case the second argument is a setformer by rewriting
												-- a incs {e(x): x in s | P(x)} as (FORALL x in s | P(x) •imp (e(x) in a))
												-- and a incs {x in s | P(x)} as (FORALL x in s | P(x) •imp (x in a))
 
				if (n31 := n3(1)) /= "ast_genset" and  n31 /= "ast_genset_noexp" then  -- cannot be dequantified 
						-- just perform a simple substitution, and return the triple 
						-- [substituted_result,nothing_skolemized,remaining_substitution_tup] 
					
					return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
				
				elseif n31 = "ast_genset" then 				-- we have a set-former with an expression; see above
						-- convert it to a universal, and process as such
						-- here the subnode is of length 4, m2 being the tree for the expr and m3 the iterator
					[m1,m2,m3,m4] := n3;			-- unpack the setformer subnode
					corresponding_universal := ["ast_forall",m3,["DOT_IMP",m4,["ast_in",m2,n2]]];
					return dequantify_in(corresponding_universal,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
							-- continue recursively, treating this as a universal
				
				else  				-- we have a set-former with no expression. Treat as described above
					[m1,m2,m3] := n3;			-- unpack the setformer subnode
 
 					the_var := m2(2)(2);			-- get the quantified variable 
					corresponding_universal := ["ast_forall",m2,["DOT_IMP",m3,["ast_in",the_var,n2]]];
					return dequantify_in(corresponding_universal,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
							-- continue recursively, treating this as a universal
				
				end if;
 			when "incin" => 				-- reversed set inclusion. we handle this in case the first argument is a setformer by rewriting
												-- {e(x): x in s | P(x)} •incin a as (FORALL x in s | P(x) •imp (e(x) in a))
												-- and {x in s | P(x)} •incin a as (FORALL x in s | P(x) •imp (x in a))
 
				if (n21 := n2(1)) /= "ast_genset" and  n21 /= "ast_genset_noexp" then  -- cannot be dequantified 
						-- just perform a simple substitution, and return the triple 
						-- [substituted_result,nothing_skolemized,remaining_substitution_tup] 
					
					return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
				
				elseif n21 = "ast_genset" then 				-- we have a set-former with an expression; see above
						-- convert it to a universal, and process as such
						-- here the subnode is of length 4, m2 being the tree for the expr and m3 the iterator
					[m1,m2,m3,m4] := n2;			-- unpack the setformer subnode
					corresponding_universal := ["ast_forall",m3,["DOT_IMP",m4,["ast_in",m2,n3]]];
					return dequantify_in(corresponding_universal,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
							-- continue recursively, treating this as a universal
				
				else  				-- we have a set-former with no expression. Treat as described above
					[m1,m2,m3] := n2;			-- unpack the setformer subnode
 
 					the_var := m2(2)(2);			-- get the quantified variable 
					corresponding_universal := ["ast_forall",m2,["DOT_IMP",m3,["ast_in",the_var,n3]]];
					return dequantify_in(corresponding_universal,substitution_tup,substitution_map,bool_sign,bound_vars_not_replaced);
							-- continue recursively, treating this as a universal
				
				end if;
			when "()" => 	-- predicate or function application
--print("function application: ",tree);
			return [tree(1..2) with substitute_in(tree(3),substitution_map,bound_vars_not_replaced),[],substitution_tup];
		end case;		-- in the remaining cases we just perform a simple substitution, 
						-- and return the triple [substituted_result,nothing_skolemized,remaining_substitution_tup] 
		
		return [[tree(1)] + [substitute_in(x,substitution_map,bound_vars_not_replaced): x in tree(2..)],[],substitution_tup];
		
	end dequantify_in_trace;
 				
 	procedure conjoin(clauses); 			-- conjoin a collection of clauses
		conj := clauses(1); 				-- start building a conjunction
 				
 		for j in [2..#clauses] loop conj := ["ast_and", conj,clauses(j)]; end loop;		-- conjoin the next  clause
 		
 		return conj;
		
	end conjoin;
--->tests
--								****************************************************
--								***************** Test Collection ******************
--								****************************************************
--
procedure test_find_fnc_symbs; 			-- test of the find_fnc_symbs procedure
print("****** TESTS OF FIND_FUNCTION_SYMBOLS PROC ******");
	
	stgs := ["sin(x) + cos(x);",
			"{sin(x) + cos(x): x in f(y) | P(x,y)};",
			"{x in f(y) | P(x,y)};",
			"(EXISTS x in f(y)~[g(y,h(y))] | P(x,y));",
			"{{x},{{x},{{y},y}}};"];
	
	for stg in stgs loop
		print(); tree := parze_expr(stg); print(find_fnc_symbs(tree(2)));
	end loop; 
	
end test_find_fnc_symbs;
procedure test_dequantify(); 			-- test of the dequantify procedure
print("****** TESTS OF DEQUANTIFY PROC ******");
	-- the tuples used for testing consist of a formula to be dequantified, 
	-- followed by a string of tems to be substituted for the quantified variables in the first formula.
	
	tups := [["not ((EXISTS x in s | (FORALL y in s | P(x,y))) •imp (FORALL y in s | (EXISTS x in s | P(x,y))));","a;","","b;"],
			["(not (((A in s) and (FORALL Y in s | P(A,Y))) •imp ((B in s) •imp (EXISTS X in s | P(X,B)))));","b;","a;"],
			["not ((FORALL x in s | (FORALL y in s | P(x,y))) •imp (FORALL y in s | (FORALL x in s | P(x,y))));","","a;","b;"],
			["not ((FORALL X in S | (FORALL Y in S | P(X,Y))) •imp ((A in S) •imp ((B in S) •imp P(B,A))));","b;","a;"],
--			["(not (((FORALL Y in s | P(A,Y))) •imp ((EXISTS X in s | P(X,B)))));","b;","a;"],
--			["(FORALL x in u | P(x));","g(c);"],
--			["(EXISTS x in u, y in v(x) | P(x,y)) or (FORALL x in u | R(x));","cc;","","dd;"],
--			["(FORALL x in u, y in v(x) | P(x,y)) or (FORALL x in u | R(x));","H(u);","","G(u);"],
--			["(EXISTS y in u | (FORALL X in S | P(x,y)));","cc;","G(u);"],
--			["(EXISTS y in u | (FORALL X in S | P(x,y)));","cc;"],
--			["(EXISTS y in u | (FORALL X in S | P(x,y))) or (FORALL X in S | Q(x));","G(u);"],
--			["(FORALL X in S | (P(X) •imp (E(X) in A)));","G(u);"],
--			["a incs {e(x,y): x in  s,y in t | P(x,y)};","G(u);","H(u);"],
--			["a incs {e(x,y): x in  s,y in t | P(x,y)};","G(u);"],
--			["a incs {x in  s| P(x)};","G(u);"],
--			["{e(x,y): x in  s,y in t | P(x,y)} •incin a;","G(u);","H(u);"],
--			["{e(x,y): x in  s,y in t | P(x,y)} •incin a;","G(u);"],
--			["{x in  s| P(x)} •incin a;","G(u);"],
--			["(FORALL Y in S | (not (X = E(Y) and P(Y))));","G(u);"],
--			["(EXISTS x in u | P(x));","G(u);"],
--			["(FORALL x in u, y in v | P(x,y));","cc;"],
--			["(FORALL x in u, y in v | P(x,y));","cc;","dd;"],
--			["(FORALL x in u, y in v(x) | P(x,y));","cc;","G(u);"],
--			["(FORALL x in u, y in v(x) | P(x,y));","H(u);","G(u);"],
--			["(FORALL x in u | (EXISTS y in v(x) | P(x,y)));","H(u);","cc;"],
--			["(FORALL x in u | (EXISTS y in v(x) | P(x,y)));","H(u);","G(u);"],
--			["(EXISTS x in u | P(x));","G(u);"],
--			["(EXISTS x in u, y in v | P(x,y));","cc;"],
--			["(EXISTS x in u, y in v | P(x,y));","cc;","dd;"],
--			["(EXISTS x in u, y in v | P(x,y));","cc;","G(u);"],
--			["(EXISTS x in u, y in v | P(x,y));","H(u);","G(u);"],
--			["not (FORALL x in u | P(x));","dd;"],
--			["not (EXISTS x in u | P(x));","G(u);"],
--			["(FORALL x in u | P(x)) and (FORALL x in u | Q(x));","G(u);","H(u);"],
--			["(FORALL x in u | P(x)) or (FORALL x in u | Q(x));","G(u);","H(u);"],
--			["(EXISTS x in u | P(x)) •imp (FORALL x in u | Q(x));","G(u);","H(u);"],
--			["x in {y in s | P(y)};","cc;"],
--			["x notin {y in s | P(y)};","H(u);"],
--			["x in {e(y): y in s | P(y)};","cc;"],
--			["x notin {e(y): y in s | P(y)};","H(u);"],
			["(EXISTS x in u | P(x));","cc;"]];
	
	for tup in tups loop
		print(); tree := parze_expr(tup(1))(2); substitution_tup := [if f = "" then OM else parze_expr(f)(2) end if: f in tup(2..)];
		[formula,skolemized,unused] := dequantify(tree,substitution_tup); 
		print("dequantified formula is: ",unparse(formula)); 
		print("variables skolemized are: ",skolemized); 
		print("unused replacements are: ",[unparse(r): r in unused]);
	end loop;
	
end test_dequantify;
procedure test_monotone_infer; 	 		-- test of the monotone_infer procedure
--->tests2
print("****** TESTS OF NEW MONOTONE INFERENCE PROC ******");
	post_monotone("Un,++;Pow,+;#,+;•PLUS,+,+;•TIMES,+,+;Finite,-;Is_map,--;One_1_map,-");
			 -- note monotonicity properties of builtins
	forms := ["a + b = b + a;",
			"(FORALL u in domain(f) | #u in x2) •imp (FORALL u in #domain(f) | #u in x2);",
--			"{[x,f(x)]: x in s | x in {c}} = {[x,f(x)]: x in s * {c}};",
--			"a * b = b * a;",
--			"if true then a + b else c end if = b + a;",
--			"Un(a + b) = Un(b + a);",
--			"pow(Un(a + b)) = pow(Un(b + a));",
--			"#pow(Un(a + b)) = #pow(Un(b + a));",
--			"#pow(Un(a + b)) = #pow(b + a);",
--			"a + b incs a;",
--			"#(a + b) incs #a;",
--			"#pow((a + b)) incs #pow(a);",
--			"#pow(Un(a + b)) incs #pow(Un(a));",
--			"#pow(Un(a + b)) incs #pow(Un(c));",
--			"Un(a + b) = Un(a) +  Un(b);",
--			"Un(a) +  Un(b) = Un(a + b);",
--			"Un(Un(a + b)) = Un(Un(a)) +  Un(Un(b));",
--			"Un(Un(a) + Un(b)) = Un(Un(a)) +  Un(Un(b));",
--			"pow(Un(a + b)) = pow(Un(a)) +  pow(Un(b));",
--			"{e(x): x in a + b | P(x)} = {e(x): x in b + a | P(x)};",
--			"{e(x,x2): x in a + b, x2 in f(x) | P(x,x2)} = {e(y,y2): y in b + a, y2 in f(y) | P(y,y2)};",
--			"{e(x): x •incin (a + b) | P(x)} = {e(x): x in pow(b + a) | P(x)};",
--			"{e(x): x •incin (a + b) | P(x)} = {e(x): x in pow(b + a)};",
--			"{e(x): x •incin (a + b) | true} = {e(x): x in pow(b + a)};",
--			"{e(x): x •incin (a * b) | true} = {e(x): x in pow(b * a)};",
--			"{x: x •incin (a * b)} = {x in pow(b * a) | true};",
--			"{x in pow(b * a) | true} = {x: x •incin (a * b)};",
--			"{x in pow(b * a + d * c) | true} = {x: x •incin (a * b + c * d)};",
--			"{e(x): x in a | P(x)} + {e(x): x in a | Q(x)} = {e(x): x in a | Q(x) or P(x)};",
--			"{e(x): x in a | P(x)} + {e(x): x in b | P(x)} = {e(x): x in a + b  | P(x)};",
--			"Un(a + b) = c +  d;",
--			"(EXISTS x in a + b | P(x)) •eq (EXISTS x in b + a | P(x));",
--			"((EXISTS x in a | P(x)) or (EXISTS x in b | P(x))) •eq (EXISTS x in b + a | P(x));",
--			"((EXISTS x in a | P(x)) or (EXISTS x in a | Q(x))) •eq (EXISTS x in a | P(x) or Q(x));",
--			"((FORALL x in a | P(x)) and (FORALL x in a | Q(x))) •eq (FORALL x in a | P(x) and Q(x));",
--			"((FORALL x in a | P(x)) and (FORALL x in b | P(x))) •eq (FORALL x in b + a | P(x));",
--			"Un(Un(a + b)) = Un(Un(a) + Un(b));",
--			"pow(Un(a)) +  pow(Un(b)) incs pow(Un(a + b));",
--			"{e(x): x in a + b | P(x)} = {ee(x): x in b + a | P(x)};",
--			"{g(x) * h(x): x in pow(b * a + d * c) | true} = {h(x) * g(x): x •incin (a * b + c * d)};",
--			"{e(x): x •incin (a + b) | P(x)} incs {e(x): x in pow(b) | P(x)};",
--			"{e(x): x •incin (a + b) | P(x)} incs {e(x): x in pow(c) | P(x)};",
--			"Un((a + b) + c) incs Un(c) + Un(b);",
--			"(Un(c) + Un(b)) •incin Un((a + b) + c);",
--			"{e(x): x in (a + (b + c)) | P(x)} incs {e(x): x in c | P(x)} + {e(x): x in b | P(x)};",
--			"{e(x): x in (a + (b + c)) | P(x)} incs {e(x): x in a | P(x)} + ({e(x): x in b | P(x)} + {e(x): x in c | P(x)});",
--			"{e(x): x in (b + (a + c)) | P(x)} incs {e(x): x in c | P(x)} + {e(x): x in b | P(x)};",
--			"(a or b) •imp c;",
--			"(a and b) •imp c;",
--			"(a and b) •imp a;",
--			"if (a and b) •imp a then c else d end if •imp c;",
--			"if (a and b) •imp a then c else d end if •imp d;",
--			"if a = a then c else d end if •imp c;",
--			"if a = a then c else d end if •imp d;",
--			"(a and b) •imp a;",
--			"((a and b) or (c and d)) •imp (a or c);",
--			"((a and b) and (c and d)) •imp (a and c);",
--			"((not(a and c)) •imp (not((a and b) and (c and d))));",
--			"(FORALL x in b + a | P(x)) •imp (FORALL x in a | P(x));",
--			"(FORALL x in b + a | P(x) and Q(x)) •imp (FORALL x in a | P(x));",
--			"(FORALL x in b + a | P(x)) •imp (FORALL x in a | P(x) or Q(x));",
--			"(EXISTS x in a | P(x)) •imp (EXISTS x in b + a | P(x) or Q(x));",
--			"(EXISTS x in b + a | P(x) or Q(x)) •imp ((EXISTS x in b | P(x) or Q(x)) or (EXISTS x in a | P(x) or Q(x)));",
--			"(EXISTS x in a | P(x) or Q(x)) •imp ((EXISTS x in a | P(x)) or (EXISTS x in a | Q(x)));",
--			"((EXISTS x in b | P(x) or Q(x)) or (EXISTS x in a | P(x) or Q(x))) •imp (EXISTS x in b + a | P(x) or Q(x));",
--			"((EXISTS x in a | P(x)) or (EXISTS x in a | Q(x))) •imp (EXISTS x in a | P(x) or Q(x));",
--			"(FORALL x in b + a | P(x) and Q(x)) •imp ((FORALL x in b | P(x) and Q(x)) and (FORALL x in a | P(x) and Q(x)));",
--			"(FORALL x in a | P(x) and Q(x)) •imp ((FORALL x in a | P(x)) and (FORALL x in a | Q(x)));",
--			"((FORALL x in b | P(x) and Q(x)) and (FORALL x in a | P(x) and Q(x))) •imp (FORALL x in b + a | P(x) and Q(x));",
--			"((FORALL x in a | P(x)) and (FORALL x in a | Q(x))) •imp (FORALL x in a | P(x) and Q(x));",
--			"{cdr(u): u in {[x,y],[zz,w]} | car(u) in {x}} = {cdr(u): u in {[x,y]} | car(u) in {x}} + {cdr(u): u in {[zz,w]} | car(u) in {x}};",
--			"{e(x,y): x in g, y in (f + ff) | P(x,y)} = {e(x,y): x in g, y in f | P(x,y)} +  {e(x,y): x in g, y in ff | P(x,y)};",
--			"{if x = a then b elseif x = b then a else x end if: x in s} incs {if x = a then b elseif x = b then a else x end if: x in {a,b,c}};",
--			"{E(X,Y): X in G, Y in F | P(X,Y)} = {E(X,Y): X in G, Y in F | P(X,Y)};",
--			"({[X,Y]: X in A, Y in C} + {[X,Y]: X in B, Y in C}) = ({[X,Y]: X in A, Y in C} + {[X,Y]: X in B, Y in C});",
--			"{[x,y]: x in a, y in c} + {[x,y]: x in b, y in c} = {[x,y]: x in a + b, y in c};",
--			"(s - {orden(y): y in o2 + a}) •incin (s - {orden(y): y in o2});",
--			"{[[car(u),car(cdr(u))],cdr(cdr(u))]: u in {[x,[y,0]]: x in n, y in m} + {[x,[y,1]]: x in n, y in k}} =  {[[car(u),car(cdr(u))],cdr(cdr(u))]: u in {[x,[y,0]]: x in n, y in m}} + {[[car(u),car(cdr(u))],cdr(cdr(u))]: u in {[x,[y,1]]: x in n, y in k}};",
--			"{e(u): u in {f(x,y): x in n, y in m} + {f(x,y): x in n, y in k}} =  {e(u): u in {f(x,y): x in n, y in m}} + {e(u): u in {f(x,y): x in n, y in k}};",
--			"{cdr(u): u in {[x,y]} + {[zz,w]} | car(u) in {x}} =    	{cdr(u): u in {[x,y]} | car(u) in {x}} + {cdr(u): u in {[zz,w]} | car(u) in {x}};",
--			"Finite(a + b) •imp Finite(a);",
--			"Finite(#(a + b)) •imp Finite(#a);",
--			"Is_map(a + b) •imp Is_map(a);",
--			"One_1_map(a + b) •imp One_1_map(a);",
			"a = a;"];
	
	for form in forms loop
		print(); 
--		print("Tree returned: ",monotone_infer(parze_expr(form)(2)));
		print(unparse(monotone_infer(parze_expr(form)(2))));
	end loop; 
	
end test_monotone_infer;
procedure test_replace_fcn_symbol; 	 		-- test of the replace_fcn_symbol procedure
	
	quads := [["hsko(s,t)~[s];","H_THRYVAR","U,V","hsko(u,v)~[u];"],
		["(h_thryvar(s,t) /= f({g(h_thryvar(y,t),y,s,t): y •incin s | (y /= s) & P(h_thryvar(y,t),y,s,t)},s,t)) & (hsko(s,t)~[s] /= f({g(hsko(y,t)~[y],y,s,t): y •incin s | (y /= s) & P(hsko(y,t)~[y],y,s,t)},s,t));","H_THRYVAR","U,V","hsko(u,v)~[u];"],
--	 ["pow(Un(a));","ast_pow","X","{u: u •incin x};"],  
--				["pow(Un(a));","UN","X","{u: v in x, u in v};"],
--				["Un(Un(a));","UN","X","{u: v in x, u in v};"],
--				["if Un(a) /= 0 then pow(Un(a)) else Un(Un(a)) end if ;","UN","X","{u: v in x, u in v};"],
--				["range(Un(a));","ast_range","X","{cdr(u): u in x};"],
--				["range(Un(range(a)));","ast_range","X","{cdr(u): u in x};"],
--				["{Un(a),Un(Un(a))};","UN","X","{u: v in x, u in v};"],
--				["{Un(a),Un(b)};","UN","X","{u: v in x, u in v};"],
--				["[Un(a),Un(Un(a))];","UN","X","{u: v in x, u in v};"],
--				["{Un(a): a in pow(Un(b)) | Un(Un(a)) = 0};","UN","X","{u: v in x, u in v};"],
--				["{a in pow(Un(b)) | Un(Un(a)) = 0};","UN","X","{u: v in x, u in v};"],
--				["(FORALL a in pow(Un(b)) | Un(Un(a)) = 0);","UN","X","{u: v in x, u in v};"],
--				["(EXISTS a in pow(Un(b)) | Un(Un(a)) = 0) or (FORALL a in pow(Un(b)) | Un(Un(a)) = 0);","UN","X","{u: v in x, u in v};"],
--				["a •MINUS b;","DOT_MINUS","X,Y","#(X - Y);"],
--				["f~[b];","TILDE_","F,X","cdr(arb({y: y in F •ON {X}}));"],
--			["arb({{c},{{c},{{d},d}}}) = arb([c,d]);","ast_enum_tup","X,Y","{{X},{{X},{{Y},Y}}};"],
--			["[c,d];","ast_enum_tup","X,Y","{{X},{{X},{{Y},Y}}};"],
--			["[Un(c),Un(d)];","UN","X","{u: v in x,u in v};"],
			["Un(f(Un(a)));","UN","X","{u: v in x,u in v};"]];
	for [form,symb,symbargs,symbdef] in quads loop
		print(); 
		print(unparse(replace_fcn_symbol(parze_expr(form)(2),symb,breakup(symbargs,","),parze_expr(symbdef)(2))));
	end loop; 
	
end test_replace_fcn_symbol;
procedure replace_fcn_symbol(expn,symbol,symbvars,symbdef);
		-- at each occurrence of a symbol in expn, finds the arguments of symbol,
		-- and substitutes them in symbdef for the indicated free variables of symbdef
		-- replacing the symbol occurrence by this substituted form 
		-- but if symbvars = OM, in which case the definition should a simple string, occurrences of
		-- 'symbol' are replaced by occurrences of 'symbdef'
		if is_string(expn) then return if expn = symbol then symbdef else expn end if; end if; 
				-- conditionally replace variables found at bottom level
--print("replace_fcn_symbol: ",expn);		
		[n1,n2,n3,n4] := expn;			-- otherwise unpack top level of tree
		
		case n1
			
			when "ast_of" => 			-- function application 
				
				arglist := [if j = 1 then arg else replace_fcn_symbol(arg,symbol,symbvars,symbdef) end if: arg = n3(j)];
							-- make substitutions within them 
				if n2 = symbol then 		-- we have occurrence of a significant symbol
--print("
replace_fcn_symbol: symbol is: ",symbol," symbvars: ",symbvars?"OM"," symbol definition is: ",unicode_unparse(symbdef)," replacement takes place in expression: ",unicode_unparse(expn)," node tag ",expn(1)," opname = symbol",expn(2) = symbol,"
");
					
--print("
now testing symbvars: ",symbvars = OM," ",symbvars?"UNDEfined");
					if symbvars = OM then 	-- just replace 'symbol' by 'symbdef' and keep the same argument list
--print("
returning substituted: ",unicode_unparse([n1,symbdef,arglist]));
						return [n1,symbdef,arglist]; 
					end if;
					
					if n3 = OM or #symbvars /= #(n3(2..)) then	
--print("[n1,n2,n3,n4]: ",[n1,n2,n3,n4]," ",symbvars);				
							-- mismatch between number of args and number of parameters supplied
						mismatched_args := true;			-- keep record of the offense
						mismatched_symbvars := symbvars;
						mismatched_expn := unparse(expn);
						return expn;			-- do no substitutions
					end if;
							
			
					replacement_map := {[symbvar,arglist(j + 1)?symbvar]: symbvar = symbvars(j)};
							-- map each definition parameter into the argument which replaces it
					return substitute(symbdef,replacement_map);
							-- substitute these replacements within the symbol definition and return the result
				end if;
				
				return [n1,n2,arglist]; 			-- otherwise just return the symbol with the substituted arguments
				
			when "ast_genset" => 		-- setformer 
				subst_n2 := replace_fcn_symbol(n2,symbol,symbvars,symbdef);
				subst_iter := [if j = 1 then iter else replace_fcn_symbol(iter,symbol,symbvars,symbdef) end if: iter = n3(j)];
				subst_cond := if n4 = "st_null" then n4 else replace_fcn_symbol(n4,symbol,symbvars,symbdef) end if;
				return [n1,subst_n2,subst_iter,subst_cond];
			when "ast_genset_no_expr","ast_forall","ast_exists" => 	-- setformer, no expression, quantifier 
				subst_iter := [if j = 1 then iter else replace_fcn_symbol(iter,symbol,symbvars,symbdef) end if: iter = n2(j)];
				subst_cond := if n3 = "st_null" then n4 else replace_fcn_symbol(n3,symbol,symbvars,symbdef) end if;
				return [n1,subst_iter,subst_cond];
--
--			when "ast_forall" => 		-- universal quantifier 
--print(expn);			
--			when "ast_exists" => 		-- existential quantifier
--print(expn);						
			when "ast_enum_set" => 		-- enumerated set 
			
				return [n1] + [replace_fcn_symbol(elt,symbol,symbvars,symbdef): elt in expn(2..)];
			when "ast_enum_tup" => 		-- enumerated tuple 
				
				arglist := [replace_fcn_symbol(elt,symbol,symbvars,symbdef): elt in expn(2..)];
				if case_change(n1,"lu") = case_change(symbol,"lu") then		-- if the tuple iself is being expanded then
					replacement_map := {[symbvar,arglist(j)?symbvar]: symbvar = symbvars(j)};
								-- map each definition parameter into the argument which replaces it
					return substitute(symbdef,replacement_map);
								-- substitute these replacements within the symbol definition and return the result
				end if;
				
				return [n1] + arglist;		-- otherwise just return the tuple arguments, as a tuple
		
			when "#","ast_pow","ast_range","ast_domain","ast_arb" => 		-- builtin monadics
				
				substarg := replace_fcn_symbol(n2,symbol,symbvars,symbdef);
				
				if n1 = symbol then 		-- we have occurrence of a significant symbol
					
					replacement_map := {[symbvar,substarg?symbvar]: symbvar = symbvars(j)};
							-- map each definition parameter into the argument which replaces it
					return substitute(symbdef,replacement_map);
							-- substitute these replacements within the symbol definition and return the result
				end if;
				
				return [n1,substarg]; 			-- otherwise just return the symbol with the substituted arguments
			
			when "TILDE_" => 		-- function application (has special syntax)
--print("expn: ",expn);					
					substarg := replace_fcn_symbol(n2,symbol,symbvars,symbdef);
					
					substarg2 := replace_fcn_symbol(n3(2),symbol,symbvars,symbdef);		-- throw away nonstandard square brackets
--print("replacement_map: ",replacement_map," result: ",unparse(substitute(symbdef,replacement_map))," substarg: ",unparse(substarg)," substarg2: ",unparse(substarg2)," symbdef: ",unparse(symbdef));
					if n1 /= symbol then return ["TILDE_",substarg,["ast_enum_tup",substarg2]]; end if;
					
					return ["ast_of", "CDR", ["ast_list", ["ast_arb", ["DOT_ON", substarg, ["ast_enum_set", substarg2]]]]];
							-- combine these replacements as a tilde and return the result
						
			otherwise =>
			
				if n1 = symbol then 		-- defined binary operator
				
					substarg := replace_fcn_symbol(n2,symbol,symbvars,symbdef);
					substarg2 :=if n3 /= OM then replace_fcn_symbol(n3,symbol,symbvars,symbdef) else OM end if;
						
					if n1 = symbol then 		-- we have occurrence of a significant symbol
						
						replacement_map := {[symbvar,if j = 1 then substarg else substarg2 end if?symbvar]: symbvar = symbvars(j)};
								-- map each definition parameter into the argument which replaces it
	
						return substitute(symbdef,replacement_map);
								-- substitute these replacements within the symbol definition and return the result
	
					end if;
					
					return [n1,substarg,substarg2]; 			-- otherwise just return the symbol with the substituted arguments
				else 						-- some other operator
			
					substarg :=if n2 /= OM then replace_fcn_symbol(n2,symbol,symbvars,symbdef) else OM end if;
					substarg2 :=if n3 /= OM then replace_fcn_symbol(n3,symbol,symbvars,symbdef) else OM end if;
					substarg3 :=if n4 /= OM then replace_fcn_symbol(n4,symbol,symbvars,symbdef) else OM end if;
						
					return [n1,substarg,substarg2,substarg3];
			
				end if;
		end case;
		
end replace_fcn_symbol;
procedure find_setformers_and_quants(tree);		-- finds the setformer and quantifier nodes in a tree, and returns them
												-- as a list
	
	var setformers_and_quants := [];	-- the list to be returned
		
	find_setformers_and_quants_in(tree);		-- call the recursive workhorse
	
	return setformers_and_quants;					-- return the map produced
	procedure find_setformers_and_quants_in(tree);	-- internal recursive workhorse
		
		if is_string(tree) then return; end if;
--print("find_setformers_and_quants_in::: ",unparse(tree));		
		[n1,n2,n3] := tree;			-- break node into operands and operator: generally infix (but not always)
		if (ah := abbreviated_headers(n1)) in {"{}","{/}","EX","ALL"} then 
			setformers_and_quants with:= tree; 
		end if;
		
		case ah 
	
			when "if" => find_setformers_and_quants_in(n2); find_setformers_and_quants_in(n3); find_setformers_and_quants_in(tree(4));
						-- if expression
			when "and","or","==","=",":=","incs","incin","imp","+","*","-" ,"in","notin","/=","/==","@" => 	-- various binaries
				
					find_setformers_and_quants_in(n2); find_setformers_and_quants_in(n3); 
					
			when "{-}" => for nj in tree(2..) loop find_setformers_and_quants_in(nj); end loop;	-- enumerated set
			when "[-]" => find_setformers_and_quants_in(n2); if n3 /= OM then find_setformers_and_quants_in(n3); end if;
					 		-- ordered pair, or singleton for tilde pplication
	
			when "domain","range","not","arb" => find_setformers_and_quants_in(n2);		-- built-in monadics
			
			when "EX","ALL","{/}" =>	-- quant or setformer, no expr; look for sets in the iterator list bounds and in the condition
				
				find_setformers_and_quants_in(n2); find_setformers_and_quants_in(n3); 
				
			when "{}" =>	-- setformer; look for sets in the lead expression, the iterator list bounds and in the condition
				find_setformers_and_quants_in(n2); find_setformers_and_quants_in(n3); find_setformers_and_quants_in(tree(4));
			when "[]" =>	-- list, e.g. of arguments; search for sets in the iterator bounds
				for iter in tree(2..) | not (is_string(iter) or is_string(bound := iter(3))) loop 
					find_setformers_and_quants_in(bound);
				end loop;
			when "()" => 			-- function and predicate application; just search all the arguments
					
				for nj in tree(3)(2..) loop find_setformers_and_quants_in(nj); end loop;
			otherwise => for nj in tree(2..) loop find_setformers_and_quants_in(nj); end loop;	
						-- other operators; process all arguments
		
		end case;
			
	end find_setformers_and_quants_in;
end find_setformers_and_quants;
--			*********************************************************************************
--			********************************* Obsolete code *********************************
--			*********************************************************************************
	
	procedure monotone_inference(node1,node2); 	-- calculates conditions for value defined  by node 1 to include value defined by node 2
		-- We begin by standardizing the two  nodes so as to  bring them into the closest feasible correspondence. 
		-- Then  we descend the two  trees in parallel, looking for differences in the parse trees, and collecting bound variables.
		-- A difference can be found either at a common functional or predicate node. A dummy variable is assigned to each difference,
		-- and we return the set of quintuples  [subtree1,subtree2,bound_variables,dummy_variable,is_pred] and the common reduced formula in which
		-- all differences have been replaced by the generated dummy variables. This common formula is then  surveyed for monotone dependencies
		-- using 'blob_to_monotone'. Then the following deductions are possible: (a) if there is just one  difference under no bound variables, 
		-- and the expression is additive, then we can  deduce an inequality between sums. (b) if there is any differnce at a position depending
		-- in a mixed  way on the variable inserted at that position,  then no deduction is possible. (c) Otherwise we can deduce either 
		-- of two inclusions, from corrresponnding inclusions for the differences,  with reversals in the difference positions which are decreasing_kind.  
		difference_quintuples := {};			-- set of difference quintuples
		monot_var_ctr := 0;						-- counter used for generation of variables
--		node1 := standardize_bound_vars(node1); node2 := standardize_bound_vars(node2);		-- standardize the bound variables in both formulae
		
		res := monotone_inference_in(node1,node2,{},true); 	-- workhorse; start at predicate level with no bound variables
print("difference_quintuples: ",difference_quintuples);		
		return res;
		
	end monotone_inference;
	procedure monotone_inference_in(tree1,tree2,bound_vars_with_ranges,is_pred); 	-- workhorse for 'monotone_inference'
--print("monotone_inference_in: ",unparse(tree1)," ",unparse(tree2)," bound_vars_with_ranges = ",bound_vars_with_ranges," is_pred = ",is_pred);
		if is_string(tree1) or is_string(tree2) then 		-- a variable confronts a subexpression
		
			if tree1 = tree2 then return tree1; end if;			-- no difference in bottom-level leaves; just return
			difference_quintuples with := [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred]; 
			return newvar;		-- return the representing variable
		
		end if;		-- otherwise the nodes differ in their principal operator; collect the difference
		
		[n1_1,n2_1,n3_1] := tree1; [n1_2,n2_2,n3_2] := tree2; 			-- get the likely parts of the two clauses
		
		if n1_1 /= n1_2 then			-- the nodes are not of the same kind, so return a difference variable
		
			difference_quintuples with:= [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred];
			return newvar;		-- return the representing variable
		end if; 						-- otherwise two subexpressions confront each other
		
			-- othewise the nodes are of the same kind,  so go on to look for differences in their arguments
	
		case (ah := abbreviated_headers(n1_1))
			when "and","or","==","/==","imp","null" =>				-- boolean operations
						 -- ordinary operators with a fixed number of arguments; look for differences in their arguments
					res_1 := monotone_inference_in(n2_1,n2_2,bound_vars_with_ranges,true);		-- find subpart differences
					res_2 := monotone_inference_in(n3_1,n3_2,bound_vars_with_ranges,true);		-- subparts are predicates 
					return [n1_1,res_1,res_2];		-- return the condensed tree, having found differences
			when "+","-","{.}","in","notin","=","/=","incs","incin","*","[-]" =>			
						 -- ordinary operators with a fixed number of arguments; look for differences in their arguments
						-- ast_enum_tup should be ordered pair only; singleton for application operator is handled elsewhere
--print("[n1_1,n2_1,n3_1]: ",[n1_1,n2_1,n3_1]," ",[n1_2,n2_2,n3_2]);	
					res_1 := monotone_inference_in(n2_1,n2_2,bound_vars_with_ranges,false);		-- find subpart differences
					res_2 := monotone_inference_in(n3_1,n3_2,bound_vars_with_ranges,false);		-- subparts are terms 
					return [n1_1,res_1,res_2];		-- return the condensed tree, having found differences
			when "->" =>			-- "->" is the functional application operator "TILDE_"
					res_1 := monotone_inference_in(n2_1,n2_2,bound_vars_with_ranges,false); 		-- subparts are expressions 
					res_2 := monotone_inference_in(n3_1(2),n3_2(2),bound_vars_with_ranges,false); 	-- syntax is ["TILDE_", "F", ["ast_enum_tup", "X"]]
					return [n1_1,res_1,["ast_enum_tup",res_2]];		-- return the condensed tree, having found differences
			when "not" =>
					res_1 := monotone_inference_in(n2_1,n2_2,bound_vars_with_ranges,true); 		-- subparts are predicates
					return [n1_1,res_1];		-- return the condensed tree, having found differences
			when "[]" =>			-- ast_list
					
					print("****** shouldnt happen - monotone_inference_in: ",ah);
					
			when "{-}" => 				-- enumerated sets. here we try to improve the agreement by sorting the elements
				if #(args1 := tree1(2..)) /= #(args2 := tree2(2..)) then 		-- the nodes differ in their number of arguments; treat as different
					difference_quintuples with:= [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred];
					return newvar;		-- return the representing variable
				end if;				-- otherwise blob the elements and sort them to bring into the order most likely to agree
				
				args1_with_blobs := [[blob_to_string(a1,[],name_ctr),a1]: a1 in args1];		-- we leave out the bound variables, since these will be common to both sets
				args2_with_blobs := [[blob_to_string(a2,[],name_ctr),a2]: a2 in args2];
				args1_sorted := [y: [x,y] in merge_sort(args1_with_blobs)];
				args2_sorted := [y: [x,y] in merge_sort(args2_with_blobs)];
--print("args1_with_blobs: ",args1_sorted," ",args2_sorted);stop;				
											-- look for differences in their arguments
				if args1_sorted /= args2_sorted then 
					difference_quintuples with:= [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred];
					return newvar;		-- return the representing variable
				end if;
				return tree1;		-- otherise we have fond identity, so return the common tree
			when "()" => 				-- this is the case of functional and predicate application; the second variable is a reserved symbol, not a set
--print("function trees: ",tree1," ",tree2);					
				if tree1 /= tree2 then 		-- the nodes differ 
					difference_quintuples with:= [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred];
					return newvar;		-- return the representing variable
				end if;				-- otherwise blob the elements and sort them to bring into the order most likely to agree
				return tree1;		-- otherise we have fond identity, so return the common tree
			when "{}" => 		-- setformer with expression; here there is a fourth argument, namely the predicate (this may be "ast_null", represetnting 'true')
					-- here we are comparing two setformers having structures like {e1(x,y,z): x in s1, y in t1(x), z in w1(x,y) | A(x,y,z)}.
					-- if the iterator lists which appear are of the same length and have corresponding structures throughout, and have identical expressions,
					-- then we look for differences in the restrictions of the bound variables, and also for differences in the predicates
					
					-- otherwise we treat the two setformers as different  
				
				body_1 := n2_1; body_2 := n2_2; 										-- get the two expressions being accumulated
				list_of_iters_1 := orig_list_of_iters_1 := n3_1(2..); list_of_iters_2 := orig_list_of_iters_2 := n3_2(2..); 	-- get the two lists of iterators
						-- the iter_list syntax is exemplified by ["ast_iter_list", ["ast_in", "X", "A"], ["ast_incs", "Y", "B"],...]
				cond_1 := tree1(4); cond_2 := tree2(4); 					-- get the two conditions
				if cond_1 = ["ast_null"] then cond_1 := "TRUE"; end if;		-- replace empty conditions with "TRUE"
				if cond_2 = ["ast_null"] then cond_2 := "TRUE"; end if;
				
				if body_1 /= body_2 then			-- iterator expressions differ; so treat as diferent
					difference_quintuples with:= [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred];
					return newvar;		-- return the representing variable
				end if;
				if (cl := #list_of_iters_1) /= #list_of_iters_2 then 		-- the iterators are of different lengths, so treat s different
					difference_quintuples with:= [tree1,tree2,bound_vars_with_ranges,newvar := "mv_" + str(monot_var_ctr +:= 1),is_pred];
					return newvar;		-- return the representing variable
				end if;			-- otherise check that the iterators are all  of the same types
--					if not verify_equality_in(body_1,body_2,bound_vars_with_ranges + list_of_iters_1,false) then -- the bodies are not identical
--						return false;				-- verification by examination of subparts succeeds		
--					end if;				-- otherwise we must check to see if the iterator lists are equivalent
--
--					if not verify_equality_in(cond_1,cond_2,bound_vars_with_ranges + list_of_iters_1,true) then -- the conditions are not identical
--						return false;				-- verification by examination of subparts succeeds		
--					end if;				-- otherwise we must check to see if the iterator lists are equivalent
--				
--				succeeds := true;		-- see if the following loop succeeds
----print("body_1,body_2:: ",body_1," ",body_2," ",list_of_iters_1," ",list_of_iters_2);
--				for k in [1..cl] loop
--					[-,-,restr_set_1] := list_of_iters_1(k); [-,-,restr_set_2] := list_of_iters_2(k); 		-- get the restriction sets
----print("restr_set_1,restr_set_2:: ",restr_set_1," ",restr_set_2," ",bound_vars_with_ranges + list_of_iters_1(1..k - 1)," ");
--					if not verify_equality_in(restr_set_1,restr_set_2,bound_vars_with_ranges + list_of_iters_1(1..k - 1),false) then
--						succeeds := false; exit;
--					end if;
--
--				end loop;
--				
--				if succeeds then return true; end if;		-- otherwise not all of the restriction sets are eqivalent, so we 
--															-- go on to see if identity can be verified 'directly' in some way 
--		
--				for j in [1..cl] loop
--
--					if j = cl then				-- there are no suffixed qualifiers
--
--						required_implication := ["ast_forall",["ast_iter_list"] + orig_list_of_iters_1,["DOT_EQ",cond_1,cond_2]];
--						required_identity := ["ast_forall",["ast_iter_list"] + orig_list_of_iters_1,["ast_eq",body_1,body_2]];
--
--						if not check_in_context(required_implication,bound_vars_with_ranges) then continue; end if;
--						if not check_in_context(required_identity,bound_vars_with_ranges) then continue; end if;
--
--						succeeds := true;		-- see if the following loop succeeds
--						for k in [1..cl] loop
--
--							[-,-,restr_set_1] := list_of_iters_1(k); [-,-,restr_set_2] := list_of_iters_2(k); 		-- get the restriction sets
--
--							if not verify_equality_in(restr_set_1,restr_set_2,bound_vars_with_ranges + list_of_iters_1(1..k - 1),false) then
--								succeeds := false; exit;
--							end if;
--
--						end loop;
--
--						if succeeds then return true; end if;			-- otherwise keep looing to try to find a verification
--						
--					else				-- there are suffixed qualifiers; use these in setformers
--
--						set_1 := ["ast_genset",body_1,["ast_iter_list"] + orig_list_of_iters_1(j + 1..),cond_1];
--						set_2 := ["ast_genset",body_2,["ast_iter_list"] + orig_list_of_iters_2(j + 1..),cond_2];
--						required_identity := ["ast_forall",["ast_iter_list"] + orig_list_of_iters_1(1..j),["ast_eq",set_1,set_2]];
--						
--						if not check_in_context(required_identity,bound_vars_with_ranges) then continue; end if;
--
--						succeeds := true;		-- see if the following loop succeeds
--						for k in [1..j] loop
--
--							[-,-,restr_set_1] := list_of_iters_1(k); [-,-,restr_set_2] := list_of_iters_2(k); 		-- get the restriction sets
--
--							if not verify_equality_in(restr_set_1,restr_set_2,bound_vars_with_ranges + list_of_iters_1(1..k - 1),false) then
--								succeeds := false; exit;
--							end if;
--
--						end loop;
--
--						if succeeds then return true; end if;			-- otherwise keep looping to try to find a verification
--
--					end if;
--
--				end loop;
--					
--					return false;			-- if the iterator sequences don't agree, we give up in this case, allowing proof to proceed more manually,
--				
--				end if;
--
--				when "{/}" => 							-- setformer without expression
--														-- see comment at start of preceding case
--						
--					list_of_iters_1 := n2_1(2..); list_of_iters_2 := n2_2(2..); 			-- get the two lists of iterators
--					cond_1 := n3_1; cond_2 := n3_2; 					-- get the two conditions
--					[cl,list_of_iters_1,list_of_iters_2,cond_1,cond_2] := common_iter_len(list_of_iters_1,list_of_iters_2,cond_1,cond_2);
--
--					if cl = #list_of_iters_1 and cl = #list_of_iters_2 then 		-- the iterators agree out to their full lengths; look for differences in bodies
--
-- 						if not verify_equality_in(cond_1,cond_2,bound_vars_with_ranges + list_of_iters_1,true) then -- the conditions are not identical
-- 							return false;				-- verification by examination of subparts succeeds		
-- 						end if;				-- otherwise we must check to see if the iterator lists are equivalent
--						
----print("cond_1,cond_2:: ",cond_1," ",cond_2," ",list_of_iters_1," ",list_of_iters_2);
--						[-,-,restr_set_1] := list_of_iters_1(1); [-,-,restr_set_2] := list_of_iters_2(1); 		-- get the restriction sets
--
--						if not verify_equality_in(restr_set_1,restr_set_2,bound_vars_with_ranges,false) then
--							return false; 
--						end if;
--					
--					end if;
--						
--					return true;		-- if all tests are passed, we return true
--					 	
--				when "ALL","EX" => 					-- universal and existential quantifiers
--					
--					lead_quantifier := if ah = "ALL" then "ast_forall" else "ast_exists" end if;
--
--					[list_of_iters_1,body_1] := flatten_universal(tree1); [list_of_iters_2,body_2] := flatten_universal(tree2);
--									-- find the full flattened lists of prefixed universal quantifiers in the two trees
--									-- now find the iterator portions which are of the same types. That is, the operators "ast_in" or "DOT_INCIN"
--									-- must be the same, and for the "ast_in" case, either both ranges or none must be OM 
--					
--					[cl,list_of_iters_1,list_of_iters_2,body_1,body_2] := common_iter_len(list_of_iters_1,list_of_iters_2,body_1,body_2);
--										-- find the length of the iterator parts which are common
--
--					if cl = #list_of_iters_1 and cl = #list_of_iters_2 then 		-- the iterators agree out to their full lengths; look for differences in bodies
--
-- 						if not verify_equality_in(body_1,body_2,bound_vars_with_ranges + list_of_iters_1,true) then -- the bodies are not identical
-- 							return false;				-- verification by examination of subparts succeeds		
-- 						end if;				-- otherwise we must check to see if the iterator lists are equivalent
--						
--						succeeds := true;		-- see if the following loop succeeds
----print("body_1,body_2:: ",body_1," ",body_2," ",list_of_iters_1," ",list_of_iters_2);
--						for k in [1..cl] loop
--							[-,-,restr_set_1] := list_of_iters_1(k); [-,-,restr_set_2] := list_of_iters_2(k); 		-- get the restriction sets
----print("restr_set_1,restr_set_2:: ",restr_set_1," ",restr_set_2,`" ",bound_vars_with_ranges + list_of_iters_1(1..k - 1)," ");
--							if not verify_equality_in(restr_set_1,restr_set_2,bound_vars_with_ranges + list_of_iters_1(1..k - 1),false) then
--								succeeds := false; exit;
--							end if;
--
--						end loop;
--						
--						if succeeds then return true; end if;		-- otherwise not all of the restriction sets are eqivalent, so we 
--																	-- go on to see if identity can be verified 'directly' in some way 
--				
--					elseif cl = #list_of_iters_1 then 		-- the full first iterator agrees with a prefix of the second iterator
--						
--						remaining_iters := list_of_iters_2(cl + 1..);			-- find the remaining iters in the first group
--						body_2 := ["ast_forall",["ast_iter_list"] + remaining_iters,body_2];			-- restore the remaining universals to the second clause
--
--					elseif cl = #list_of_iters_2 then 		-- the full second iterator agrees with a prefix of the second iterator; insist on an identity
--						
--						remaining_iters := list_of_iters_1(cl + 1..);			-- find the remaining iters in the second group
--						body_1 := ["ast_forall",["ast_iter_list"] + remaining_iters,body_1];			-- restore the remaining universals to the first clause
--						
--					else 		-- only portions of the iterators agree; insist on an identity of the remaining parts
--						
--						remaining_iters1 := list_of_iters_1(cl + 1..);			-- find the remaining iters in the first group
--						body_1 := ["ast_forall",["ast_iter_list"] + remaining_iters1,body_1];			-- restore the remaining universals to the first clause
--						
--						remaining_iters2 := list_of_iters_2(cl + 1..);			-- find the remaining iters in the second group
--						body_2 := ["ast_forall",["ast_iter_list"] + remaining_iters2,body_2];			-- restore the remaining universals to the second clause
--					
--					end if;
--							-- here we attempt 'direct', rather than 'subpart' verifiction, by trying identities 
--							-- prefixed by the full list of quantifiers and by any of its initial subparts, any one of which might be relevant
----print("try direct verification: cl = ",cl," lead_quantifier = ",lead_quantifier," list_of_iters_1 = ",list_of_iters_1," list_of_iters_2 = ",list_of_iters_2," body_1 = ",body_1," body_2 = ",body_2);						
--					for j in [cl + 1,cl..1] loop		-- try clauses with varying numbers of prefixed quantifiers
--
--						qbody_1 := if j > cl then body_1 else [lead_quantifier,["ast_iter_list"] + list_of_iters_1(j..),body_1] end if;
--												-- prefix the bodies with varying numbers of quantifiers
--						qbody_2 := if j > cl then body_2 else [lead_quantifier,["ast_iter_list"] + list_of_iters_2(j..),body_2] end if;
--						
--						required_equivalence := ["DOT_EQ",qbody_1,qbody_2];		-- set up, and then quantify, the required equivalence
--						if j > 1 then required_equivalence := ["ast_forall",["ast_iter_list"] + list_of_iters_1(1..j - 1),required_equivalence]; end if;
--
----print("required_equivalence: ",j," ",unparse(required_equivalence));
--						if not check_in_context(required_equivalence,bound_vars_with_ranges) then continue; end if;
--										-- since the equivalence, quantified using the list of quantifiers drawn from the first formula, fails.
--										-- otherwise we must verify that the two lists of restriction sets are equivalent. 
--										-- This can be done recursively, since the restriction sets are syntacticallly 'smaller' than the
--										-- expressions in which thy appear
--
--						succeeds := true;		-- see if the following loop succeeds
--								
--						for k in [1..j - 1] loop		-- check the restriction sets in the prefixed iterators
--							[-,-,restr_set_1] := list_of_iters_1(k); [-,-,restr_set_2] := list_of_iters_2(k); 		-- get the restriction sets
--
--							if not verify_equality_in(restr_set_1,restr_set_2,bound_vars_with_ranges + list_of_iters_1(1..k - 1),false) then
--								succeeds := false; exit;
--							end if;
--																
----							req_ident := ["ast_eq",restr_set_1,restr_set_2];			-- form the body of the required identity 
----							required_equivalence := ["ast_and",required_equivalence,build_quantified_version(req_ident,list_of_iters_1(1..k - 1))];
----										-- add the necessary quantifiers and conjoin to the required equivalence
--						end loop;
--						
--						if succeeds then return true; end if;		-- since a quantified equivalence and all the restriction set equivalences have been verified
--
--					end loop;
--
--					return false;		-- if none of the quantified clauses verifies, we fail at this level
--	
--				when "itr","Etr" => 				-- iterators; here the iterators must be of the same kinds, and if one is involves a bounding set
--													-- so must the other. the bound variable names may differ; if they do we generate a common new name for both, 
--													-- and sustitute it uniformly down both trees.
--							-- find the common minimum of the iterator lists lengths
--					if #(args1 := tree1(2..)) /= #(args2 := tree2(2..)) then 		-- the nodes differ in their number of arguments; treat as different
--	
--						diffs_vars_ranges with:= [bound_vars_with_ranges,tree1,tree2];
--						return; 
--
--					end if;
--				
--					for sn = args1(j) loop verify_equality_in(sn,args2(j),bound_vars_with_ranges,is_pred); end loop;
			otherwise => print("shouldn't happen verify_equality_in: ",ah," ",node); 		-- shouldn't happen
		
		end case;
	end monotone_inference_in;
	procedure test_monotone_inference; 			-- test of the monotone_inference procedure
	print("****** TESTS OF BASIC MONOTONE INFERENCE PROC ******");
	
	--	print(); print("test_monotone_inference: ",parze_expr("{e(x): x incs a | P(x)};")); 
	--	print(); print("test_monotone_inference forall: ",parze_expr("(FORALL x incs a | P(x));"));
		
		pairs := [["x incs a;","(x + y) incs a;"],
					["x incs a or (not (y incs b));","(x + y) incs a or (not ((z + w) incs b));"]];
		
		for [hypothesis,conclusion] in pairs loop
			print(); tree1 := parze_expr(hypothesis); tree2 := parze_expr(conclusion); 
			print(monotone_inference(tree1(2),tree2(2)));
		end loop; 
		
	end test_monotone_inference;
end logic_syntax_analysis_pak2; 
--					****************************************************
--					**************** Proof by structure ****************
--					****************************************************
--	Proof by structure uses a simple auxiliary language of structure
--	descriptors to keep track of the top structural levels of sets appearing
--	in scenario proofs. Any special set defined in a scenario, for example
--	Za, the set of all non-negative integers, or Re, the set of all reals, can be used as
--	a primary structure symbol in this language. This descriptor attaches to
--	all members of the set, i.e. any integer has the descriptor Za. A
--	significant but less basic example is Si, the set of all
--	signed integers, which does not occur in our present scenario but could
--	easily be defined. Structure descriptors need not be confined to sets,
--	but can also designate classes, like the class Ord of all ordinals, the
--	class Fin of all finite sets, the class Infin of all infinite sets,
--	and the class U of all sets, etc.
--	Given any symbols S,S_1,S_2,... representing structures we can then
--	form:
--	(i) {S}: describes a set all of whose elements have the descriptor S.
--	For example,the set Za has the descriptor {Za}; the set of sets of
--	integers has the descriptor {{Za}}.
--	(ii) [S_1,S_2]: describes a pair whose components have respectively the
--	descriptors S_1 and S_2.
--	These constructions can be compounded. For example
--	(e.1) {[Za,Za]} describes a set of integer pairs (and so applies to Si,
--	the set of signed integers
--	(e.2) {[Za,U]} describes a map from integers to elements of any kind, i.e
--	describe any finite or infinite sequence.
--	A given set can have several descriptors. For example, a finite sequence
--	of signed integers has the descriptors {[Za,Si]} and Fin. Since Si itself
--	has the descriptor {[Za,Za]}, a sequence of signed integers also has the
--	descriptor {[Za,[Za,Za]]}, which in any given situation we may wish either
--	to use of ignore. Infinite sequences of signed integers have the
--	descriptors {[Za,Si]} and Infin. Real numbers in Cantor's representation
--	are equivalence classes of such sequences, and therefore have the
--	descriptors {{[Za,Si]}} and {Infin}.
--	A mechanism within the verifier should track the descriptors of
--	variables and expressions appearing in proofs whenever possible. For
--	example, a variable x known to satisfy a clause 'x in Z' has the
--	descriptor Za, a variable known to satisfy 'x in Si' has the descriptors
--	Si and [Za,Za].
--	Setformers and other basic constructors operate in a known way on the
--	structure descriptors introduced above. Suppose, for example, that s is
--	a set known to have some descriptor {D}, and that e(x) is an expression
--	having the free variable x which is known to map elements having the
--	descriptor D into elements having the descriptor D'. Then
--	 {e(x): x in s | P}
--	has the descriptor {D'}, while
--	 {[x,e(y)]: x in s, y in s | P}
--	has the descriptor {[D,D']}.
--	When a set s is known to have a descriptor {D}, any element x for which
--	'x in s' has been proved is known to have the descriptor D. If D is a
--	primitive descriptor representing a known set, this will give us the
--	assertion 'x in D', for example 'x in Z', which may be needed as an
--	auxiliary hypothesis for the application of some theorem. Similarly any
--	set s having the descriptor {[Za,Za]} is known to satisfy Is_map(s), and
--	also
--	 (FORALL x in s | car(x) in Za & cdr(x) in Za)
--	Conclusions of this kind can often result automatically. This is a
--	principal use for our system of structure descriptors.
--	Many other basic set-theoretic operations have known effects on
--	descriptors. These often follow from the definitions of the operators in
--	question. For example:
--	(1) if s has the descriptor {D}, then so does every one of its subsets,
--	and pow(s) has the descriptor {{D}}.
--	(2) if s has the descriptor {{D}}, then Un(s) has the descriptor {D}.
--	Note hat this follows automatically from the definition {x: y in s, x in
--	y} of Un(s), since the bound variable y in the iterator has the
--	descriptor {D}, so each of the x has the descriptor D, and the set as a
--	whole has the descriptor {D}.
--	(3) if s_1 and s_2 both have a descriptor {D}, then so does s_1 + s_2.
--	(4) if s_1 and s_2 both have the descriptor Fin, then so does s_1 + s_2.
--	(5) if s_1 and s_2 have descriptors {D_1} and {D_2} respectively, then
--	s_1 * s_2 has both descriptors {D_1} and {D_2}. Even if s_2 has no
--	descriptor, s_1 * s_2 and s_1 - s_2 has the descriptor {D_1}, as does
--	any set s for which an assertion s •incin s_1 has been proved.
--	(6) if s_1 has the descriptor Fin, so do s_1 * s_2 and s_1 - s_2, as
--	does any set s for which an assertion s •incin s_1 has been proved.
--	(7) if s_1 and s_2 have the descriptor Fin, so does any setformer {e: x
--	in s_1, y in s_2 | P},or any setformer {e: x in s_1 | P}
--	(8) #s always has the descriptor Card. Since the class of cardinals has
--	the descriptor {Ord}, #s also has the descriptor Ord, as does any x
--	known to be a cardinal. If s has the descriptor Fin, then #s has the
--	descriptor Za. Since Za itself has the descriptor {Fin}, #s also has the
--	descriptor Fin.
--	(9) if has the descriptor {D}, then any setformer like {x: x •incin s|
--	p} is known to have the descriptor {{D}}; this result obviously
--	generalizes.
--	(10) if sets s and t have the descriptors {D} and {D'} respectively,
--	then their Cartesian product s •PROD t has the descriptor {[D,D']}, If s
--	and t both have the descriptor Fin, so does s •PROD t.
--	(11) if s and t have descriptors {[D,D']} and {[D',D'']} respectively,
--	then s@t has the descriptor {[D,D'']}. If s and t both have the
--	descriptor Fin, so does s@t.
--	(12) if s and t have descriptors D, D' respectively, then [s,t] has the
--	descriptor [D,D']. If u has the descriptor [D,D'], then car(u) has the
--	descriptor D and cdr(u) has the descriptor D'.
--	(12) if F has the descriptor {[D,D']}, then inv(F) has the descriptor
--	{[D',D]}, and F •ON s has the descriptor {[D',D]}. if F has the
--	descriptor Fin, then inv(F) and F •ON s both have the descriptor Fin
--	also.
--	There may be useful extensions of these ideas to single-valued and
--	one-one maps; also to topologcal situations, spaces of continuous
--	functions, etc.
--	Note that some of the conclusions derived manually in the present
--	scenarios can result automatically by use of structure descriptors. For
--	example, the cardinal sum is defined as
--	 #({[x,0]: x in s_1} + {[x,1]: x in s_2}),
--	making it obvious that the sum of two integers is an integer. Similarly,
--	the definition of cardinal product, namely
--	 #{[x,y]: x in s_1,y in s_2}
--	makes it apparent that the product of two integers is an integer. Since
--	the difference of integers is defined by #(n - m), it also follows
--	immediately that the difference of integers is an integer.
--	Ordinals also have the descriptors {Ord}, since any element of an
--	ordinal is an ordinal. Any Un(s) of a set having the descriptor {Ord}
--	has the descriptor Ord. It may be worth carrying the set next(Z) as an
--	additional descriptor. If this s done, Un(s) will be known to have the
--	descriptor next(Za) is s has the descriptor {Za}, and so to have the
--	descriptor Za (i.e. to be an integer) if there is another s' having the
--	descriptor next(Z) for which
--	a statement s in s' is available.
--	In many cases a definition or theorem appearing in a scenario will
--	characterize the action on structure descriptors of one or more of the
--	function symbols appearing in it. The examples given just above
--	illustrate this. Such facts combined with the other rules given above
--	extend the verifier's ability to tack the structures of objects appearing
--	in proofs. For example, if s, t and u are sets known to have the
--	descriptor {Za}, then
--	 {(x •TIMES y) •PLUS z: x in s, y in t, z in u | P}
--	is also known to have the descriptor {Za}.
--	The theory of summation yields the fact that Sum(f) has the descriptor D
--	if f has the descriptors {[d,D]} and Fin, and if the 'PLUZ' operator
--	appearing in the summation can be shown to map pairs of objects having
--	the descriptor D into objects having this same descriptor. Thus, for
--	example, the sum or product of any setformer like
--	 {[[x,y,z],(x •TIMES y) •PLUS z]: x in s, y in t, z in u | P}
--	is also known to be an integer if s, t and u are sets known to have the
--	descriptor {Za}.
--	The structure definition mechanism explained above carries over in a
--	useful way to recursively defined functions (in our set-theoretic
--	context, these can be functions defined by transfinite induction.) To
--	show why such extension is possible, we first need to note that the
--	system of descriptors extends readily to function symbols, since these
--	are very close semantically to sets of pairs. For example, the
--	descriptor {[D_1,D_2]} can be ascribed to any one-parameter function
--	symbol which maps each object having the descriptor D_1 into an object
--	having the descriptor D_2. Similarly, the descriptor {[[D_1,D_2],D_3]}
--	can be ascribed to any two-parameter function symbol which yields an
--	object having the descriptor D_3 whenever its two parameters have the
--	respective descriptors D_1 and D_2. (For example, the integer addition
--	operator •PLUS has the descriptor {[[Za,Za],Za]}, but also the descriptors
--	{[U,U],Ord]} since it always produces an ordinal, and the descriptor
--	{[[Fin,Fin],Za]}, since it produces an integer for any two finite
--	inputs.) In the three-parameter case, {[[[D_1,D_2],D_3],D_4]} can be
--	ascribed to any three-parameter function symbol which yields an object
--	having the descriptor D_4 whenever its three parameters have the
--	respective descriptors D_1, D_2 and D_3.
--	Using these descriptors, we can state the rule for function application
--	as follows: If a one-parameter function symbol f has the descriptor
--	{[D_1,D_2]}, and x has the descriptor D_1, then f(x) has the descriptor
--	D_2. Similarly, if a two-parameter function symbol f has the descriptor
--	{[[D_1,D_2],D_3]}, and its two arguments x_1,x_2 has the descriptor
--	D_1,D_2, then f(x_1,x_2) has the descriptor D_3. We leave it to the
--	reader to formulate the rules for more than two arguments.
--	Function compounding acts in an obvious way on descriptors, for example
--	if f has the descriptor {[D_1,D_2]} and g has the descriptor
--	{[D_2,D_3]}, then g(f(.)) has the descriptor {[D_1,D_3]}. Rules like
--	this make it obvious why
--	 #({[x,0]: x in s_1} + {[x,1]: x in s_2}),
--	yields an integer for every pair of integer arguments: the functional
--	expression {[x,0]: x in s_1} has the descriptor {[Fin,Fin]} simply
--	because it is a setformer with s_1 as its only free variable, and
--	likewise for {[x,1]: x in s_2}. Since the union operator '+' has the
--	descriptor {[[Fin,Fin],Fin]}, it follows immediately that {[x,0]: x in
--	s_1} + {[x,1]: x in s_2} has the descriptor {[[Fin,Fin],Fin]} also.
--	Since #has the descriptors {[Fin,Fin]} and{[U,Ord]}, #({[x,0]: x in s_1}
--	+ {[x,1]: x in s_2}) has the descriptors {[[Za,Za],Fin]} and
--	{[[Za,Za],Ord]}, and therefore {[[Za,Za],Za]}. Much the same argument applies
--	to the integer product.
--	Next consider a transfinite recursive definition of one of the general
--	types we allow,namely
--	 f(s,t) := d({g(f(x,h(s,t)),s,t): x in s | P(x,f(x,h(s,t)),s,t)},s,t),
--	where as before we assume that the functions d, g, and h have been
--	defined prior to the occurrence of the recursive definition shown. In
--	working with this definition we will want to establish that f has some
--	descriptor {[[D_1,D_2],D_3]}, i.e. that it yields an element having
--	descriptor D_3 for any input arguments with descriptors D_1,D_2
--	respectively. This conclusion will be valid under the following
--	circumstances: we need to know that there exits descriptor D' such that
--	 (a) h has the descriptor {[[D_1,D_2],D_2]};
--	 (b) g has the descriptor {[[[D_3,D_1],D_2],D']};
--	 (c) d has the descriptor {[[[{D'},D_1],D_2],D_3]}.
--	Then in the ground case of the transfinite recursive definition f({},t)
--	has the value d({},s,t), and so must produce an element with the
--	descriptor D_3. In the remaining case it follows inductively (given that
--	s and t have the respective descriptors D_1,D_2) that f(x,h(s,t)) has
--	the descriptor D_3 for every x in s, so that g(f(x,h(s,t)),s,t) has the
--	descriptor D', and so
--	(*) {g(f(x,h(s,t)),s,t): x in s | P(x,f(x,h(s,t)),s,t)}
--	has the descriptor {D'}. Therefore the right side of the recursive
--	definition seen above has the descriptor D_3, and it follows inductively
--	that f has the descriptor {[[D_1,D_2],D_3]}.
--	If s has the descriptor Fin, then the set (*) will have this descriptor
--	also, and so if d has the descriptor {[[[Fin,Fin],D_2],Fin]}, f will
--	have the descriptor {[[Fin,D_2],Fin]}. O the other hand, if d is an
--	operator like arb, and so has the descriptor {[{D_1},D_2]} (where the
--	null set must have the descriptor D_2), then g must have the descriptor
--	{[[[Fin,Fin],D_2],Fin]}, and s the descriptor {Fin},for f(s,t) to have
--	the descriptor Fin. In this case f has once again the descriptor
--	{[[Fin,D_2],Fin]}.
--
-- *****************************************************************
-- ************ routines realizing 'Proof by Structure' ************
-- *****************************************************************
package proof_by_structure;
	var vars_to_descriptors,debug_extract,debug_conj_tree;			-- exposed for debugging purposes only
	
-- main interface procedures
	procedure extract_relevant_descriptors(conj_tree);	-- extract additional descriptors given	tree of a conjunction	
	procedure expression_descriptor(expn_stg,vars_to_descriptors);			
-- secondary interface procedures (exposed for debugging purposes only and to be invoked by test programs)
	procedure get_assertions_from_descriptors(vars_to_descriptors);	-- convert descriptors to set membership assertions	
	procedure descriptor_action_of_memb(descriptor_set);			-- result of choosing an unknown member
	procedure descriptor_action_of_subset(descriptor_set);			-- result of choosing an unknown subset
	procedure descriptor_action_of_pow(descriptor_set);
	procedure descriptor_action_of_Un(descriptor_set);
	procedure descriptor_action_of_union(descriptor_set1,descriptor_set2);
	procedure descriptor_action_of_intersection(descriptor_set1,descriptor_set2);
	procedure descriptor_action_of_difference(descriptor_set1,descriptor_set2);
	procedure descriptor_action_of_range(descriptor_set);
	procedure descriptor_action_of_domain(descriptor_set);
	procedure descriptor_action_of_arb(descriptor_set);
	procedure descriptor_action_of_pair(descriptor_set1,descriptor_set2);
	procedure descriptor_action_of_car(descriptor_set);
	procedure descriptor_action_of_cdr(descriptor_set);
	procedure descriptor_action_of_count(descriptor_set);
	procedure descriptor_action_of_next(descriptor_set);
	procedure descriptor_action_of_cartprod(descriptor_set1,descriptor_set2);
	procedure descriptor_action_of_funcprod(descriptor_set1,descriptor_set2);
	procedure descriptor_action_of_func_inv(descriptor_set);
--	procedure descriptor_action_of_func_ident(descriptor_set); -- UNUSED
--procedure tests;			-- SYSTEMATIC TESTS FOR ABOVE PROCEDURES
end proof_by_structure;
package body proof_by_structure;
	use parser,string_utility_pak,logic_syntax_analysis_pak,logic_syntax_analysis_pak2;
var first_proof_by_structure := true;
var basic_sets := {"ZA","HFIN","RA","RE","SI","CM"};
var basic_preds := {"ORD","CARD","FIN","INFIN","COUNTABLE","NONNULL","SVM","IS_MAP","ONE_1_MAP"};
var basic_descriptors; -- union of basic_sets with basic_preds
-- "COUNTABLE" MEANS 'FINITE OR ENUMERABLY INFINITE'; "HFIN" MEANS 'HEREDITARILY FINITE'
-- COMPOUND DESCRIPTORS ARE CONSTRUCTED FROM THESE BASIC DESCRIPTORS BY PAIRING AND SINGLETON FORMATION
var descriptors_of_basic := {			-- MAPPING FROM BASIC DESCRIPTORS TO COMPOUND DESCRIPTORS GIVING THEIR PROPERTIES
	["ZA",{{"ORD"},{"FIN"},{"HFIN"},"COUNTABLE","ORD","CARD","FIN","HFIN",{"CARD"},{"ZA"},{"COUNTABLE"}}],
	["ORD",{{"ORD"}}],
	["CARD",{"ORD",{"ORD"}}],
	["HFIN",{{"HFIN"},{"FIN"},{"COUNTABLE"},"FIN","COUNTABLE"}],
	["FIN",{"COUNTABLE"}],
--	["SI",{["ZA","ZA"]}], -- overly conventional
	["RASEQ",{{["ZA","RA"]},"NONNULL","INFIN","IS_MAP","SVM","COUNTABLE"}],
	["CM",{["RE","RE"]}],
	["IS_MAP",{{["OM","OM"]}}],
	["SVM",{{["OM","OM"]},"IS_MAP"}],
	["ONE_1_MAP",{{["OM","OM"]},"SVM","IS_MAP"}],
	["OM",{{"OM"}}] -- IS THIS NEEDED?? 
};
var m_descriptors_of_basic := {		-- SIMILAR TO THE PRECEDING ONE, HANDLES MULTIPLE INHERITANCE
    [{"FIN",{"HFIN"}}, {"HFIN"}],
	[{"FIN","CARD"}, {"ZA"}],
	[{"FIN","ORD"}, {"ZA"}]
};
				-- *********** ASSOCIATION OF ACTIONS WITH SYNTAX-TREE NODES ***********
var unary_op_actions := {["ast_arb",DESCRIPTOR_ACTION_OF_ARB],
		["ast_range",DESCRIPTOR_ACTION_OF_RANGE],["ast_domain",DESCRIPTOR_ACTION_OF_DOMAIN],
		["ast_nelt",DESCRIPTOR_ACTION_OF_COUNT],["ast_pow",DESCRIPTOR_ACTION_OF_POW]};
var binary_op_actions := {["ast_add",DESCRIPTOR_ACTION_OF_UNION],["ast_mult",DESCRIPTOR_ACTION_OF_INTERSECTION],
		["DOT_PROD",DESCRIPTOR_ACTION_OF_CARTPROD],																				
		["TILDE_",DESCRIPTOR_ACTION_OF_FUNC_APP],["AT_",DESCRIPTOR_ACTION_OF_FUNCPROD],
		["ast_sub",DESCRIPTOR_ACTION_OF_DIFFERENCE],["ast_enum_tup",DESCRIPTOR_ACTION_OF_PAIR]};
var function_actions_and_numpars := {["UN",[DESCRIPTOR_ACTION_OF_UN,1]],["NEXT",[DESCRIPTOR_ACTION_OF_NEXT,1]],
		["INV",[DESCRIPTOR_ACTION_OF_FUNC_INV,1]],
		["CAR",[DESCRIPTOR_ACTION_OF_CAR,1]], ["CDR",[DESCRIPTOR_ACTION_OF_CDR,1]]};
var const_properties := {		-- MAPS CONSTANTS TO COLLECTIONS OF SETS TO WHICH THEY ARE KNOWN TO BELONG
		["0",{"CARD","FIN","COUNTABLE","IS_MAP","SVM","ONE_1_MAP"}], 
		["1",{"CARD","FIN","COUNTABLE","NONNULL"}],
		["2",{"CARD","FIN","COUNTABLE","NONNULL"}],
		["3",{"CARD","FIN","COUNTABLE","NONNULL"}],
		["ZA",{"CARD","ORD","INFIN","COUNTABLE","NONNULL"}], ["RA",{"INFIN","COUNTABLE","NONNULL"}],
		["RE",{"INFIN","NONNULL"}], ["HFIN",{"INFIN","COUNTABLE","NONNULL"}],
		["CM",{{["RE","RE"]},"INFIN","NONNULL"}],
		["SI",{"INFIN","NONNULL"}],
-- ONLY A FEW OF THE CONSTANTS WHICH FOLLOW ARE DESCRIBED AS BEING NONNULL, ALTHOUGH THEY ALL ARE
		["S_0",{"SI"}],  ["S_1",{"SI"}], ["RA_0",{"RA"}], ["RA_1",{"RA"}], ["R_0",{"RE"}], ["R_1",{"RE"}], ["C_0",{"CM",["RE","RE"]}], ["C_1",{"CM",["RE","RE"]}],
		["RASEQ",   {{{["ZA","RA"]},"IS_MAP","SVM","COUNTABLE"},"NONNULL","INFIN"}],
		["RACAUCHY",{{"RASEQ",{["ZA","RA"]},"IS_MAP","SVM","COUNTABLE"},"NONNULL","INFIN"}],
		["RESEQ",   {{{["ZA","RE"]},"IS_MAP","SVM","COUNTABLE"},"NONNULL","INFIN"}],
		["RECAUCHY",{{"RESEQ",{["ZA","RE"]},"IS_MAP","SVM","COUNTABLE"},"NONNULL","INFIN"}],
		["RA0SEQ",{"RASEQ",{["ZA","RA"]},"NONNULL","INFIN","IS_MAP","SVM","COUNTABLE"}],
		["RA1SEQ",{"RASEQ",{["ZA","RA"]},"IS_MAP","SVM","NONNULL","INFIN","COUNTABLE"}],
		["RE0SEQ",{"RESEQ",{["ZA","RE"]},"NONNULL","INFIN","IS_MAP","SVM","COUNTABLE"}],
		["RE1SEQ",{"RESEQ",{["ZA","RE"]},"IS_MAP","SVM","NONNULL","INFIN","COUNTABLE"}]
	};
var unary_closed_on := {		-- MAPS UNARY OPERATORS AND FUNCTIONS TO COLLECTIONS OF SETS ON WHICH THEY ARE KNOWN TO BE CLOSED
		["S_REV",{"SI"}], ["RA_REV",{"RA"}], ["RA_ABS",{"RA"}],
		["R_REV",{"RE"}], ["ABS",{"RE"}], ["C_REV",{"CM"}], ["INV",{"HFIN"}],
		["RAS_REV",{{["ZA","RA"]}}], ["RAS_ABS",{{["ZA","RA"]}}], ["RES_REV",{{["ZA","RE"]}}], ["RES_ABS",{{["ZA","RE"]}}]
	};
var binary_closed_on := {		-- MAPS BINARY OPERATORS AND FUNCTIONS TO COLLECTIONS OF SETS ON WHICH THEY ARE KNOWN TO BE CLOSED
		["ast_add",{"CARD","ZA","ORD"}], ["ast_mult",{"CARD","ZA","ORD"}],
		["DOT_PLUS",{"CARD","ZA"}], ["DOT_TIMES",{"CARD","ZA"}], -- NEEDED??
		["DOT_MINUS",{"CARD","ZA"}], ["DOT_OVER",{"ZA"}],
		["DOT_S_PLUS",{"SI"}], ["DOT_S_TIMES",{"SI"}], ["DOT_S_MINUS",{"SI"}],
		["DOT_RA_PLUS",{"RA"}], ["DOT_RA_TIMES",{"RA"}], ["DOT_RA_MINUS",{"RA"}],
		["DOT_R_PLUS",{"RE"}], ["DOT_R_TIMES",{"RE"}], ["DOT_R_MINUS",{"RE"}],  
		["DOT_C_PLUS",{"CM"}], ["DOT_C_TIMES",{"CM"}], ["DOT_C_MINUS",{"CM"}],
		["DOT_RAS_PLUS",{"RASEQ"}], ["DOT_RAS_TIMES",{"RASEQ"}], ["DOT_RAS_MINUS",{"RASEQ"}],
		["DOT_RES_PLUS",{{["ZA","RE"]}}], ["DOT_RES_TIMES",{{["ZA","RE"]}}], ["DOT_RES_MINUS",{{["ZA","RE"]}}],
		["DOT_PROD",{"NONNULL","FIN","COUNTABLE"}], ["AT_",{"FIN","HFIN","COUNTABLE","SVM","ONE_1_MAP"}]
	};
	
var finitely_additive_descriptors := {			-- BASIC DESCRIPTORS KNOWN TO BE FINITELY ADDITIVE
			"ZA", "ORD", "FIN", "HFIN", "COUNTABLE", "CARD", "IS_MAP", {"ZA"} };
var fully_additive_descriptors := {				-- BASIC DESCRIPTORS KNOWN TO BE UNCONDITIONALLY ADDITIVE
			"ORD", "IS_MAP", {"ZA"}};
var quasi_additive_descriptors := {		-- BASIC DESCRIPTORS KNOWN TO BE UNCONDITIONALLY ADDITIVE (IF THE OVERALL ARGUMENT IS NONNULL)
			"INFIN","NONNULL"};
var inherited_descriptors := {					-- BASIC DESCRIPTORS KNOWN TO BE INHERITED BY SUBSETS
			"FIN","HFIN","COUNTABLE","IS_MAP","SVM","ONE_1_MAP"};
var transitive_descriptors := {				-- BASIC DESCRIPTORS KNOWN TO BE INHERITED BY MEMBERS
			"ZA","HFIN","ORD"};
procedure init_proof_by_structure();
--print("first_proof_by_structure", first_proof_by_structure);
	
if first_proof_by_structure then
		first_proof_by_structure := false;
		basic_sets +:= domain descriptors_of_basic - basic_preds;
		basic_descriptors :=  (basic_sets + basic_preds) with "OM";
		for k in ["0","1","2","3"] loop
			const_properties(k) := const_properties(k) + transitive_descriptors;
		end loop;
		for x in domain(const_properties)  * basic_descriptors loop
			const_properties(x) := extended_descriptors3(const_properties(x) with {x});
		end loop;
--print("const_properties: ", const_properties);
		m_descriptors_of_basic := {[m, mm + descriptors_of_basic(arb(mm))]: mm = m_descriptors_of_basic(m)};
--print("m_descriptors_of_basic: ", m_descriptors_of_basic);
	end if;
end init_proof_by_structure;
procedure expression_descriptor(expn_stg,vars_to_descriptors);			
		-- Calculates result descriptor set given an expression in the form of string (to be internally parsed into a tree)
		-- and the descriptors applicable to its free variables.
		-- This is the main routine of the the proof_by_structure system. We are concerned here with expressions of two
		-- principal kinds, simple expressions and setformer expressions.
		-- Simple expressions are processed recursively, descriptors being attached to all their nodes from bottom to top 
		-- in accordance with the expressions found at each node.
		-- Conditional expressions of the form 'if c1 then e1 elseif c2 then e2.. end if' are treated as intersections of
		-- their 'e' parts;  set-theoretic expressions have the form {e(x,y,..): x in s1, y in s2 | C } etc.
		-- We collect relevant clauses from the condition part C, and then calculate descriptors for all of the bounding sets 
		-- s1, s2,... This gives us descriptors for their members x,y,.., which are then used to calculate 
		-- a descriptor set D for e(x,y,..). The setformer then gets the descriptor {D}, to which Fin and Countable
		-- are attached if all the s1, s2,... have these descriptors, and NonNull is attached if all the sj are NonNull
		-- and the condition C is missing.
		
		tree := parse_expr(expn_stg + ";")(2);			-- get the parse tree of the expression
		
init_proof_by_structure();
		return expression_descriptor_in(tree,vars_to_descriptors);			-- call the recursive workhorse
					-- this will return the set of descriptors which attach to the top-level expression
		
end expression_descriptor;
procedure expression_descriptor_in(tree,vars_to_descriptors);			-- recursive workhorse for 'expression_descriptor' (debugging wrapper)
	res := expression_descriptor_inn(tree,vars_to_descriptors);
--print("expression_descriptor_in: ",tree," vars_to_descriptors: ",vars_to_descriptors," res: ",res);
	return if res = {} then {"OM"} elseif res /= {"OM"} then res less "OM" else res end if;
end expression_descriptor_in;
procedure expression_descriptor_inn(tree,vars_to_descriptors);			-- recursive workhorse for 'expression_descriptor'		
		-- returns the set of known descriptors of the top node of a tree
 --print("expression_descriptor_inn: ",tree,"\n",vars_to_descriptors);		
		-- this routine also exploits known closure properties of unary and binary operators and functions of several parameters.
		-- Whenever such an operator or function is encountered, we find the set on which it is closed. If this appears in the
		-- descriptors of both arguments (or one argument in the case of unaries) it is transmitted to the result
	
	
	if is_string(tree) then return (vars_to_descriptors(tree)?{}) + (const_properties(tree)?{}); end if; 
				-- if simple variable or constant, then return its given or known descriptor
	
	buffer_set := vars_to_descriptors(unparse(tree)) ? {};
	[n1,n2,n3] := tree;				-- otherwise decompose the tree at its top level, allowing for two arguments
									-- [n1,n2,n3] is [op,arg1,arg2]
									-- we check first for descriptors available because of general transitive closure properties,
									-- and return these if there are any
	if n1 = "TILDE_" then
		if #n3 /= 2 then buffer_set with:= "OM"; end if;
		n3 := n3(2);
	end if; 
	
--	if (closure_sets := unary_closed_on(n1)) /= OM then		-- this is a non-built-in operator closed on some basic set 
--		arg_descs := expression_descriptor_in(n2,vars_to_descriptors);		-- get the descriptors of the operator argument
--		ok_descs := closure_sets * arg_descs;
--		if ok_descs /= {} then buffer_set +:= ok_descs; end if;		-- if transitivity generates a useful result, return it
--	end if;
	if (closure_sets := binary_closed_on(n1)) /= OM then		-- this is a non-built-in binary operator closed on some basic set 
		arg_descs1 := expression_descriptor_in(n2,vars_to_descriptors);		-- get the descriptors of the operator argument
		arg_descs2 := expression_descriptor_in(n3,vars_to_descriptors);		-- get the descriptors of the operator argument
		ok_descs := closure_sets * arg_descs1 * arg_descs2;
--print("binary_closed_on(n1): ",n1," ",closure_sets," ",arg_descs1," ",arg_descs2," ok_descs: ",ok_descs);
		if ok_descs /= {} then buffer_set +:= ok_descs; end if;		-- if transitivity generates a useful result, return it
	end if;
	
	if n1 = "ast_of" and (closure_sets := unary_closed_on(n2)) /= OM then		-- unary function case; 
					-- these are non-built-in; abs is special since it is an operator
		arg_descs := expression_descriptor_in(n3(2),vars_to_descriptors);
		ok_descs := closure_sets * arg_descs;			-- see if any of the closure sets is available as an arg descriptor
--print("closure_sets: ",closure_sets," ",n3(2)," arg_descs: ",arg_descs);
		if ok_descs /= {} then buffer_set +:= ok_descs; end if;		-- if transitivity generates a useful result, return it
	end if;
	
			-- Note: the following 'actions' are found in the library of procedures given at the beginning of this package
	action := unary_op_actions(n1)?binary_op_actions(n1)?if n1 = "ast_of" then (fanp := (function_actions_and_numpars(n2))?[])(1) end if;
						-- get the action associated with the function at this node, if any 
--print("expression_descriptor_in: ",tree," ",action," ",descriptor_action_of_intersection);	
	if n1 notin {"ast_enum_set","ast_genset","ast_if_expr"} and action = OM then buffer_set with:= "OM"; end if;
						-- if there is none, we know nothing, so return the descriptor 'generic set'
	
	case n1				-- handle the various possible cases separately
	
		when "ast_arb","ast_range","ast_domain","ast_nelt","ast_pow" =>		-- unary operators
			argdescs := expression_descriptor_in(n2,vars_to_descriptors);		-- get the descriptors of the argument expression
--print("unary operator: ",n1," ",n2," ",argdescs," vars_to_descriptors: ",vars_to_descriptors);
			buffer_set +:= action(argdescs);		-- apply the action relevant at this node
			
		when "ast_add","ast_mult","ast_sub","ast_enum_tup","DOT_PROD","TILDE_","AT_" => 		-- binary operators
			if n1 = "ast_enum_tup" and #tree /= 3 then buffer_set with:= "OM"; end if;
			argdescs1 := expression_descriptor_in(n2,vars_to_descriptors);		-- get the descriptors of the argument expression
			argdescs2 := expression_descriptor_in(n3,vars_to_descriptors);		-- get the descriptors of the argument expression
--print("binary operator: ",n1," ",n2," ",n3," ",argdescs1," ",argdescs2);
			buffer_set +:= action(argdescs1,argdescs2);		-- apply the action relevant at this node
	
		when "ast_of" =>		-- function application
			
			arglist := n3(2..);		-- get the list of argument expressions of the function application
			argdescs1 := expression_descriptor_in(arglist(1),vars_to_descriptors);		-- get the descriptors of the argument expressions
			if (np := fanp(2)?0) > 1 then argdescs2 := expression_descriptor_in(arglist(2),vars_to_descriptors); end if;
			if np > 2 then argdescs3 := expression_descriptor_in(arglist(3),vars_to_descriptors); end if;
--print("np: ",np," ",action," ",descriptor_action_of_Un);			
			if np = 1 then buffer_set +:= action(argdescs1); end if;
			if np = 2 then buffer_set +:= action(argdescs1,argdescs2); end if;
			if np = 3 then buffer_set +:= action(argdescs1,argdescs2,argdescs3); end if;		-- other cases can obviously be added
		when "ast_enum_set" =>			-- enumerated sets: find the common descriptors of all the elements
			descs := expression_descriptor_in(n2,vars_to_descriptors);	-- the members are the tree elements after n1
--print("ast_enum_set: ",descs," ",n2);
			descs := descs */[expression_descriptor_in(memb,vars_to_descriptors): memb = tree(j) | j > 2];
			buffer_set +:= {{d}: d in descs} + {"FIN","NONNULL"} + -- the result is of course finite and nonempty
				 if (forall tree_j = tree(j) | j = 1 or
					(exists d in expression_descriptor_in(tree_j,vars_to_descriptors) | is_pair(d)))
				then
					{"IS_MAP"}
				else
					{}
				end if;
					-- We are checking that there exists at least one pair d among the descriptors
					--  of each enumerated element, to allow for sets of the type
					-- {[x,y], a}, where a has a pair as descriptor. However, its efficiency
					-- can be improved, as expression_descriptor_in gets computed one more time.
-- The following extension to the treatment of maps would be unsound:
--
--			if "IS_MAP" in buffer_set then
--
--				is_svm := true;
--
--				map_check := {};
--
--				for j in [2..#tree] loop
--
--					if is_tuple(p := tree(j)) and (p(1) = "ast_enum_tup") and (#p = 3) and (map_check(p(2)) = OM) then
-- 
--				                       map_check(p(2)) := p(3);
--					else
--                        				is_svm := false;
--					end if;
--				end loop;
--
--				if (is_svm) then
--
--					buffer_set with:= "SVM";
--
--					if (#domain(map_check) = #range(map_check)) then buffer_set with:= "ONE_1_MAP"; end if;
--				end if;
--			end if;
 
		when "ast_genset" =>			-- setformer; see comment above
				-- a sample template, for {e(x,y): x in s, y in t | P(x,y)}, is
				--  ["ast_genset", tree_of_e(x,y), ["ast_iter_list", ["ast_in", "X", "S"], ["ast_in", "Y", "T"]], tree_of_P(x,y)]
				-- if the condition is void, we have instead
				--  ["ast_genset", tree_of_e(x,y), ["ast_iter_list", ["ast_in", "X", "S"], ["ast_in", "Y", "T"]], ["ast_null"]]
				
				lc1 := (limiting_clauses := n3(2..))(1);		-- the limiting clauses of the setformer
				bound_var_descs := {};			-- these will be collected as we go along in the limiting clauses
				restriction_set_descs := [];	-- we also collect the descriptors of the restriction sets themselves
				
							-- get the descriptor of the first limiting set
				restriction_set_descs with:= (descs_ls1 := expression_descriptor_in(lc1(3),vars_to_descriptors));
--print("lc1(3): ",lc1(3)," ",vars_to_descriptors," restriction_set_descs: ",restriction_set_descs);
				bound_var_descs(lc1(2)) :=	if lc1(1) = "ast_in" then descriptor_action_of_memb(descs_ls1)  
							elseif lc1(1) = "DOT_INCIN" then descriptor_action_of_subset(descs_ls1)
							else {} end if;		-- allow for inclusion-form iterators
							-- progressing from left to right, get the descriptors of the other limiting sets,
				for j in [2..#limiting_clauses] loop
					lcj := limiting_clauses(j);		-- the limiting clauses of the setformer
				
							-- get the descriptor of the jth limiting set
					restriction_set_descs with:= (descs_lsj := expression_descriptor_in(lcj(3),vars_to_descriptors + bound_var_descs));
							-- meanwhile assigning descriptors to the bound variables as they are encountered
					bound_var_descs(lcj(2)) :=
						if lcj(1) = "ast_in" then descriptor_action_of_memb(descs_lsj)
						elseif lcj(1) = "DOT_INCIN" then descriptor_action_of_subset(descs_lsj)
						else {} end if;		-- allow for inclusion-form iterators
				end loop;
				
				 			-- recursively calculate the descriptor of the lead expression
				 vars_to_descriptors_save := vars_to_descriptors;
				 					-- save, in case some of the bound variables have the same name as some of the free variables
				for [v,descs] in bound_var_descs loop vars_to_descriptors(v) := descs; end loop;
				descs_lead := expression_descriptor_in(n2,vars_to_descriptors);
--print(" descs_lead: ",descs_lead," vars_to_descriptors: ",vars_to_descriptors," n2: ",n2);				
				vars_to_descriptors := vars_to_descriptors_save; -- restore, so as not to affect the external environment
				 
				descs_lead := {{x}: x in descs_lead}; -- form the descriptor for a set of elements of this kind
-- n1 is "ast_genset"
-- n2 is of the form ["ast_enum_tup","x",tree_of_e(x)]
-- n3 is of the form ["ast_iter_list",["ast_in","x","s"]]
-- or of the equivalent form in which "ast_in" is replaced by "DOT_INCIN"
				if n2(1) = "ast_enum_tup" then
					lhss := {x(2): x = n3(i) | i > 1 and (x(1) = "ast_in" or x(1) = "DOT_INCIN")}; -- variables bound by iterators
					if (#lhss = #n3 - 1) -- viz., each iterator binds a distinct variable
						and
							if #lhss = 1 then n2(2) = arb(lhss)
							else is_tuple(n22 := n2(2)) and n22(1) = "ast_enum_tup" and
								#n22 = 3 and {n22(2),n22(3)} = lhss
							end if
						and
							all_conss(n2(3)) = lhss
					then
						descs_lead with:= "ONE_1_MAP";
					end if;
					if ("ONE_1_MAP" in descs_lead) or (is_string(n2(2)) and -- we check that we have a pair [x,e(x)], where x is indeed a variable
					   n3(1) = "ast_iter_list" and -- we check that we indeed have a list of iterators
					   #n3 < 3 and -- we check that we have only one iterator and no condition P(x)
					   ( (iter_type := n3(2)(1)) = "ast_in" or iter_type = "DOT_INCIN" ))
					then
 
						  descs_lead with:= "SVM";
					end if;
					descs_lead with:= "IS_MAP";
				end if;
--print("restriction_set_descs: ",restriction_set_descs);
									-- if all of the limiting sets are known to be finite, the result is finite
				if (forall de in restriction_set_descs | "FIN" in de) then descs_lead with:= "FIN"; end if;
							-- the setformer is countable if every restriction set is either finite or countable and a membership restriction 
				if (forall de = restriction_set_descs(j) | ("COUNTABLE" in de or "FIN" in de) 
								and (not("FIN" notin de and limiting_clauses(j)(1) = "DOT_INCIN"))) then descs_lead with:= "COUNTABLE"; end if;
					
									-- if all of the limiting sets are known to be NonNull and there is no condition the result is NonNull
				if (forall de = restriction_set_descs(j) | "NONNULL" in de) and tree(4) = ["ast_null"] then descs_lead with:= "NONNULL"; end if;
									-- if all of the limiting sets are known to be NonNull and there is no condition and one is infinite the result is Infinite
				if (forall de = restriction_set_descs(j) | ("NONNULL" in de or "INFIN" in de)) and 
							(exists de = restriction_set_descs(j) | "INFIN" in de) and tree(4) = ["ast_null"] and
					all_conss(n2) = {x(2): x = n3(i) | i > 1 and (x(1) = "ast_in" or x(1) = "DOT_INCIN")}
				then descs_lead with:= "INFIN"; end if;
				buffer_set +:= descs_lead;			-- return the calculated descriptors
		when "ast_if_expr" =>			-- conditional expression; find the common descriptors of both branches
			-- template is ["ast_if_expr", "A", "B", ["ast_if_expr", "C", "D", "E"]]
			argdescs1 := expression_descriptor_in(n3,vars_to_descriptors);		-- get the descriptors of the first branch
			argdescs2 := expression_descriptor_in(tree(4),vars_to_descriptors);		-- get the descriptors of the second branch
--print("ast_if_expr: ",argdescs1," ",argdescs2);			
			buffer_set +:= argdescs1 * argdescs2;				-- return those descriptors that are common to both branches
	end case;
	
	return buffer_set;
end expression_descriptor_inn;
procedure all_conss(tree);		-- test a tree for being all cons operators, and return the set of its variables
		return
			if is_string(tree) then {tree}
			elseif is_tuple(tree) and tree(1) = "ast_enum_tup" and #tree = 3 then
							-- if not all cons operators then return OM
				if (left := all_conss(tree(2))) = OM or  (right := all_conss(tree(3))) = OM then OM
				else left + right end if
			else
				OM
			end if;
end all_conss;
procedure extract_relevant_descriptors(conj_tree);	-- extract relevant descriptors given tree of a conjunction	
	-- this involves information concerning membership, equality, and inclusion; also particular predicates
	-- it looks for Ref clauses of various forms directly expressible as descriptors, and collects these descriptors into a set which is returned.
	-- the clauses which can be handled in this way, and their translations, are x in Za, x in Re, Card(x), Ord(x), and in the other elementary sets, 
	-- which translate into Za, Re, Card, Ord, etc.; x •incin Za etc., which translate into 
	-- {Za}, {Re}, {Card}, {Ord}, etc.  Finite(x) and Countable(x), which become Fin and Countable; not Finite(x) which becomes Infin;
	-- and x /= 0 which becomes NonNull. This extraction is iterated twice to handle cases like 'x in n and n in Za', and also handles cases like
	-- 'x /= 0 and y incs x' and 'Finite(x) and x incs y'. We also handle Svm and one_1_map.
	-- note that this routine works with variables only, not with subexpressions, so in some cases local variables will need to be defined in
	-- proofs and properties stated for these variables.
	init_proof_by_structure();
	debug_extract := [memb_conjs,inc_conjs,equality_conjs] := extract_relevant_conjuncts(debug_conj_tree := conj_tree);
					-- walk the tree, collecting the relevant top-level conjuncts. This adds non-nullity assertions whenever memberships are found.
		-- dummy inclusions like [x,"FIN"] are used to represent the predicates Fin, Svm, Is_map, and one_1_map
		-- dummy inclusions like ["Nonull",x]  are used to represent the predicates 'x /= 0' and 'not Finite(x)'
		-- Note the use of partially lowercase forms, which will distinguish these from actual scenario clauses which resemble them                                             
		
		-- an attempt is made to identify the range and domain of variables for which Is_map(f) is asserted
		-- equalities with simple variables on one side are treated in a special way by calculating descriptors for their right-hand side
		-- and propagating them to the left-hand variable. This makes it possible to identify some setformers that give rise 
		-- to mappings, single-valued mappings, and 1-1 mappings, or to subsets of known sets.
		
	vars_to_descriptors := {[v,w] in memb_conjs | w in basic_descriptors} + {[v,{w}]: [v,w] in inc_conjs | w in basic_descriptors};
				-- preliminary conversion into map from vars to descriptors, as multivalued map
--print("\nmemb_conjs before loop: ",memb_conjs); print("inc_conjs before loop: ",inc_conjs); print("equality_conjs before loop: ",equality_conjs); 
--print("vars_to_descriptors before loop: ",vars_to_descriptors); 
	
				-- now we use a bit of transitive closure: if a in b is available and b incin basic_descriptor
				-- is present, we collect a in basic_descriptor; if a incin b is available and b incin basic_descriptor
				-- is present, we collect a incin basic_descriptor; if a in Za is available we collect a incin Za, and likewise
				-- for ordinals and cardinals. This process is continued as long as we are finding more
				-- primitives like Is_map, Svm, and Fin are treated a bit specially because of their relationship to inclusion,
				-- as is Infin.
				
	something_changed := true;			-- force entry to loop
	debug_count := 0;
	
	while something_changed loop			-- this propagation loop iterates as long as new attributes are being found
											-- for the variables which it encounters. 
		something_changed := false;			-- but maybe something will change in the body of the loop
			-- set inclusions are used to propagate all inheritable descriptors from their right-hand side to their  
			-- left, and to propagate all membership relationship involving their left-hand sides to their right.
			if (debug_count +:= 1) > 10 then exit; end if;		-- limit to at most 10 iterations
			newincs := {[s1,s3]: [s1,s2] in inc_conjs, s3 in inc_conjs{s2}} - inc_conjs;		-- exploit the transitivity of inclusion
			if newincs /= {} then		-- expand the set of known inclusions
				inc_conjs +:= newincs; something_changed := true;		-- and flag for another iteration
			end if;
			newmembs := {[s1,s3]: [s1,s2] in memb_conjs, s3 in inc_conjs{s2}};
			newmembs -:= memb_conjs;
			if newmembs /= {} then		-- expand the set of known inclusions
				memb_conjs +:= newmembs; something_changed := true;		-- and flag for another iteration
			end if;
			
			vars_to_descriptors +:= {[u,{v}]: [u,v] in inc_conjs | is_string(u) and u notin basic_preds and v in basic_descriptors}
										+ {[u,v]: [u,v] in memb_conjs | is_string(u) and u notin basic_preds and v in basic_descriptors}
										+ {[u,"NONNULL"]: u in inc_conjs{"NONNULL"} | is_string(u) and u notin basic_descriptors}
										+ {[u,"INFIN"]: u in inc_conjs{"INFIN"} | is_string(u) and u notin basic_descriptors};
			
									-- convert the conjuncts we have so far to a vars_to_descriptors multivalued map
									-- for each memb_conj [u,v], we get all the descriptors of v given the 
									-- current vars_to_descriptors,
									-- and of thoos take the ones which are sets, whos menbers become descriptors of x.  
									-- for each inc_conj [u,v], we get all the descriptors of v given the 
									-- current vars_to_descriptors,
									-- and of those take the ones which are sets, which become descriptors of x.
--print("propagation iteration " + debug_count + ": vars_to_descriptors in extract_relevant_descriptors: ",vars_to_descriptors);									
			for [u,v] in memb_conjs loop 			-- v can be the tree of an expression
				if is_string(u) then
					vtd := {[x,vars_to_descriptors{x}]: x in domain(vars_to_descriptors)}; -- convert to set-valued map, as required by expression_descriptor_in
					expn_descriptors := expression_descriptor_in(v,vtd);
							-- call the recursive workhorse to get the descriptor set of the expression
					new_u := {};
	
				
					if v in basic_preds then
						new_u := {[u,d]: d in descriptors_of_basic(v) with v};
	 				elseif u notin basic_preds then --?? wouldn't this be an indication of an earlier bug? [ALEXANDRU]
						new_u := {[u,d]: d in descriptor_action_of_memb(expn_descriptors)};
	
				end if;
					
					if not( vars_to_descriptors incs new_u) then
						vars_to_descriptors +:= new_u;
						something_changed := true;
					end if;
				end if;
			end loop;
									
			for [u,v] in inc_conjs loop 			-- v can be the tree of an expression
				vtd := {[x,vars_to_descriptors{x}]: x in domain(vars_to_descriptors)};		-- convert to set-valued map, as required by expression_descriptor_in
				expn_descriptors := expression_descriptor_in(v,vtd);
							-- call the recursive workhorse to get the descriptor set of the expression
				vars_to_descriptors +:= ({[u,ed]: ed in expn_descriptors | (is_set(ed) or (ed in inherited_descriptors and u notin basic_descriptors))} 
						+ {[u,ed]: ed in expn_descriptors | is_string(ed) and (ed notin basic_descriptors) and (u = "INFIN" or u = "NONNULL")});
			end loop;		-- end of loop for initial propagation by transitive closure
			
									-- for equalities, we calculate the descriptor set of the right hand side using 
									-- the available vars_to_descriptors and propagate it to the left-hand variable.
					-- here expn is the parse tree of an expression
			for [v,expn] in equality_conjs loop			-- iterate over all available equalities
				if v notin basic_sets then
					vtd := {[x,vars_to_descriptors{x}]: x in domain(vars_to_descriptors)};		-- convert to set-valud map, as required by expression_descriptor_in
				
					expn_descriptors := expression_descriptor_in(expn,vtd);
							-- call the recursive workhorse to get the descriptor set of the expression
-- look for the new_descriptors for this variable
--					new_descriptors := expn_descriptors - (desv := vars_to_descriptors{v}); 
					new_descriptors := expn_descriptors + vars_to_descriptors{unparse(expn)} - (desv := vars_to_descriptors{v});
--print("vars_to_descriptors equality loop: ",v," ",vars_to_descriptors," expn_descriptors: ",expn_descriptors," new_descriptors: ",new_descriptors);
					if new_descriptors /= {} then vars_to_descriptors{v} := desv + new_descriptors; something_changed := true; end if;
				end if;	
			end loop;
			-- if any of these operations extend the set of descriptors available for any variable, we set the 'something_changed'
			-- to true, thereby forcing another iteration.
				
	end loop;		-- on exiting this loop we have collected all relevant descriptors
			
	vars_to_descriptors +:= ({[v,"INFIN"]: v in inc_conjs{"INFIN"} | is_string(v)} + {[v,"NONNULL"]: v in inc_conjs{"NONNULL"} | is_string(v)}); 
									-- convert the INFIN and NONULL special inclusions to standard descriptors
	
	res := {[x,vars_to_descriptors{x}]: x in domain(vars_to_descriptors) | x notin basic_descriptors};
								-- convert into map-to-sets and return
				
--print("\ndebug_count: ",debug_count," vars_to_descriptors after loop exit: ",res," inc_conjs after loop exit: ",inc_conjs); 
	return res;
				
end extract_relevant_descriptors;
procedure extract_relevant_conjuncts(tree);		-- walk the tree, collecting the relevant top-level conjuncts.
			-- the relevant conjuncts are those stating membrship or inclusion of one variable in another
	var memb_conjs := {};		-- conjuncts designating membership: will be collected as pairs
	var inc_conjs := {};		-- conjuncts designating inclusion: will be collected as pairs
	var equality_conjs := {};		-- conjuncts designating equalities to setformers: will be collected as pairs
	
	extract_relevant_conjuncts_in_new(tree);		-- call the recursive workhorse
--print("[memb_conjs,inc_conjs,equality_conjs]: ",[memb_conjs,inc_conjs,equality_conjs]);	
	return [memb_conjs,inc_conjs,equality_conjs];		-- return all the pairs collected
	
	procedure extract_relevant_conjuncts_in_new(tree);		-- internal recursive workhorse
--print("extract_relevant_conjuncts_in_new: ",tree);	
		if is_string(tree) then return; end if;		-- since we have hit bottom
		
		[n1,n2,n3] := tree;			-- decompose the node
		
		case n1
		
			when "ast_in" => 		-- collect if membership between variables
				
						-- memberships with compound right hand side are collected, since these can produce valuable descriptors
				memb_conjs with:= [unparse(n2),n3];
				inc_conjs with:= ["NONNULL",unparse(n3)];		-- memberships with simple right hand side are collected as assertions of non-nullity
			when "ast_incs" => 		-- reverse and collect if inclusion between variables
				
				if is_tuple(n3) and n3(1) = "ast_enum_set" then
					for j in [2..#n3] loop
              					memb_conjs with:= [unparse(n3(j)), n2];
       					end loop;
				else
				
					inc_conjs with:= [unparse(n3),n2];		-- inclusions with compound right hand side are collected, since these can produce valuable descriptors
				end if;
			when "DOT_INCIN" => 		-- collect if inclusion between variables
				if is_tuple(n2) and n2(1) = "ast_enum_set" then
					for j in [2..#n2] loop
               					memb_conjs with:= [unparse(n2(j)), n3];
       					end loop;
				else				
					inc_conjs with:= [unparse(n2),n3];		-- inclusions with compound right hand side are collected, since these can produce valuable descriptors
				end if;
--print("inc_conjs: ",inc_conjs);
			when "ast_ne" =>		-- collect assertions of non-nullity
				if n3 = "0" then	
					inc_conjs with:= ["NONNULL",unparse(n2)];			-- this is collected as an inclusion of a dummy  element
				elseif n2 = "0" then	
					inc_conjs with:= ["NONNULL",unparse(n3)];			-- this is collected as an inclusion of a dummy  element
				end if;
			when "ast_eq" =>		-- we are concerned here with just a few kinds of equality clauses
				-- (1) assertions domain(x) = y and range(x) = y between variables, which are collected into dom_conjs and range_conjs respectively
				-- (2) assertions of equality between a variable and a setformer whose lead expression is a pair
				-- and whose limiting sets are simple variables, e.g. {[e(x),f(x)]: x in s | P(x)} but not 
				-- {[e(x),f(x)]: x in (s + t) | P(x)}. These always define maps, and the closure properties of e(x) and 
				-- f(x) may allow something to be said concerning the domain and range of these maps. In some cases like
				-- {[x,f(x)]: x in s | P(x)} we may be able to say that the map is single valued, and in cases like
				-- {[[x,y],[y,x]]: x in s, y in t | P(x,y)} we may be able to say that the map is 1-1.
				if is_string(n2) then							-- variable equals setformer
						-- we collect equalities of all kinds, since even if they do not define maps we may be able to deduce something concerning their elements
					equality_conjs with:= [n2,n3]; 	
					if is_string(n3) then equality_conjs with:= [n3,n2]; end if;	-- collect symmetrically to propagate descriptors in both directions
				elseif is_string(n3) then							-- setformer equals variable
						-- we collect equalities of all kinds, since even if they do not define maps we may be able to deduce something concerning their elements
					equality_conjs with:= [n3,n2]; 
				end if;		-- these equalities should be used to propagate type info to the next level 
							-- 'expression_descriptor' routine that calls this routine.
				
			when "ast_of" => 		-- collect if this is a predicate we can handle, converting it to a nominal membership
		
				case n2				-- examine the predicate
					when "FINITE" => 		-- collect and convert into a nominal inclusion
						memb_conjs with:= [unparse(n3(2)),"FIN"];		
					when "COUNTABLE" => 		-- collect and convert into a nominal inclusion
						memb_conjs with:= [unparse(n3(2)),"COUNTABLE"];		
						
					when "SVM" => 		-- collect and convert into a nominal inclusion
						memb_conjs with:= [unparse(n3(2)),"SVM"];
					when "ONE_1_MAP" => 		-- collect and convert into a nominal inclusion
						memb_conjs with:= [unparse(n3(2)),"ONE_1_MAP"];
					when "IS_MAP" => 		-- collect and convert into a nominal inclusion
						memb_conjs with:= [unparse(n3(2)),"IS_MAP"];	
					when "ORD" => 		-- collect and convert into a descriptor
						memb_conjs with:= [unparse(n3(2)),"ORD"];	
					when "CARD" => 		-- collect and convert into a descriptor
						memb_conjs with:= [unparse(n3(2)),"CARD"];	
				end case;
		
			when "ast_not" => 		-- collect if this is a negated predicate we can handle, converting it to a nominal membership
				
				if is_tuple(n2) then
				
					[m1,m2,m3] := n2;			-- unpack the argument of the 'not'
					
					case m1
						when "ast_eq" =>		-- collect assertions of non-nullity
							
							if is_string(m2) and m3 = "0" then	
								inc_conjs with:= ["NONNULL",m2];			-- this is collected as an inclusion of a dummy  element
							elseif is_string(m3) and m2 = "0" then	
								inc_conjs with:= ["NONNULL",m3];			-- this is collected as an inclusion of a dummy  element
							end if;
						when "ast_of" =>		-- collect assertions of infiniteness
							if m2 = "FINITE" then
							
								inc_conjs with:= ["INFIN",unparse(m3(2))];			-- this is collected as an inclusion of a dummy  element
							
							end if;
					end case;
				end if;
				
				return;		-- what remains is opaque
			when "ast_and","AMP_" => 			-- process arguments recursively; note that conjunctions can have two syntactic forms
				
				extract_relevant_conjuncts_in_new(n2); extract_relevant_conjuncts_in_new(n3);
			otherwise => return;		-- what remains is opaque
			
		end case;
		
	end extract_relevant_conjuncts_in_new;
	
	procedure extract_relevant_conjuncts_in(tree);		-- internal recursive workhorse (OLD VERSION)
	
		if is_string(tree) then return; end if;		-- since we have hit bottom
		
		[n1,n2,n3] := tree;			-- decompose the node
		
		case n1
		
			when "ast_in" => 		-- collect if membership between variables
				
				if is_string(n2) then		-- memberships with compound right hand side are collected, since these can produce valuable descriptors
					memb_conjs with:= [n2,n3];
				end if;
				if is_string(n3) then		-- memberships with simple right hand side are collected as assertions of non-nullity
					inc_conjs with:= ["NONNULL",n3];
				end if;
				
				return;		-- what remains is opaque
			when "ast_incs" => 		-- reverse and collect if inclusion between variables
				
				if is_string(n3) then		-- inclusions with compound right hand side are collected, since these can produce valuable descriptors
					inc_conjs with:= [n3,n2];
				end if;
				
				return;		-- what remains is opaque
			when "DOT_INCIN" => 		-- collect if inclusion between variables
				
				if is_string(n2) then		-- inclusions with compound right hand side are collected, since these can produce valuable descriptors
					inc_conjs with:= [n2,n3];
				end if;
				
				return;		-- what remains is opaque
			when "ast_ne" =>		-- collect assertions of non-nullity
				if is_string(n2) and n3 = "0" then	
					inc_conjs with:= ["NONNULL",n2];			-- this is collected as an inclusion of a dummy  element
				elseif is_string(n3) and n2 = "0" then	
					inc_conjs with:= ["NONNULL",n3];			-- this is collected as an inclusion of a dummy  element
				end if;
				
				return;		-- what remains is opaque
			when "ast_eq" =>		-- we are concerned here with just a few kinds of equality clauses
				-- (1) assertions domain(x) = y and range(x) = y between variables, which are collected into dom_conjs and range_conjs respectively
				-- (2) assertions of equality between a variable and a setformer whose lead expression is a pair
				-- and whose limiting sets are simple variables, e.g. {[e(x),f(x)]: x in s | P(x)} but not 
				-- {[e(x),f(x)]: x in (s + t) | P(x)}. These always define maps, and the closure properties of e(x) and 
				-- f(x) may allow something to be said concerning the domain and range of these maps. In some cases like
				-- {[x,f(x)]: x in s | P(x)} we may be able to say that the map is single valued, and in cases like
				-- {[[x,y],[y,x]]: x in s, y in t | P(x,y)} we may be able to say that the map is 1-1.
				if is_string(n2) then							-- variable equals setformer
						-- we collect equalities of all kinds, since even if they do not define maps we may be able to deduce something concerning their elements
					equality_conjs with:= [n2,n3]; 	
					if is_string(n3) then equality_conjs with:= [n3,n2]; end if;	-- collect symmetrically to propagate descriptors in both directions
				elseif is_string(n3) then							-- setformer equals variable
						-- we collect equalities of all kinds, since even if they do not define maps we may be able to deduce something concerning their elements
					equality_conjs with:= [n3,n2]; 
				end if;
				
			when "ast_of" => 		-- collect if this is a predicate we can handle, converting it to a nominal membership
		
				case n2				-- examine the predicate
					when "FINITE" => 		-- collect and convert into a nominal inclusion
						if is_string(arg := n3(2))	then 		-- argument is a variable
							memb_conjs with:= [arg,"FIN"];		
						end if;
				
						return;		-- what remains is opaque
					when "COUNTABLE" => 		-- collect and convert into a nominal inclusion
						if is_string(arg := n3(2))	then 		-- argument is a variable
							memb_conjs with:= [arg,"COUNTABLE"];		
						end if;
				
						return;		-- what remains is opaque
						
					when "SVM" => 		-- collect and convert into a nominal inclusion
						if is_string(arg := n3(2))	then 		-- argument is a variable
							memb_conjs with:= [arg,"SVM"];
						end if;
				
						return;		-- what remains is opaque
					when "ONE_1_MAP" => 		-- collect and convert into a nominal inclusion
						if is_string(arg := n3(2))	then 			-- argument is a variable
							memb_conjs with:= [arg,"ONE_1_MAP"];
						end if;
				
						return;		-- what remains is opaque
					when "IS_MAP" => 		-- collect and convert into a nominal inclusion
						if is_string(arg := n3(2))	then 			-- argument is a variable
							memb_conjs with:= [arg,"IS_MAP"];	
						end if;
				
						return;		-- what remains is opaque
					when "ORD" => 		-- collect and convert into a descriptor
						if is_string(arg := n3(2))	then 			-- argument is a variable
							memb_conjs with:= [arg,"ORD"];	
						end if;
				
						return;		-- what remains is opaque
				end case;
		
			when "ast_not" => 		-- collect if this is a negated predicate we can handle, converting it to a nominal membership
				
				if is_tuple(n2) then
				
					[m1,m2,m3] := n2;			-- unpack the argument of the 'not'
					
					case m1
						when "ast_eq" =>		-- collect assertions of non-nullity
							
							if is_string(m2) and m3 = "0" then	
								inc_conjs with:= ["NONNULL",m2];			-- this is collected as an inclusion of a dummy  element
							elseif is_string(m3) and m2 = "0" then	
								inc_conjs with:= ["NONNULL",m3];			-- this is collected as an inclusion of a dummy  element
							end if;
						when "ast_of" =>		-- collect assertions of infiniteness
							if m2 = "FINITE" then
							
								if is_string(arg := m3(2))	then 			-- argument is a variable
									inc_conjs with:= ["INFIN",arg];			-- this is collected as an inclusion of a dummy  element
								end if;
							
							end if;
					end case;
				end if;
				
				return;		-- what remains is opaque
			when "ast_and","AMP_" => 			-- process arguments recursively; note that conjunctions can have two syntactic forms
				
				extract_relevant_conjuncts_in(n2); extract_relevant_conjuncts_in(n3);
			otherwise => return;		-- what remains is opaque
			
		end case;
		
	end extract_relevant_conjuncts_in;
	
end extract_relevant_conjuncts;
procedure get_assertions_from_descriptors(vars_to_descriptors);	-- convert descriptors to set membership assertions for use in ELEM deductions
	-- this looks for descriptors of various forms directly expressible as Ref clauses, and generates these clauses.
	-- the descriptors which can be handled in this way, and their translations, are Za, Re, Card, Ord, and the other elementary sets, 
	-- which translate into x in Za, x in Re, Card(x), Ord(x), etc.; {Za}, {Re}, {Card}, {Ord}, which translate into 
	-- x •incin Za etc., Fin and Countable, which become Finite(x) and Countable(x); Infin which becomes not Finite(x);
	-- and NonNull, which becomes x /= 0. We also handle Svm and one_1_map.
	
	new_vars_to_descriptors := vars_to_descriptors;		-- make copy to extend
--print("vars_to_descriptors: ",vars_to_descriptors);	
	for [x,descs] in vars_to_descriptors loop
		if "CARD" in descs then descs with:= "ORD"; end if;
		for m in (domain m_descriptors_of_basic) loop
			if (descs incs m) then descs +:= m_descriptors_of_basic(m); end if;
		end loop;
		new_vars_to_descriptors(x) := descs;
	end loop;
--print("new_vars_to_descriptors: ",new_vars_to_descript
 
             
             
            