% -----------------------------------------------------------------------------
%  (C) Altran Praxis Limited
% -----------------------------------------------------------------------------
% 
%  The SPARK toolset is free software; you can redistribute it and/or modify it
%  under terms of the GNU General Public License as published by the Free
%  Software Foundation; either version 3, or (at your option) any later
%  version. The SPARK toolset is distributed in the hope that it will be
%  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
%  Public License for more details. You should have received a copy of the GNU
%  General Public License distributed with the SPARK toolset; see file
%  COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy of
%  the license.
% 
% =============================================================================

%###############################################################################
% PURPOSE
%-------------------------------------------------------------------------------
% Provide inference facilities.
%###############################################################################

%===============================================================================
% infer(+Formula, -HypList).
%-------------------------------------------------------------------------------
% Try to infer Formula.  If it succeeds, HypList is instantiated to the
% list of hypotheses which were used to perform the inference. The
% inference is achieved through a collection of heuristic strategies.
%===============================================================================

infer(A, Hs) :-
    retractall(inference_depth_limit(_,_)),
    asserta(inference_depth_limit(main, 5)),        /* limit on recursion */
    asserta(inference_depth_limit(infrule, 3)),     /* for a fresh goal */
    retractall(used(_)),
    retractall(buffered_used_fact(_,_,_)),
    !,
    (
        infer_subgoal(A, Hs)
    ;
        allow_new_strategies,
        is_inequality(A, Left, Right, _Op),
	!,
        find_mutual_types(Left, Right, Type),
        try_new_numeric_strategies(A, Type, Hs)
    ),
    !.

%-------------------------------------------------------------------------------

infer_subgoal(A, _) :- var(A), !, fail.

infer_subgoal(A, Hs) :-
    (
        simplification_is_on,
        simplify(A, B),
        !,
        (
          see_if_can_infer(B, Hs)
        ;
          standardise_in_infer(on),
          norm_typed_expr(A, boolean, C),
          see_if_can_infer(C, Hs)
        )
    ;
        see_if_can_infer(A, Hs)
    ;
        standardise_in_infer(on),
        norm_typed_expr(A, boolean, B),
        A \= B,
        see_if_can_infer(B, Hs)
    ),
    !.

%-------------------------------------------------------------------------------


see_if_can_infer(X, Hs) :-
    could_infer(X, Hs),
    !.

see_if_can_infer(X, _) :-
    could_not_infer(X),
    !,
    fail.

see_if_can_infer(X, Hs) :-
    do_infer(X, Hs),
    assertz(could_infer(X, Hs)),
    !.

see_if_can_infer(X, _) :-
    assertz(could_not_infer(X)),
    !,
    fail.

%-------------------------------------------------------------------------------
do_infer(A=A, []) :- !.

do_infer(A<>A, _) :- !, fail.

do_infer([H1|T1]=[H2|T2], Hs) :-
    do_infer(H1=H2, Hx),
    do_infer(T1=T2, Hy),
    !,
    merge_sort(Hx, Hy, Hs),
    !.

do_infer([H1|_]<>[H2|_], Hs) :- do_infer(H1<>H2, Hs), !.

do_infer([_|T1]<>[_|T2], Hs) :- do_infer(T1<>T2, Hs), !.

do_infer([]<>[_|_], []) :- !.

do_infer([_|_]<>[], []) :- !.

do_infer((set [])<>(set [_|_]), []) :- !.

do_infer((set [_|_])<>(set []), []) :- !.

do_infer(true, []) :- !.

do_infer(not false, []) :- !.

do_infer(A, [N]) :-
    get_hyp(A, _, N),
    !.

do_infer(A and B, Hs) :-
    do_infer(A, Ha),
    do_infer(B, Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(A or B, Hs) :-
    (
        do_infer(A, Hs)
    ;
        do_infer(B, Hs)
    ), !.

do_infer(A -> B, Hs) :-
    (
        do_infer(B, Hs)
    ;
        do_infer(not A, Hs)
    ;
        new_strategies_are_allowed,
        try_new_logic_strategies(A -> B, Hs)
    ), !.

do_infer(A <-> B, Hs) :-
    do_infer(A -> B, Ha),
    do_infer(B -> A, Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(A <-> B, Hs) :-
    new_strategies_are_allowed,
    try_new_logic_strategies(A <-> B, Hs).

do_infer(not not A, Hs) :- do_infer(A, Hs), !.

do_infer(not A, Hs) :- neg(A, B), B\=(not A), do_infer(B, Hs), !.

do_infer(Inequality, Hs) :-
    is_inequality(Inequality, A, B, Op),
    find_mutual_types(A, B, T),
    try_to_infer(Op, A, B, T, Hs),
    !.

do_infer(E in (set [X|Y]), Hs) :-
    (
        do_infer(E=X, Hs)
    ;
        do_infer(E in (set Y), Hs)
    ), !.

do_infer(E in X \/ Y, Hs) :-
    (
        do_infer(E in X, Hs)
    ;
        do_infer(E in Y, Hs)
    ), !.

do_infer(E in X /\ Y, Hs) :-
    do_infer(E in X, Ha),
    do_infer(E in Y, Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(E in X \ Y, Hs) :-
    do_infer(E in X, Ha),
    do_infer(E not_in Y, Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(_E not_in (set []), []) :- !.

do_infer(E not_in (set [X|Y]), Hs) :-
    do_infer(E<>X, Ha),
    do_infer(E not_in (set Y), Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(E not_in X \/ Y, Hs) :-
    do_infer(E not_in X, Ha),
    do_infer(E not_in Y, Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(E not_in X /\ Y, Hs) :-
    (
        do_infer(E not_in X, Hs)
    ;
        do_infer(E not_in Y, Hs)
    ), !.

do_infer(E not_in X \ Y, Hs) :-
    (
        do_infer(E not_in X, Hs)
    ;
        do_infer(E in Y, Hs)
    ), !.

do_infer(X subset_of Y, Hs) :- do_infer(X = Y, Hs), !.

do_infer((set []) subset_of _X, []) :- !.

do_infer(X \ _Y subset_of Z, Hs) :- do_infer(X subset_of Z, Hs), !.

do_infer(X \ Y subset_of X \ Z, Hs) :-
    (
        do_infer(Z subset_of Y, Hs)
    ;
        do_infer(X /\ Z subset_of X /\ Y, Hs)
    ), !.

do_infer(X \/ Y subset_of X \/ Z, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(Y \/ X subset_of Z \/ X, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(Y \/ X subset_of X \/ Z, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(X \/ Y subset_of Z \/ X, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(X /\ Y subset_of X /\ Z, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(Y /\ X subset_of Z /\ X, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(Y /\ X subset_of X /\ Z, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(X /\ Y subset_of Z /\ X, Hs) :- do_infer(Y subset_of Z, Hs), !.

do_infer(X /\ Y subset_of X \/ Y, []) :- !.

do_infer(Y /\ X subset_of X \/ Y, []) :- !.

do_infer(X subset_of Y \/ Z, Hs) :-
    (
        do_infer(X subset_of Y, Hs)
    ;
        do_infer(X subset_of Z, Hs)
    ), !.

do_infer(X subset_of Y /\ Z, Hs) :-
    do_infer(X subset_of Y, Ha),
    do_infer(X subset_of Z, Hb),
    !,
    merge_sort(Ha, Hb, Hs),
    !.

do_infer(X /\ Y subset_of Z, Hs) :-
    (
        do_infer(X subset_of Z, Hs)
    ;
        do_infer(Y subset_of Z, Hs)
    ), !.

do_infer((set X) subset_of (set Y), []) :- is_subset_of(X, Y), !.


do_infer((set []) strict_subset_of X, Hs) :- set_infrule(_E in X, Hs), !.

do_infer(X \ Y strict_subset_of Z, Hs) :-
    (
        do_infer(X strict_subset_of Z, Hs)
    ;
        do_infer(X subset_of Z, Ha),
        set_infer(Y /\ Z <> (set []), Hb),
        !,
        merge_sort(Ha, Hb, Hs)
    ), !.

do_infer(X /\ Y strict_subset_of Z, Hs) :-
    (
        do_infer(X strict_subset_of Z, Hs)
    ;
        do_infer(Y strict_subset_of Z, Hs)
    ), !.

do_infer(X strict_subset_of Y \/ Z, Hs) :-
    (
        do_infer(X strict_subset_of Y, Hs)
    ;
        do_infer(X strict_subset_of Z, Hs)
    ), !.

do_infer((set X) strict_subset_of (set Y), []) :-
    is_strict_subset_of(X, Y),
    !.

do_infer(first([H|_T]) = X, Hs) :- do_infer(H=X, Hs), !.

do_infer(first([H|_T] @ _Z) = X, Hs) :- do_infer(H=X, Hs), !.

do_infer(last([H|T]) = X, Hs) :- last([H|T],L), do_infer(L=X, Hs), !.

do_infer(last(_F @ [H|T]) = X, Hs) :- last([H|T],L), do_infer(L=X, Hs), !.

% Added to improve the handling of structured objects.

% The fact get_forall_hyp(Formula, Side_Conditions, N) is a hypothesis
% of the form (forall Side_Conditions, Formula).
% The Simplifier needs to ensure, before trying to infer the side conditions,
% that the formula is not one of the side conditions; Otherwise, the
% Simplifier will goto into an infinite recursion.
% All the clauses below involving a universal quantified hypothesis needs
% to first check that Formula is not a member of Side_Conditions.

do_infer(Formula, Hs) :-
    get_forall_hyp(Formula, Conditions, N),
    \+ member(Formula, Conditions),
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(X <= Y, Hs) :-
    get_forall_hyp(Y >= X, Conditions, N),
    \+ member(Y >= X, Conditions),
    \+ member(X <= Y, Conditions),
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(X >= Y, Hs) :-
    get_forall_hyp(Y <= X, Conditions, N),
    \+ member(Y <= X, Conditions),
    \+ member(X >= Y, Conditions),
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

% Added to cope with cases of the form:
% H: for_all(i, el(a, [i]) <= 100
% C: el(a, [n]) <= 2147483647

do_infer(X <= Y, Hs) :-
    int_or_enum_lit(Y, Which),
    (
        % Know X <= Z (if Conditions hold)
        get_forall_hyp(X <= Z, Conditions, N)
    ;
        get_forall_hyp(Z >= X, Conditions, N)
    ),
    \+ member(X <= Z, Conditions),
    \+ member(Z >= X, Conditions),
    int_or_enum_lit(Z, Which),
    simplify(Z <= Y, true),                         /* and     Z<=Y, therefore X<=Y */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(Y >= X, Hs) :-
    int_or_enum_lit(Y, Which),
    (
        get_forall_hyp(X <= Z, Conditions, N) /* so know X<=Z (if Conditions hold) */
    ;
        get_forall_hyp(Z >= X, Conditions, N)
    ),
    \+ member(X <= Z, Conditions),
    \+ member(Z >= X, Conditions),
    int_or_enum_lit(Z, Which),
    simplify(Z <= Y, true),                         /* and     Z<=Y, therefore X<=Y, i.e. Y>=X */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(X <= Y, Hs) :-
    int_or_enum_lit(X, Which),
    (
        get_forall_hyp(Z <= Y, Conditions, N)
    ;
        get_forall_hyp(Y >= Z, Conditions, N)
    ),
    \+ member(Z <= Y, Conditions),
    \+ member(Y >= Z, Conditions),   
    int_or_enum_lit(Z, Which),
    simplify(X <= Z, true),                         /* and     X<=Z, therefore X<=Y */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(Y >= X, Hs) :-
    int_or_enum_lit(X, Which),
    (
        get_forall_hyp(Z <= Y, Conditions, N)
    ;
        get_forall_hyp(Y >= Z, Conditions, N)
    ),
    \+ member(Z <= Y, Conditions),
    \+ member(Y >= Z, Conditions),   
    int_or_enum_lit(Z, Which),
    simplify(X <= Z, true),                         /* and     X<=Z, therefore X<=Y, i.e. Y>=X */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

% Added to cope with cases of the form:
% H: for_all(i, el(a, [i]) <= 359
% C: el(a, [n]) * 10 <= 3599

do_infer(XtimesK <= Y, Hs) :-
    (
        XtimesK = X * K
    ;
        XtimesK = K * X
    ),
    int(K),
    simplify(K > 0, true),                          /* Strictly positive K only */
    int(Y),
    (
        get_forall_hyp(X <= Z, Conditions, N)
    ;
        get_forall_hyp(Z >= X, Conditions, N)
    ),
    \+ member(X <= Z, Conditions),
    \+ member(Z >= X, Conditions),   
    int(Z),
    simplify(Z * K <= Y, true),                     /* and     Z*K<=Y, therefore X*K<=Y */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(Y >= XtimesK, Hs) :-
    (
        XtimesK = X * K
    ;
        XtimesK = K * X
    ),
    int(K),
    simplify(K > 0, true),                          /* Strictly positive K only */
    int(Y),
    (
        get_forall_hyp(X <= Z, Conditions, N)
    ;
        get_forall_hyp(Z >= X, Conditions, N)
    ),
    \+ member(X <= Z, Conditions),
    \+ member(Z >= X, Conditions),
    int(Z),
    simplify(Z * K <= Y, true),                     /* and     Z*K<=Y, therefore X*K<=Y */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(X <= YtimesK, Hs) :-
    (
        YtimesK = Y * K
    ;
        YtimesK = K * Y
    ),
    int(K),
    simplify(K > 0, true),                          /* Strictly positive K only */
    int(X),
    (
        get_forall_hyp(Z <= Y, Conditions, N)
    ;
        get_forall_hyp(Y >= Z, Conditions, N)
    ),
    \+ member(Z <= Y, Conditions),
    \+ member(Y >= Z, Conditions),   
    int(Z),
    simplify(X <= Z * K, true),                     /* and     X<=Z*K, therefore X<=Y*K */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).

do_infer(YtimesK >= X, Hs) :-
    (
        YtimesK = Y * K
    ;
        YtimesK = K * Y
    ),
    int(K),
    simplify(K > 0, true),                          /* Strictly positive K only */
    int(X),
    (
        get_forall_hyp(Z <= Y, Conditions, N)
    ;
        get_forall_hyp(Y >= Z, Conditions, N)
    ),
    \+ member(Z <= Y, Conditions),
    \+ member(Y >= Z, Conditions),   
    int(Z),
    simplify(X <= Z * K, true),                     /* and     X<=Z*K, therefore X<=Y*K */
    do_infer_side_conditions(Conditions, HCL),
    !,
    merge_sort([N], HCL, Hs).


%===============================================================================
% TN K616-030 - additional reasoning for inequality arising
% from looping over unconstrained arrays.
%
% The following inference rule essentially embodies the proof:
%
% H1:    x + y <= z .
% H2:    z <= c .
% H3:    c2 + y >= c .
%         ->
% C1:    x <= c2 .
%
% for x, y, z, c, and c2 all integers
%
% Proof of this rule is established in the Proof Checker with:
%   infer x + y <= c2 + y using transitivity.
%   replace h # 4 : x + y <= c2 + y by x <= c2 using standardisation.
%   yes
%   done
%
% Such problems arise when iterating over unconstrained arrays in SPARK.
%===============================================================================

do_infer(X <= C2, Hs) :-
    intexp(C2),
    get_hyp(X + Y <= Z, _, H1),
    intexp(Y),
    get_hyp(Z <= C, _, H2),
    intexp(C),
    simplify(C2 + Y >= C, true),
    merge_sort([H1], [H2], Hs).

%===============================================================================
% TN K616-030 - additional reasoning for inequality arising
% from looping over unconstrained arrays.
%
% and the "reverse" case for decrementing the loop index...
%===============================================================================
do_infer(X >= C2, Hs) :-
    intexp(C2),
    get_hyp(X - Y >= Z, _, H1),
    intexp(Y),
    get_hyp(Z >= C, _, H2),
    intexp(C),
    simplify(C2 - Y <= C, true),
    merge_sort([H1], [H2], Hs).



%
% Discharge conclusions of the form
%     my_scalar_type__max(A, B) >= A and
%     A <= my_scalar_type__max(A, B) and
%     my_scalar_type__max(A, B) >= B and
%     B <= my_scalar_type__max(A, B)
%

do_infer(Arg1 <= Max_Function, []):-
    is_min_max_function(Max_Function, max, Arg1, _Arg2), 
    !.

do_infer(Arg2 <= Max_Function, []):-
    is_min_max_function(Max_Function, max, _Arg1, Arg2), 
    !.

do_infer(Max_Function >= Arg1, []):-
    is_min_max_function(Max_Function, max, Arg1, _Arg2), 
    !.

do_infer(Max_Function >= Arg2, []):-
    is_min_max_function(Max_Function, max, _Arg1, Arg2), 
    !.

%
% Discharge conclusions of the form
%     my_scalar_type__min(A, B) <= A and
%     A >= my_scalar_type__min(A, B) and
%     my_scalar_type__min(A, B) <= B and
%     B >= my_scalar_type__min(A, B).
%

do_infer(Arg1 >= Min_Function, []):-
    is_min_max_function(Min_Function, min, Arg1, _Arg2), 
    !.

do_infer(Arg2 >= Min_Function, []):-
    is_min_max_function(Min_Function, min, _Arg1, Arg2), 
    !.

do_infer(Min_Function <= Arg1, []):-
    is_min_max_function(Min_Function, min, Arg1, _Arg2), 
    !.

do_infer(Min_Function <= Arg2, []):-
    is_min_max_function(Min_Function, min, _Arg1, Arg2), 
    !.

%
% If the two arguments to a min/max function have the same upper and lower bound,
% then infer that the min/max function share the upper and lower bounds.
%

do_infer(LowerBound <= Function, Hs):-
    is_min_max_function(Function, _MinOrMax, Arg1, Arg2),
    do_infer(LowerBound <= Arg1, H1),
    do_infer(LowerBound <= Arg2, H2),
    !,
    merge_sort(H1, H2, Hs),
    !.

do_infer(Function >= LowerBound, Hs):-
    is_min_max_function(Function, _MinOrMax, _Arg1, _Arg2),
    do_infer(LowerBound <= Function, Hs),
    !.

do_infer(UpperBound >= Function, Hs):-
    is_min_max_function(Function, _MinOrMax, Arg1, Arg2),
    do_infer(UpperBound >= Arg1, H1),
    do_infer(UpperBound >= Arg2, H2),
    !,
    merge_sort(H1, H2, Hs),
    !.

do_infer(Function <= UpperBound, Hs):-
    is_min_max_function(Function, _MinOrMax, _Arg1, _Arg2),
    do_infer(UpperBound >= Function, Hs),
    !.

% Added to cope with equalities which need to make use of quantified facts.

do_infer(X = K, Hs) :-
    get_forall_hyp(X <= K, Conditions, N), % So X <= K via a for_all fact ...
    \+ member(X <= K, Conditions),
    \+ member(K >= X, Conditions),   
    do_infer_side_conditions(Conditions, HCL),      /* ...whose side-conditions hold... */
    find_mutual_types(X, K, T),
    try_to_infer((>=), X, K, T, HL),                /* ...and X>=K too, so X=K.         */
    append([N], HL, Hrest),
    !,
    merge_sort(Hrest, HCL, Hs).

do_infer(X = K, Hs) :-
    get_forall_hyp(K >= X, Conditions, N),   % So X <= K via a for_all fact.
    \+ member(X <= K, Conditions),
    \+ member(K >= X, Conditions),   
    do_infer_side_conditions(Conditions, HCL),      /* ...whose side-conditions hold... */
    find_mutual_types(X, K, T),
    try_to_infer((>=), X, K, T, HL),                /* ...and X>=K too, so X=K.         */
    append([N], HL, Hrest),
    !,
    merge_sort(Hrest, HCL, Hs).

do_infer(X = K, Hs) :-
    get_forall_hyp(K <= X, Conditions, N),  % So K <= X via a for_all fact.
    \+ member(K <= X, Conditions),
    \+ member(X >= K, Conditions),   
    do_infer_side_conditions(Conditions, HCL),      /* ...whose side-conditions hold... */
    find_mutual_types(X, K, T),
    try_to_infer((>=), K, X, T, HL),                /* ...and K>=X too, so X=K.         */
    append([N], HL, Hrest),
    !,
    merge_sort(Hrest, HCL, Hs).

do_infer(X = K, Hs) :-
    get_forall_hyp(X >= K, Conditions, N), % So K <= X via a for_all fact 
    \+ member(K <= X, Conditions),
    \+ member(X >= K, Conditions),   
    do_infer_side_conditions(Conditions, HCL),      /* ...whose side-conditions hold... */
    find_mutual_types(X, K, T),
    try_to_infer((>=), K, X, T, HL),                /* ...and K>=X too, so X=K.         */
    append([N], HL, Hrest),
    !,
    merge_sort(Hrest, HCL, Hs).


% Additional predicates added to handle universally quantified terms.

do_infer(element(ArrayStatement, [Index]) >= Bound_Val, H) :-
    Bound_Val \= element(_,_),
    do_infer(Bound_Val <= element(ArrayStatement, [Index]), H).

do_infer(Bound_Val <= element(ArrayStatement, [Index]), H) :-
    Bound_Val \= element(_,_),
    element_update_infer(ArrayStatement, >=, Bound_Val, Index, Status, BaseArray, Ha),
    (
        Status = found, /** The element is updated to satisfy the condition **/
        H = Ha
    ;
        Status = base, /** The element is not updated so prove using a uq hypothesis **/
        fact(for_all(I : Type, LWB <= element(BaseArray, [I]) and element(BaseArray, [I]) <= _UPB), H1),
        checktype(Index, Type),
        infer_subgoal(Bound_Val <= LWB, H2),
        testused(LWB >= Bound_Val),
        !,
        merge_sort(H1, H2, H)
    ).

do_infer(Bound_Val >= element(ArrayStatement, [Index]), H):-
    Bound_Val \= element(_,_),
    do_infer(element(ArrayStatement, [Index]) <= Bound_Val, H).

do_infer(element(ArrayStatement, [Index]) <= Bound_Val, H) :-
    Bound_Val \= element(_,_),
    element_update_infer(ArrayStatement, <=, Bound_Val, Index, Status, BaseArray, Ha),
    (
        Status = found,
        H = Ha
    ;
        Status = base,
        fact(for_all(I : Type, _LWB <= element(BaseArray, [I]) and element(BaseArray, [I]) <= UPB), H1),
        checktype(Index, Type),
        infer_subgoal(UPB <= Bound_Val, H2),
        testused(Bound_Val >= UPB),
        !,
        merge_sort(H1, H2, H)
    ).



% Adds special-case to handle conclusions of the form:
% fld_F(element(A, [I}) >= Bound_Val
%
% Such expressions are common in SPARK VCs since they arise from the need
% to show that fields of a 1-dimensional array of records are in their
% subtypes.
%
% Note that we cannot directly pattern-match for fld_F, since this is
% second-order, so we look first for a record_function() that might match,
% then we look for a quantified hypothesis that matches.

% Lower Bound case.
do_infer(R >= Bound_Val, H) :-
    Bound_Val \= element(_,_),
    record_function(_, R, access, _, [element(_ArrayStatement, [_Index])], _),
    do_infer(Bound_Val <= R, H).

do_infer(Bound_Val <= R, H) :-
    Bound_Val \= element(_,_),
    record_function(_, R, access, _, [element(ArrayStatement, [Index])], _),
    fact(for_all(I : Type, ILB <= I and I <= IUB -> LWB <= E and E <= _UPB), H1),
    functor(R, F, 1),
    E =.. [F, element(ArrayStatement, [I])],
    checktype(Index, Type),
    infer_subgoal(ILB   <= Index, H2),
    infer_subgoal(Index <= IUB,   H3),
    infer_subgoal(Bound_Val <= LWB, H4),
    testused(LWB >= Bound_Val),
    !,
    merge_sort(H1, H2, H5),
    merge_sort(H3, H4, H6),
    merge_sort(H5, H6, H).

% Upper Bound case.
do_infer(Bound_Val >= R, H) :-
    Bound_Val \= element(_,_),
    record_function(_, R, access, _, [element(_ArrayStatement, [_Index])], _),
    do_infer(R <= Bound_Val, H).

do_infer(R <= Bound_Val, H) :-
    Bound_Val \= element(_,_),
    record_function(_, R, access, _, [element(ArrayStatement, [Index])], _),
    fact(for_all(I : Type, ILB <= I and I <= IUB -> _LWB <= E and E <= UPB), H1),
    functor(R, F, 1),
    E =.. [F, element(ArrayStatement, [I])],
    checktype(Index, Type),
    infer_subgoal(ILB   <= Index, H2),
    infer_subgoal(Index <= IUB,   H3),
    infer_subgoal(UPB <= Bound_Val, H4),
    testused(Bound_Val >= UPB),
    !,
    merge_sort(H1, H2, H5),
    merge_sort(H3, H4, H6),
    merge_sort(H5, H6, H).

do_infer(Bound_Val <> element(ArrayStatement, [Index]), H):-
    Bound_Val \= element(_,_),
    do_infer(element(ArrayStatement, [Index]) <> Bound_Val, H).

do_infer(element(ArrayStatement, [Index]) <> Bound_Val, H) :-
    Bound_Val \= element(_,_),
    element_update_infer(ArrayStatement, <>, Bound_Val, Index, Status, BaseArray, Ha),
    (
        Status = found,
        H = Ha
    ;
        Status = base,
        fact(for_all(I : Type, LWB <= element(BaseArray, [I]) and element(BaseArray, [I]) <= UPB), H1),
        checktype(Index, Type),
        (
            infer_subgoal(UPB < Bound_Val, H2)
        ;
            infer_subgoal(Bound_Val < LWB, H2)
        ),
        !,
        merge_sort(H1, H2, H)
    ).

do_infer(Bound_Val = element(ArrayStatement, [Index]), H):-
    Bound_Val \= element(_,_),
    do_infer(element(ArrayStatement, [Index]) = Bound_Val, H).

do_infer(element(ArrayStatement, [Index]) = Bound_Val, H) :-
    Bound_Val \= element(_,_),
    element_update_infer(ArrayStatement, =, Bound_Val, Index, Status, BaseArray, Ha),
    (
        Status = found,
        H = Ha
    ;
        Status = base,
        /* check to see if there is a range of one that is equal to Bound_Val */
        fact(for_all(I : Type, Bound <= element(BaseArray, [I]) and element(BaseArray, [I]) <= Bound), H1),
        infer_subgoal(Bound = Bound_Val, H2),
        checktype(Index, Type),
        !,
        merge_sort(H1, H2, H)
    ).

% The 'element(....) OP Val' conclusion cannot be proved by
% element_update_infer or from simple type bounded universally qualified
% hypotheses.  Therefore look for an implication bounded universally
% qualified hypothesis.

do_infer(element(Array, [Index]) <= Val, H) :-
    Val \= element(_,_),
    infer_by_uq_imp_hyp(element(Array, [Index]) <= Val, H),
    !.

do_infer(Val <= element(Array, [Index]), H) :-
    Val \= element(_,_),
    infer_by_uq_imp_hyp(Val <= element(Array, [Index]), H),
    !.


do_infer(element(Array, [Index]) <> Val, H):-
    Val \= element(_,_),
    infer_by_uq_imp_hyp(Val <> element(Array, [Index]), H),
    !.

do_infer(Val <> element(Array, [Index]), H) :-
    Val \= element(_,_),
    infer_by_uq_imp_hyp(Val <> element(Array, [Index]), H),
    !.

do_infer(Val = element(Array, [Index]), H) :-
    Val \= element(_,_),
    infer_by_uq_imp_hyp(Val = element(Array, [Index]), H),
    !.

do_infer(element(Array, [Index]) = Val, H) :-
    Val \= element(_,_),
    infer_by_uq_imp_hyp(Val = element(Array, [Index]), H),
    !.
% Prove conclusions involving universal quantifiers.

do_infer(for_all(Var : T, X and Y), H) :-
    uq_infer(Var : T, X, H1),
    uq_infer(Var : T, Y, H2),
    !,
    merge_sort(H1, H2, H).

do_infer(for_all(Var : T, X or Y), H) :-
    !,
    (
    uq_infer(Var : T, X, H)
    ;
    uq_infer(Var : T, Y, H)
    ).

do_infer(for_all(Var : T, X -> A and B), H) :-
    do_infer(for_all(Var : T, X -> A), H1),
    do_infer(for_all(Var : T, X -> B), H2), !,
    merge_sort(H1, H2, H).

do_infer(for_all(Var : T, X -> A or B), H) :-
    (
        do_infer(for_all(Var : T, X -> A), H)
    ;
        do_infer(for_all(Var : T, X -> B), H)
    ),
    !.

% Upper bound on an array element.

% A common class of VCs from a loop updating an array is that the update to an array element is
% within range.
% The tactic to discharge the VC is:
%   1) show that it is true for all I, I_LWB <= I <= I_UPB and
%   2) show that it is true when I = I_UPB+1.

do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 ->
        element(update(Array, [I_UPB+1], Val_Update), [I]) <= Val), H) :-
    !,
    % Prove it is true for all I, I_LWB <= I <= I_UPB - see next clause.
    do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB -> element(Array, [I]) <= Val), H1),
    % Prove it is true for I = I_UPB + 1.
    do_infer(Val_Update <= Val, H2),
    merge_sort(H1, H2, H),
    !.

% Handles the case when the index is an enumerated type.
% The VC can be re-expressed by replacing succ(I_UPB) with I_UPB + 1.

do_infer(for_all(I : T, I_LWB <= I and I <= succ(I_UPB) ->
        element(update(Array, [succ(I_UPB)], Val_Update), [I]) <= Val), H) :-
    !,
    % The tactic to discharge this type of VC is the same as the case when
    % the last element in an array indexed by an integer is updated - that is,
    % the Simplifier first tries to infer that the VC is true
    % for all I, I_LWB <= I <= I_UPB and then tries to show the VC is true
    % for the updated array element.
    % The Simplifier invokes the clause above by re-expressing succ(I_UPB)
    % to I_UPB+1 only to pattern match the above clause. Note
    % that I_UPB+1 is never evaluated.
    do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 ->
        element(update(Array, [I_UPB + 1], Val_Update), [I]) <= Val), H),
    !.

do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB -> element(Array, [I]) <= Val), H) :-
    !,
    update_chain_infer(<=, Array, I_LWB, I_UPB, Val, Base_Array, H1),
    (
        /* See if we can determine both a lower and upper bound on Base_Array(I2) */
        fact(for_all(I2 : T, I2_LWB <= I2 and I2 <= I2_UPB ->
                            (Val_LWB <= element(Base_Array, [I2]) and element(Base_Array, [I2]) <= Val_UPB)), H2)
    ;
        fact(for_all(I2 : T, I2_LWB <= I2 and I2 <= I2_UPB ->
                            (element(Base_Array, [I2]) <= Val_UPB and Val_LWB <= element(Base_Array, [I2]))), H2)
    )
    ,
    infer_subgoal(I_UPB <= I2_UPB, H3),
    infer_subgoal(I2_LWB <= I_LWB, H4),
    infer_subgoal(Val_UPB <= Val, H5),
    !,
    merge_sort(H1, H2, Ha),
    merge_sort(H3, H4, Hb),
    merge_sort(H5, Ha, Hc),
    merge_sort(Hc, Hb, H).

% Lower bound on an array element.

% Similar to uppoer bound case.

do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 ->
        Val <= element(update(Array, [I_UPB+1], Val_Update), [I])), H) :-
    !,
    % Prove it is true for all I, I_LWB <= I <= I_UPB - see next clause.
    do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB -> Val <= element(Array, [I])), H1),
    % Prove it is true for I = I_UPB + 1.
    do_infer(Val <= Val_Update, H2),
    merge_sort(H1, H2, H),
    !.

do_infer(for_all(I : T, I_LWB <= I and I <= succ(I_UPB) ->
        Val <= element(update(Array, [succ(I_UPB)], Val_Update), [I])), H) :-
    !,
    % See comment for upper bound case for discharging VC updating an array element
    % indexed by an enumerated type.
    do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 ->
        Val <= element(update(Array, [I_UPB+1], Val_Update), [I])), H),
    !.

do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB -> Val <= element(Array, [I])), H) :-
    !,
    update_chain_infer(>=, Array, I_LWB, I_UPB, Val, Base_Array, H1),
    (
        /* See if we can determine both a lower and upper bound on Base_Array(I2) */
        fact(for_all(I2 : T, I2_LWB <= I2 and I2 <= I2_UPB ->
                            (Val_LWB <= element(Base_Array, [I2]) and element(Base_Array, [I2]) <= Val_UPB)), H2)
    ;
        fact(for_all(I2 : T, I2_LWB <= I2 and I2 <= I2_UPB ->
                            (element(Base_Array, [I2]) <= Val_UPB and Val_LWB <= element(Base_Array, [I2]))), H2)
    )
    ,
    infer_subgoal(I_UPB <= I2_UPB, H3),
    infer_subgoal(I2_LWB <= I_LWB, H4),
    infer_subgoal(Val <= Val_LWB, H5),
    !,
    merge_sort(H1, H2, Ha),
    merge_sort(H3, H4, Hb),
    merge_sort(H5, Ha, Hc),
    merge_sort(Hc, Hb, H).

% Unit-range case for universal quantifier
do_infer(for_all(I : _T, LWB <= I and I <= UPB -> P), H) :-
    do_infer(LWB = UPB, H1),
    subst_vbl(I, LWB, P, New_P),
    do_infer(New_P, H2),
    !,
    merge_sort(H1, H2, H).

% The universal quantifier is trivially true as the range is empty.

do_infer(for_all(I : _T, LWB <= I and I <= UPB -> _X), H) :-
    do_infer(UPB < LWB, H),
    !.


% VCs from a loop updating an array is an invariant of the form
% for_all(I:T, I_LWB <= I and I <= I_UPB + 1) -> element(update(Array, [I_UPB+1], Val_Update), [I]) = Val
% The tactic to discharge this type of VCs is
%   1) show that the it is true for all I, I_LWB <= I <= I_UPB and
%   2) show that it is true when I = I_UPB+1.

do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 ->
        element(update(Array, [I_UPB+1], Val_Update), [I]) = Val), H) :-
    % Phove the invariant is true for all I, I_LWB <= I <= I_UPB.
    fact(for_all(I : T, I_LWB <= I and I <= I_UPB -> element(Array, [I]) = Val), H),
    % We now prove that the invariant is true for I = I_UPB + 1.
    % Val is an arbitrary function of I (array index) - the function can be as simple as
    % a constant, the identity function or something more complicated such as 2 * I + 100.
    % We replace all 'I's in Val with 'I_UPB+1' as we are proving some
    % property on the I_UPB+1 element and then prove that the invariant
    % still holds for I_UPB+1.
    subst_vbl(I, I_UPB+1, Val, Val_Update_X),
    simplify(Val_Update_X = Val_Update, true),
    !.

% Handle the case when the index to the array is an enumerated type. Simplifier
% re-expresses the VC by replacing succ(I_UPB) with I_UPB + 1.

do_infer(for_all(I : T, I_LWB <= I and I <= succ(I_UPB) ->
        element(update(Array, [succ(I_UPB)], Val_Update), [I]) = Val), H) :-
    % See comment for upper bound case for discharging VC updating an array element
    % indexed by an enumerated type.
    do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 ->
        element(update(Array, [I_UPB+1], Val_Update), [I]) = Val), H),
    !.

do_infer(for_all(I : T, I_LWB <= I and I <= I_UPB + 1 -> X), H) :-
    fact(for_all(I : T, I_LWB <= I and I <= I_UPB -> X), H1),
    subst_vbl(I, I_UPB+1, X, X_New),
    fact(X_New, H2),
    merge_sort(H1, H2, H),
    !.

% Handling of inequality in universal quantifiers.

% The universal quantifier reduces to a VC about a single element of the array as
% the lower and upper bound are the same.

do_infer(for_all(I : _T, Bound <= I and I <= Bound -> element(Array, [I]) <> Val), H) :-
    subst_vbl(I, Bound, Val, Val_X),
    fact(element(Array, [Bound]) <> Val_X, H),
    !.

% The universal quantifier is trivially true as the range is empty.

do_infer(for_all(I : _T, LWB <= I and I <= UPB -> element(_Array, [I]) <> _Val), H) :-
    do_infer(UPB < LWB, H),
    !.

do_infer(for_all(Var : T, X), H) :-
    !,
    uq_infer(Var : T, X, H).

%-------------------------------------------------------------------------------

new_strategies_are_allowed :- allow_new_strategies, !.

%-------------------------------------------------------------------------------

find_mutual_types(A, B, T) :-
    checktype(A, T),
    checktype(B, T),
    !.

%-------------------------------------------------------------------------------

do_infer_side_conditions(List, Hs) :-
    inhibit_new_strategies(NeedToRestore),
    !,
    (
        safe_infer_side_conditions(List, Hs),
        Success = true
    ;
        Success = fail
    ),
    !,
    restore_new_strategies(NeedToRestore),
    !,
    call(Success).

%-------------------------------------------------------------------------------

safe_infer_side_conditions([C|CL], Hs) :-
    infer_subgoal(C, H),
    !,
    safe_infer_side_conditions(CL, HL),
    !,
    merge_sort(H, HL, Hs).
safe_infer_side_conditions([], []).

%-------------------------------------------------------------------------------

% This enforces the depth of recursive call limit, which is reset by the
% top-level infer/2 predicate when it is invoked.
try_to_infer(RO, A, B, TYPE, Hs) :-
    decrement_inference_depth_remaining(main),
    !,
    (
        do_try_to_infer(RO, A, B, TYPE, Hs),
        Success = true
    ;
        Success = fail
    ),
    !,
    increment_inference_depth_remaining(main),
    !,
    call(Success).


%-------------------------------------------------------------------------------
% is_min_max_function(+Function, -MinOrMax, -Arg1, -Arg2)
%
% Function is a compound term and the predicate succeeds if the
% function is a min or max function with exactly two arguments.
% The parameter MinOrMax returns either 'min' or 'max' and
% the parameters Arg1 and Arg2 returns the arguments to the function.
%-------------------------------------------------------------------------------

is_min_max_function(Function, MinOrMax, Arg1, Arg2):-
    compound(Function),
    functor(Function, Function_Name, 2),
    arg(1, Function, Arg1),
    arg(2, Function, Arg2),
    is_min_max_function_name(Function_Name, MinOrMax),
    !.

is_min_max_function_name(Function_Name, min):-
    atom_concat(_Prefix_Str, '__min', Function_Name).

is_min_max_function_name(Function_Name, max):-
    atom_concat(_Prefix_Str, '__max', Function_Name),
    !.

%-------------------------------------------------------------------------------

% Inference depth limit utilitity: for preventing non-termination.

decrement_inference_depth_remaining(Category) :-
    inference_depth_limit(Category, Depth),
    Depth > 0,              /* Fail if already reached 0 */
    !,
    retractall(inference_depth_limit(Category, _)),
    !,
    NewDepth is Depth - 1,
    asserta(inference_depth_limit(Category, NewDepth)).

%-------------------------------------------------------------------------------

% Inference depth limit utilitity: for preventing non-termination.

increment_inference_depth_remaining(Category) :-
    retract(inference_depth_limit(Category, Depth)),
    NewDepth is Depth + 1,
    !,
    asserta(inference_depth_limit(Category, NewDepth)).

%-------------------------------------------------------------------------------

do_try_to_infer(RO, A, B, TYPE, Hs) :-
    GOAL =.. [RO, A, B],
    (
        type(TYPE, set(_)),
        !,
        set_infer(GOAL, Hs)
    ;
        type(TYPE, sequence(_)),
        !,
        sequence_infer(GOAL, Hs)
    ;
        type(TYPE, enumerated),
        enumeration(TYPE, ENUMLIST),
        handle_nested_used_facts(main),
        enumerated_infer(GOAL, ENUMLIST, Hs),
        restore_nested_used_facts(main)
        /* If this branch fails for enumerated types, then try */
        /* using apply_deduction/3, so no cut here...          */
    ;
        handle_nested_used_facts(main),
        apply_deduction(GOAL, TYPE, Hs),
        restore_nested_used_facts(main)
    ),
    !.

%-------------------------------------------------------------------------------

apply_deduction(GOAL, TYPE, Hs) :-
    deduce_formula(GOAL, TYPE, Hs),         /* No need to try anything else */
    !.
apply_deduction(GOAL, TYPE, Hs) :-
    allow_new_strategies,           /* Guard: only use new strategies sledgehammer */
    retractall(used(_)),            /*  to crack the remaining harder nuts! */
    !,
    try_new_deduction_strategies(GOAL, TYPE, Hs),
    !.

%-------------------------------------------------------------------------------

sequence_infer(X = [], Hs) :-
    do_infer(length(X)=0, Hs),
    !.

sequence_infer([] = X, Hs) :-
    do_infer(length(X)=0, Hs),
    !.

sequence_infer(X @ Y = [], Hs) :-
    sequence_infer(X=[], H1),
    sequence_infer(Y=[], H2),
    !,
    merge_sort(H1, H2, Hs),
    !.

sequence_infer([] = X @ Y, Hs) :-
    sequence_infer(X=[], H1),
    sequence_infer(Y=[], H2),
    !,
    merge_sort(H1, H2, Hs),
    !.

sequence_infer(X @ [] = X, []) :- !.
sequence_infer([] @ X = X, []) :- !.
sequence_infer(X = X @ [], []) :- !.
sequence_infer(X = [] @ X, []) :- !.

sequence_infer([X|XT]=[Y|YT], Hs) :-
    do_infer(X=Y, H1),
    sequence_infer(XT=YT, H2),
    !,
    merge_sort(H1, H2, Hs),
    !.
sequence_infer(first(X) @ nonfirst(X) = X, []) :- !.
sequence_infer(X = first(X) @ nonfirst(X), []) :- !.
sequence_infer(nonlast(X) @ last(X) = X, []) :- !.
sequence_infer(X = nonlast(X) @ last(X), []) :- !.

sequence_infer(nonfirst([_H|T]) = X, Hs) :-
    do_infer(T=X, Hs),
    !.

sequence_infer(nonlast([H|T]) = X, Hs) :-
    append(N, [_L], [H|T]),
    do_infer(N=X, Hs),
    !.

sequence_infer(X1 @ Y1 = X2 @ Y2, Hs) :-
    do_infer(X1=X2, H1),
    do_infer(Y1=Y2, H2),
    !,
    merge_sort(H1, H2, Hs),
    !.

sequence_infer(X=Y, Hs) :-
    (
        X=Y, Hs=[]
    ;
        sequence_infrule(X=Y, Hs)
    ),
    !.

sequence_infer([_|_] <> [], []) :- !.
sequence_infer([] <> [_|_], []) :- !.
sequence_infer([X|_XT] <> [Y|_YT], Hs) :- do_infer(X<>Y, Hs), !.
sequence_infer([_X|XT] <> [_Y|YT], Hs) :- sequence_infer(XT<>YT, Hs), !.

sequence_infer(X @ Y <> [], Hs) :-
    (
        sequence_infer(X <> [], Hs)
    ;
        sequence_infer(Y <> [], Hs)
    ),
    !.

sequence_infer(X @ Y <> X, Hs) :- sequence_infer(Y <> [], Hs), !.
sequence_infer(X @ Y <> Y, Hs) :- sequence_infer(X <> [], Hs), !.
sequence_infer(X @ Y <> X @ Z, Hs) :- sequence_infer(Y <> Z, Hs), !.
sequence_infer(X @ Y <> Z @ Y, Hs) :- sequence_infer(X <> Z, Hs), !.
sequence_infer(X <> Y, Hs) :- sequence_infrule(X <> Y, Hs), !.

%-------------------------------------------------------------------------------

sequence_infrule(X, Hs) :- fact(X, Hs).
sequence_infrule(X=Y, Hs) :-
    fact(X=Z, H1),
    testused(X=Z),
    sequence_infrule(Y=Z, H2),
    merge_sort(H1, H2, Hs).
sequence_infrule(X<>Y, Hs) :-
    fact(X=Z, H1),
    testused(X=Z),
    sequence_infrule(Z<>Y, H2),
    merge_sort(H1, H2, Hs).
sequence_infrule(X<>Y, Hs) :-
    fact(X<>Z, H1),
    sequence_infrule(Z=Y, H2),
    merge_sort(H1, H2, Hs).
sequence_infrule(X<>Y, Hs) :-
    fact(Y<>Z, H1),
    sequence_infrule(X=Z, H2),
    merge_sort(H1, H2, Hs).

%-------------------------------------------------------------------------------

enumerated_infer(X, L, Hs) :-
    length(L, Depth),
    enum_infrule(X, L, Hs, Depth),
    !.

%===============================================================================
% int_or_enum_lit(+X, -Which).
%-------------------------------------------------------------------------------
% Succeed if X is a signed integer or an enumeration literal, with Which
% set to integer or enum respectively.
%===============================================================================

int_or_enum_lit(X, integer) :-
    signed_integer(X),
    !.

int_or_enum_lit(X, enum) :-
    atom(X),
    !,
    var_const(X, T, c),
    type(T, enumerated),
    enumeration(T, E),
    !,
    is_in(X, E),
    !.

%===============================================================================
% handle_nested_used_facts(+Category).
%-------------------------------------------------------------------------------
% With recursive calls, we want to buffer the used(_) facts at the point of
% call and restore them to what they were afterwards.  This will either be
% achieved on backtracking (in the event that the inference fails), or by
% explicit call to the restore predicate (if the inference succeeds).
%===============================================================================

handle_nested_used_facts(Category) :-
    save_nested_used_facts(Category).

handle_nested_used_facts(Category) :-
    restore_nested_used_facts(Category),
    fail.   /* want to fail if we get here, since inference failed */

%-------------------------------------------------------------------------------

save_nested_used_facts(Category) :-
    inference_depth_limit(Category, D),
    retractall(buffered_used_fact(Category, D, _)),
    !,
    buffer_the_used_facts(Category, D).     /* always succeeds exactly once */

%-------------------------------------------------------------------------------

buffer_the_used_facts(Category, D) :-
    used(Fact),
    assertz(buffered_used_fact(Category, D, Fact)),
    fail.
buffer_the_used_facts(_, _) :-
    retractall(used(_)).

%-------------------------------------------------------------------------------

restore_nested_used_facts(Category) :-
    retractall(used(_)),
    inference_depth_limit(Category, D),
    !,
    unbuffer_the_used_facts(Category, D).   /* always succeeds exactly once */

%-------------------------------------------------------------------------------

unbuffer_the_used_facts(Category, D) :-
    buffered_used_fact(Category, D, Fact),
    assertz(used(Fact)),
    fail.
unbuffer_the_used_facts(_, _).                  /* when finished */

%===============================================================================
% enum_infrule(+Expression, +EnumerationLiterals, -Hypotheses, +RemainingDepth).
%-------------------------------------------------------------------------------
% Espression is the expression of an enumeration type to be inferred.
% EnumerationLiterals is an ordered list of the type's enumeration
% literals.  Hypotheses are the hypotheses used in the inference.
% RemainingDepth is further depth of recursive calls to enum_infrule that
% can be made in attempting to infer the Expression.  When the
% RemainingDepth is 0 enum_infrule will fail. The initial value of
% RemainingDepth should be set to the length of EnumerationLiterals.
%===============================================================================

enum_infrule(X, _L, Hs, _) :-
    fact(X, Hs),!.

% DepthReamining is 0 - no further recursion terminate and fail.
enum_infrule(_, _, _, 0) :- !, fail.

enum_infrule(pred(X)=Y, L, Hs, RemDepth) :-
    !,
    X \= Y,
    prove_not_first(X, L, H1),
    prove_not_last(Y, L, Ha),
    enumerated_simp(succ(Y), L, NEWY),
    NEWY \= succ(Y),
    !,
    % Simplified succ: do not decrement remaining recursion depth.
    enum_infrule(X=NEWY, L, Hb, RemDepth),
    merge_sort(Ha, Hb, H2),
    merge_sort(H1, H2, Hs).

enum_infrule(succ(X)=Y, L, Hs, RemDepth) :-
    !,
    X \= Y,
    prove_not_last(X, L, H1),
    prove_not_first(Y, L, Ha),
    enumerated_simp(pred(Y), L, NEWY),
    NEWY \= pred(Y),
    !,
    % Simplified pred: do not decrement remaining recursion depth.
    enum_infrule(X=NEWY, L, Hb, RemDepth),
    merge_sort(Ha, Hb, H2),
    merge_sort(H1, H2, Hs).

enum_infrule(X=succ(Y), L ,Hs, Depth) :- enum_infrule(succ(Y)=X, L, Hs, Depth),!.
enum_infrule(X=pred(Y), L, Hs, Depth) :- enum_infrule(pred(Y)=X, L, Hs, Depth),!.

enum_infrule(X=Y, L, Hs, RemDepth) :-
    (
        is_in(X, L),
        is_in(Y, L),
        !,
        X = Y
    ;
        fact(X=Z, H1),
        testused(X=Z),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(Z=Y, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        fact(Y=Z, H1),
        testused(Y=Z),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(X=Z, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        equality_by_elimination(X=Y, L, Hs)
    ).

enum_infrule(pred(X)<=Y, L, Hs, RemDepth) :-
    !,
    prove_not_first(X, L, H1),
    (
        X = Y,
        H2 = []
    ;
        prove_not_last(Y, L, Ha),
        enumerated_simp(succ(Y), L, NEWY),
        NEWY \= succ(Y),
        !,
        % Simplified succ: do not decrement remaining recursion depth.
        enum_infrule(X<=NEWY, L, Hb, RemDepth),
        merge_sort(Ha, Hb, H2)
    ;
        NewRemDepth is RemDepth - 1,
        enum_infrule(X<=Y, L, H2, NewRemDepth)
    ),
    merge_sort(H1, H2, Hs).

enum_infrule(X<=succ(Y), L, Hs, RemDepth) :-
    !,
    prove_not_last(Y, L, H1),
    (
        X = Y,
        H2 = []
    ;
        prove_not_first(X, L, Ha),
        enumerated_simp(pred(X), L, NEWX),
        NEWX \= pred(X),
        !,
        % Simplified pred: do not decrement remaining recursion depth.
        enum_infrule(NEWX<=Y, L, Hb, RemDepth),
        merge_sort(Ha, Hb, H2)
    ;
        NewRemDepth is RemDepth - 1,
        enum_infrule(X<=Y, L, H2, NewRemDepth)
    ),
    merge_sort(H1, H2, Hs).

enum_infrule(X<=pred(Y), L, Hs, RemDepth) :-
    !,
    (
        X = Y, fail
    )
    ;
    prove_not_first(Y, L, H1),
    NewRemDepth is RemDepth - 1,
    enum_infrule(X<Y, L, H2, NewRemDepth),
    merge_sort(H1, H2, Hs).

enum_infrule(succ(X)<=Y, L, Hs, RemDepth) :-
    !,
    (
        X = Y, fail
    )
    ;
    prove_not_last(X, L, H1),
    NewRemDepth is RemDepth - 1,
    enum_infrule(X<Y, L, H2, NewRemDepth),
    merge_sort(H1, H2, Hs).

enum_infrule(X<=Y, L, Hs, RemDepth) :-
    NewRemDepth is RemDepth - 1,
    (
        is_in(X, L),
        is_in(Y, L),
        !,
        append(_, [X|T], L),
        is_in(Y, [X|T]),
        Hs=[]
    ;
        % Infer by case no need to decrement remaining recursion depth.
        enum_infrule(X=Y, L, Hs, RemDepth)
    ;
        % Infer by case no need to decrement remaining recursion depth.
        enum_infrule(X<Y, L, Hs, RemDepth)
    ;
        (
          fact(X<=Z, H1),
          Z \= Y,
          testused(Z>=X)
        ;
          fact(X=Z, H1),
          Z \= Y,
          testused(X=Z)
        ;
          fact(X<Z, H1),
          Z \= Y
        ),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(Z<=Y, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        (
          fact(Z<=Y, H1),
          Z \= X,
          testused(Y>=Z)
        ;
          fact(Z=Y, H1),
          Z \= X,
          testused(Z=Y)
        ;
          fact(Z<Y, H1),
          Z \= X
        ),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(X<=Z, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        % Unterminated recursion can result if L has 2 or fewer elements, so...
        length(L, LEN),
        LEN > 2,
        strict_sublist([NEWX, X], L),
        enum_infrule(NEWX<Y, L, Hs, NewRemDepth)
    ;
        % Unterminated recursion can result if L has 2 or fewer elements, so...
        length(L, LEN),
        LEN > 2,
        strict_sublist([Y, NEWY], L),
        enum_infrule(X<NEWY, L, Hs, NewRemDepth)
    ;

        % If L has exactly 2 elements and Y is a literal, then find
        % succ(Y) if possible, and infer X < succ(Y)
        length(L, 2),
        is_in(Y, L),
        prove_not_last(Y, L, Ha),
        !,
        enumerated_simp(succ(Y), L, NEWY),
        NEWY \= succ(Y),
        !,
        % Simplified succ: do not decrement remaining recursion depth
        enum_infrule(X<NEWY, L, Hb, RemDepth),
        merge_sort(Ha, Hb, Hs)
    ;

        % If L has exactly 2 elements and X is a literal, then find
        % pred(X) if possible, and infer pred(X) < Y
        length(L, 2),
        is_in(X, L),
        prove_not_first(X, L, Ha),
        !,
        enumerated_simp(pred(X), L, NEWX),
        NEWX \= pred(X),
        !,
        % Simplified succ: do not decrement remaining recursion depth.
        enum_infrule(NEWX<Y, L, Hb, RemDepth),
        merge_sort(Ha, Hb, Hs)
    ;

        % If L has exactly 2 elements, and they are both literals, then
        % must not recurse.
        length(L, 2),
        is_in(X, L),
        is_in(Y, L),
        !,
        enum_lte(X, Y, L),
        Hs=[]
    ).

enum_infrule(X>=Y, L, Hs, Depth) :- enum_infrule(Y<=X, L, Hs, Depth).

enum_infrule(pred(X)<Y, L, Hs, RemDepth) :-
    !,
    (
        X = Y,
        Hs = []
    )
    ;
    prove_not_first(X, L, H1),
    prove_not_last(Y, L, Ha),
    enumerated_simp(succ(Y), L, NEWY),
    NEWY \= succ(Y),
    !,
    % Simplified succ: do not decrement remaining recursion depth.
    enum_infrule(X<NEWY, L, Hb, RemDepth),
    merge_sort(Ha, Hb, H2),
    merge_sort(H1, H2, Hs).

enum_infrule(succ(X)<Y, L, Hs, RemDepth) :-
    !,
    (
        X = Y,
        fail
    )
    ;
    prove_not_last(X, L, H1),
    prove_not_first(Y, L, Ha),
    !,
    enumerated_simp(pred(Y), L, NEWY),
    NEWY \= pred(Y),
    % Simplified pred: do not decrement remaining recursion depth.
    enum_infrule(X<NEWY, L, Hb, RemDepth),
    merge_sort(Ha, Hb, H2),
    merge_sort(H1, H2, Hs).

enum_infrule(X<pred(Y), L, Hs, RemDepth) :-
    !,
    (
        X = Y,
        fail
    )
    ;
    prove_not_first(Y, L, H1),
    prove_not_last(X, L, Ha),
    !,
    enumerated_simp(succ(X), L, NEWX),
    NEWX \= succ(X),
    % Simplified succ: do not decrement remaining recursion depth.
    enum_infrule(NEWX<Y, L, Hb, RemDepth),
    merge_sort(Ha, Hb, H2),
    merge_sort(H1, H2, Hs).

enum_infrule(X<succ(Y), L, Hs, RemDepth) :-
    !,
    prove_not_last(Y, L, H1),
    prove_not_first(X, L, Ha),
    !,
    enumerated_simp(pred(X), L, NEWX),
    NEWX \= pred(X),
    % Simplified pred: do not decrement remaining recursion depth.
    enum_infrule(NEWX<Y, L, Hb, RemDepth),
    merge_sort(Ha, Hb, H2),
    merge_sort(H1, H2, Hs).

enum_infrule(X<Y, L, Hs, _) :-
    append(_, [Y], L),
    fact(X <> Y, Hs),
    !.

enum_infrule(X<Y, L, Hs, RemDepth) :-
    length(L, LEN),
    NewRemDepth is RemDepth - 1,
    (
        X = Y,
        !,
        fail
    ;
        is_in(X, L),
        is_in(Y, L),
        !,
        enum_lt(X, Y, L),
        Hs=[]
    ;
        LEN > 2,
        fact(X<Z, H1),
        enum_infrule(Z<=Y, L, H2, NewRemDepth),
        merge_sort(H1, H2, Hs)
    ;
        LEN > 2,
        fact(Z<Y, H1),
        enum_infrule(X<=Z, L, H2, NewRemDepth),
        merge_sort(H1, H2, Hs)
    ;
        fact(X=Z, H1),
        testused(X=Z),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(Z<Y, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        fact(Y=Z, H1),
        testused(Y=Z),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(X<Z, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;

        LEN > 2,
        fact(X<=Z, H1),
        testused(Z>=X),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(Z<Y, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        LEN > 2,
        fact(Z<=Y, H1),
        testused(Y>=Z),
        % Protected by testused - no need to decrement Depth.
        enum_infrule(X<Z, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ), !.

enum_infrule(X>Y, L, Hs, Depth) :- enum_infrule(Y<X, L, Hs, Depth), !.

enum_infrule(X<>Y, L, Hs, RemDepth) :-
    (
        X = Y,
        !,
        fail
    ;
        is_in(X, L),
        is_in(Y, L),
        !,
        X\=Y,
        Hs=[]
    ;
        enumerated_infer(X<Y, L, Hs)
    ;
        enumerated_infer(Y<X, L, Hs)
    ;
        fact(X=Z, H1),
        testused(X=Z),
        % Protected by testused - no need to decrement depth.
        enum_infrule(Z<>Y, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ;
        fact(Y=Z, H1),
        testused(Y=Z),
        % Protected by testused - no need to decrement depth.
        enum_infrule(X<>Z, L, H2, RemDepth),
        merge_sort(H1, H2, Hs)
    ),
    !.

%-------------------------------------------------------------------------------

enum_lt(X, Y, L) :- enum_gt(Y, X, L).

enum_gt(X, Y, L) :-
    append(_, [Y|T], L),
    is_in(X, T).







enum_lte(X, X, _).
enum_lte(X, Y, L) :-
    enum_lt(X, Y, L).

%-------------------------------------------------------------------------------

equality_by_elimination(X=Y, L, Hs) :-
    (
        is_in(X, L),
        append(Before, [X| After], L),
        append(Before, After, L2),
        not_any(Y, L2, [], Hs)
    ;
        is_in(Y, L),
        append(Before, [Y| After], L),
        append(Before, After, L2),
        not_any(X, L2, [], Hs)
    ).

not_any(_X, [], Hs, Hs) :- !.

not_any(X, [L|Ls], Hsin, Hsout) :-
    fact(not X = L, H),!,
    not_any(X, Ls, [H|Hsin], Hsout).

%===============================================================================
% is_inequality(+Exp, -LHS, -RHS, -Op).
%-------------------------------------------------------------------------------
% Split an inequality into its constituent sides and operator.
%===============================================================================

is_inequality(A=B,  A, B, (=)).
is_inequality(A<>B, A, B, (<>)).
is_inequality(A<=B, A, B, (<=)).
is_inequality(A>=B, A, B, (>=)).
is_inequality(A<B,  A, B, (<)).
is_inequality(A>B,  A, B, (>)).

%===============================================================================
% infer_by_uq_imp_hyp(+Exp, -Hyp).
%-------------------------------------------------------------------------------
% Infer conclusion from universal quantified implication hypotheses.
%===============================================================================

infer_by_uq_imp_hyp(element(Array, [Index]) >= Val, H) :-
    infer_by_uq_imp_hyp(Val <= element(Array, [Index]), H).

infer_by_uq_imp_hyp(Val <= element(Array, [Index]), H) :-
    fact(for_all(I__1 : IType, I_LWB <= I__1 and I__1 <= I_UPB ->
         E_LWB <= element(Array, [I__1]) and element(Array, [I__1]) <= _E_UPB), H1),
    checktype(Index, IType),
    infer_subgoal(Val <= E_LWB, H2),
    infer_subgoal(Index <= I_UPB, H3),
    infer_subgoal(I_LWB <= Index, H4),
    merge_sort(H1, H2, Ha),
    merge_sort(H3, H4, Hb),
    merge_sort(Ha, Hb, H).

infer_by_uq_imp_hyp(Val >= element(Array, [Index]), H) :-
    infer_by_uq_imp_hyp(element(Array, [Index]) <= Val, H).

infer_by_uq_imp_hyp(element(Array, [Index]) <= Val, H) :-
    fact(for_all(I__1 : IType, I_LWB <= I__1 and I__1 <= I_UPB ->
         _E_LWB <= element(Array, [I__1]) and element(Array, [I__1]) <= E_UPB), H1),
    checktype(Index, IType),
    infer_subgoal(E_UPB <= Val, H2),
    infer_subgoal(Index <= I_UPB, H3),
    infer_subgoal(I_LWB <= Index, H4),
    merge_sort(H1, H2, Ha),
    merge_sort(H3, H4, Hb),
    merge_sort(Ha, Hb, H).

infer_by_uq_imp_hyp(Val <> element(Array, [Index]), H) :-
    infer_by_uq_imp_hyp(element(Array, [Index]) <> Val, H).

infer_by_uq_imp_hyp(element(Array, [Index]) <> Val, H) :-
    fact(for_all(I__1 : IType, I_LWB <= I__1 and I__1 <= I_UPB ->
         E_LWB <= element(Array, [I__1]) and element(Array, [I__1]) <= E_UPB), H1),
    checktype(Index, IType),
    infer_subgoal(Index <= I_UPB, H3),
    infer_subgoal(I_LWB <= Index, H4),
    (
        infer_subgoal(E_UPB < Val, H2)
    ;
        infer_subgoal(Val < E_LWB, H2)
    ),
    merge_sort(H1, H2, Ha),
    merge_sort(H3, H4, Hb),
    merge_sort(Ha, Hb, H).

infer_by_uq_imp_hyp(Val = element(Array, [Index]), H) :-
    infer_by_uq_imp_hyp(element(Array, [Index]) = Val, H).

infer_by_uq_imp_hyp(element(Array, [Index]) = Val, H) :-
    (
        fact(for_all(I__1 : IType, I_LWB <= I__1 and I__1 <= I_UPB ->
             Bound <= element(Array, [I__1]) and element(Array, [I__1]) <= Bound), H1)
    ;
        fact(for_all(I__1 : IType, I_LWB <= I__1 and I__1 <= I_UPB ->
             element(Array, [I__1]) = Bound ), H1)
    ),
    checktype(Index, IType),
    infer_subgoal(Bound =  Val, H2),
    infer_subgoal(Index <= I_UPB, H3),
    infer_subgoal(I_LWB <= Index, H4),
    merge_sort(H1, H2, Ha),
    merge_sort(H3, H4, Hb),
    merge_sort(Ha, Hb, H).

%===============================================================================
% update_chain_infer(+Op, +Exp, +I_LWB, +I_UPB, +Val, -Base_Array, -H).
%-------------------------------------------------------------------------------
% Update chain inference.
%===============================================================================

% Check that all updates to elements of an array, whose indexes are within
% a range, are bounded by a value.
update_chain_infer(Op, update(Array, [I], UVal), I_LWB, I_UPB, Val, Base_Array, H) :-
    infer_subgoal(I_LWB <= I, H1),
    infer_subgoal(I <= I_UPB, H2),
    GOAL =.. [Op, UVal, Val],
    infer_subgoal(GOAL, H3),
    !,
    update_chain_infer(Op, Array, I_LWB, I_UPB, Val, Base_Array, HRest),
    merge_sort(H1, H2, Ha),
    merge_sort(H3, HRest, Hb),
    merge_sort(Ha, Hb, H).

% From above, the updated element is outside the bounded region of the
% array.
update_chain_infer(OP, update(Array, [I], _UVal), I_LWB, I_UPB, Val, Base_Array, H) :-
    (
        infer_subgoal(I < I_LWB, H1)
    ;
        infer_subgoal(I_UPB < I, H1)
    ),
    !,
    update_chain_infer(OP, Array, I_LWB, I_UPB, Val, Base_Array, H2),
    !,
    merge_sort(H1, H2, H).

% This is the base array. If there are no more updates, then we succeed and
% return Array. If there are more updates that the preceding clauses have
% not been able to strip away, then we must fail.
update_chain_infer(_OP, Array, _I_LWB, _I_UPB, _Val, Array, []) :-
    Array \= update(_, [_], _),
    !.

%===============================================================================
% element_update_infer(+Exp, +OP, +Bound_Val, +Idx2, -Status, -Base_Array, -H).
%-------------------------------------------------------------------------------
% Searches an update chain for the 'outermost'(i.e. last) update of the
% element I.  It then infers that the update meets the inequality
% condition. This is similar to the update_chain_infer predicate, but for
% only a single element, rather than all elements in a portion of an array.
%
% If the element is not updated, then the base array of the updates is
% returned.
%
% If the element is updated but the test fails then the predicate fails.
%
% If the element is updated and the test succeeds then the predicate
% succeeds.
%===============================================================================

% Check the outermost element update, Idx = Idx2, then check the update
% value against the inequality bound.  The base array is unified with the
% empty list.  There is no real need as the variable should not be used but
% is included just in case.  By using the empty list we avoid any possible
% name clash.

element_update_infer(update(_Array, [Idx], Upval), <=, Bound_Val, Idx2, found, [], H) :-
    infer_subgoal(Idx = Idx2, H1),
    !,
    infer_subgoal(Upval <= Bound_Val, H2),
    merge_sort(H1, H2, H).

element_update_infer(update(_Array, [Idx], Upval), >=, Bound_Val, Idx2, found, [], H) :-
    infer_subgoal(Idx = Idx2, H1),
    !,
    infer_subgoal(Bound_Val <= Upval, H2),
    merge_sort(H1, H2, H).

element_update_infer(update(_Array, [Idx], Upval), <>, Bound_Val, Idx2, found, [], H) :-
    infer_subgoal(Idx = Idx2, H1),
    !,
    infer_subgoal(Bound_Val <> Upval, H2),
    merge_sort(H1, H2, H).

element_update_infer(update(_Array, [Idx], Upval), =, Bound_Val, Idx2, found, [], H) :-
    infer_subgoal(Idx = Idx2, H1),
    !,
    infer_subgoal(Bound_Val = Upval, H2),
    merge_sort(H1, H2, H).

% The previous clause failed at the Idx = Idx2 stage, therefore move down
% the update chain.
% Note that it is not safe to assume here that Idx <> Idx2, so we must
% check this explicitly.
element_update_infer(update(Array, [Idx], _Upval), OP, Bound_Val, Idx2, Status, Base_Array, H) :-
    infer_subgoal(Idx <> Idx2, H1),
    !,
    element_update_infer(Array, OP, Bound_Val, Idx2, Status, Base_Array, H2),
    merge_sort(H1, H2, H).

% If there are no more updates, then return the Array unchanged.  If there
% are any remaining updates, then fail.
element_update_infer(Array, _OP, _Bound_Val, _Idx2, base, Array, []) :-
    Array \= update(_, [_], _),
    !.

%===============================================================================
% uq_infer(+Var : +Exp, -H).
%-------------------------------------------------------------------------------
% Infer conclusions containing universal quantifiers.
%===============================================================================

uq_infer(Var : T, element(update(A, [I], Val), [Var]) >= X, H) :-
    uq_infer(Var : T, X <= element(update(A, [I], Val), [Var]), H).

uq_infer(Var : T, X <= element(update(A, [_I], Val), [Var]), H) :-
    infer_subgoal(X <= Val, H1), !,
    uq_infer(Var : T, X <= element(A, [Var]), H2),!,
    merge_sort(H1, H2, H).

uq_infer(Var : T, X >= element(update(A, [I], Val), [Var]), H) :-
    uq_infer(Var : T, element(update(A, [I], Val), [Var]) <= X, H).

uq_infer(Var : T, element(update(A, [_I], Val), [Var]) <= X, H) :-
    infer_subgoal(Val <= X, H1),!,
    uq_infer(Var : T, element(A, [Var]) <= X, H2),
    merge_sort(H1, H2, H).

uq_infer(Var : T, X >= element(A, [Var]), H) :-
    uq_infer(Var : T, element(A, [Var]) <= X, H).

uq_infer(Var : T, element(A, [Var]) <= X, H) :-
    (
        fact(for_all(Var : T, element(A, [Var]) <= UPB and LWB <= element(A, [Var])), H1)
    ;
        fact(for_all(Var : T, LWB <= element(A, [Var]) and element(A, [Var]) <= UPB), H1)
    ),
    infer_subgoal(UPB <= X, H2), !,
    merge_sort(H1, H2, H).

uq_infer(Var : T, element(A, [Var]) >= X, H) :-
    uq_infer(Var : T, X <= element(A, [Var]), H).

uq_infer(Var : T, X <= element(A, [Var]), H) :-
    (
        fact(for_all(Var : T, element(A, [Var]) <= UPB and LWB <= element(A, [Var])), H1)
    ;
        fact(for_all(Var : T, LWB <= element(A, [Var]) and  element(A, [Var]) <= UPB), H1)
    ),
    infer_subgoal(X <= LWB, H2), !,
    merge_sort(H1, H2, H).

%===============================================================================
% intexp(+A).
%-------------------------------------------------------------------------------
% Succeeds if E is a nice integers-only expression.
%===============================================================================

intexp(A)       :- var(A), !, fail.
intexp(A)       :- integer(A), !.
intexp(-A)      :- intexp(A), !.
intexp(A+B)     :- intexp(A), intexp(B), !.
intexp(A-B)     :- intexp(A), intexp(B), !.
intexp(A*B)     :- intexp(A), intexp(B), !.
intexp(A div B) :- intexp(A), intexp(B), !.







%===============================================================================
% set_infer(+Exp, -Hs).
%-------------------------------------------------------------------------------
% Infer set Exp, reporting used hypotheses as Hs.
%===============================================================================



set_infer(_X /\ (set []) = (set []), []) :- !.
set_infer((set []) = _X /\ (set []), []) :- !.
set_infer((set []) /\ _X = (set []), []) :- !.
set_infer((set []) = (set []) /\ _X, []) :- !.
set_infer(X /\ X = X, [])                :- !.
set_infer(X = X /\ X, [])                :- !.
set_infer(X /\ Y = Y /\ X, [])           :- !.
set_infer(X \/ (set []) = X, [])         :- !.
set_infer(X = X \/ (set []), [])         :- !.
set_infer((set []) \/ X = X, [])         :- !.
set_infer(X = (set []) \/ X, [])         :- !.
set_infer(X \/ X = X, [])                :- !.
set_infer(X = X \/ X, [])                :- !.
set_infer(X \/ Y = Y \/ X, [])           :- !.
set_infer(X \ (set []) = X, [])          :- !.
set_infer(X = X \ (set []), [])          :- !.
set_infer((set []) \ _X = (set []), [])  :- !.
set_infer((set []) = (set []) \ _X, [])  :- !.
set_infer(X \ X = (set []), [])          :- !.

set_infer(X=Y, Hs) :-
    (
        X=Y, Hs=[]
    ;
        set_infrule(X=Y, Hs)
    ;
        set_infrule(X subset_of Y, Ha),
        set_infrule(Y subset_of X, Hb),
        !,
        merge_sort(Ha, Hb, Hs)
    ), !.

set_infer((set [_X|_Y]) <> (set []), []) :- !.

set_infer((set []) <> (set [_X|_Y]), []) :- !.

set_infer(X <> (set []), Hs) :- set_infrule(_E in X, Hs), !.

set_infer((set []) <> X, Hs) :- set_infrule(_E in X, Hs), !.

set_infer(X <> Y, Hs) :- set_infrule(X <> Y, Hs), !.

%-------------------------------------------------------------------------------

set_infrule(X, Hs) :- fact(X, Hs).

set_infrule(X=Y, Hs) :-
    fact(X=Z, H1),
    testused(X=Z),
    set_infrule(Y=Z, H2),
    merge_sort(H1, H2, Hs).

set_infrule(X<>Y, Hs) :-
    fact(X=Z, H1),
    testused(X=Z),
    set_infrule(Z<>Y, H2),
    merge_sort(H1, H2, Hs).

set_infrule(X<>Y, Hs) :-
    fact(X<>Z, H1),
    set_infrule(Z=Y, H2),
    merge_sort(H1, H2, Hs).

set_infrule(X<>Y, Hs) :-
    fact(Y<>Z, H1),
    set_infrule(X=Z, H2),
    merge_sort(H1, H2, Hs).

set_infrule(X in Y, Hs) :-
    (
        fact(not (X not_in Y), Hs)
    ;
        fact(X=Z, H1),
        testused(X=Z),
        set_infrule(Z in Y, H2),
        merge_sort(H1, H2, Hs)
    ;
        fact(Y=Z, H1),
        testused(Y=Z),
        set_infrule(X in Z, H2),
        merge_sort(H1, H2, Hs)
    ).

set_infrule(X not_in Y, Hs) :-
    (
        fact(not (X in Y), Hs)
    ;
        fact(X=Z, H1),
        testused(X=Z),
        set_infrule(Z not_in Y, H2),
        merge_sort(H1, H2, Hs)
    ;
        fact(Y=Z, H1),
        testused(Y=Z),
        set_infrule(X not_in Z, H2),
        merge_sort(H1, H2, Hs)
    ).

%###############################################################################
% END-OF-FILE
