-- JunoSolverImpl.mesa
-- June 11, 1983 12:05 am
-- GNelson
-- Last Edited by: Gnelson, December 11, 1983 10:10 pm

DIRECTORY SolverCommon, JunoSolver, JunoSyntax, RealSolver, 
               PairSolver, JunoBind, Lisp, JunoLookup, LinearSolver;

JunoSolverImpl: PROGRAM
  IMPORTS SolverCommon, JunoSyntax, RealSolver, PairSolver, 
              JunoBind, Lisp, JunoLookup, LinearSolver
  EXPORTS JunoSolver 
= BEGIN OPEN SolverCommon, JunoSolver, Lisp, JS: JunoSyntax, JB: JunoBind;

Solve: PUBLIC PROC [f: JS.Formula, vl: JS.Varlist, al: JunoBind.Alist, defs: REF]
        RETURNS [al2: JunoBind.Alist, rc: ResultCode]
= {g: JS.Formula; 
   vl2: JS.Varlist;
   rcon: RealSolver.Context;
   lcon: PairSolver.Context;
   eq: Value;
   [g, vl2] ← Normalize[f, defs];
   al2 ← JB.EmptyBinding[];
   {vll: JS.Varlist ← vl; 
     WHILE vll # NIL DO
       {temp: Solvee ← NewSolvee[Car[vll]]; 
        al2 ← JB.NewBind[al2, Car[vll], temp];
        vll ← Cdr[vll]}
     ENDLOOP};
   rc ← $solved;
   [eq, rcon, lcon] ← SetUp[al2, vl2, al, g ! ExplicitOccurrenceOfFalse => rc ← $refuted];
   IF rc = $refuted THEN RETURN;
   rc ← SolveLoop[eq, rcon, lcon];
   SELECT TRUE FROM
     rc = $refuted OR rc = $stumped => NULL;
     rc = $solved OR rc = $close => al2 ← ConstructSolution[vl, al2, rcon, lcon]
   ENDCASE => ERROR};

ExplicitOccurrenceOfFalse: SIGNAL = CODE;

SetUp: PROC [al2: JS.Alist, vl2: JS.Varlist, al: JS.Alist, g: JS.Formula] 
		 RETURNS [eq: Value, rcon: RealSolver.Context, lcon: PairSolver.Context]
= {rcon ← RealSolver.NewContext[];
   lcon ← PairSolver.NewContext[];
   al2 ← JB.Push[al2];
   eq ← NIL;
   WHILE g # NIL DO
     {f: JS.Formula ← Car[g];
      pred: JS.PredName ← JS.PredOfAtomicFormula[f];
      args: JS.Varlist ← JS.TermlistOfAtomicFormula[f];
      ndl: Value;
      g ← Cdr[g];
      [ndl, al2] ← VarlistToNodeList[args, al2, vl2, al, rcon, lcon];
      SELECT TRUE FROM
        pred = JS.true => NULL;
        pred = JS.false => SIGNAL ExplicitOccurrenceOfFalse;
        pred = JS.eq => eq ← Cons[ndl, eq];
        pred = JS.neq => lcon.neq ← Cons[ndl, lcon.neq];
        pred = JS.isPairOf => lcon.pa ← Cons[ndl, lcon.pa];
        pred = JS.isSumOf => rcon.su ← Cons[ndl, rcon.su];
        pred = JS.isPositive => rcon.po ← Cons[ndl, rcon.po];
        pred = JS.isProductOf => rcon.pr ← Cons[ndl, rcon.pr];
        Pair[pred] AND Car[pred] = JS.constant AND
         (ISTYPE[Cadr[pred], REF INT] OR ISTYPE[Cadr[pred], REF REAL])
          => rcon.b ← Cons[Cons[Cadr[pred], ndl], rcon.b];
        Pair[pred] AND Car[pred] = JS.constant AND ISTYPE[Cadr[pred], ATOM] 
          => lcon.b ← Cons[Cons[Cadr[pred], ndl], lcon.b]
      ENDCASE => ERROR}
    ENDLOOP;
    al2 ← JB.Pop[al2]};

VarlistToNodeList: PROC [args: JS.Varlist, 
    					       al2: JB.Alist, 
    					       vl2: JS.Varlist, 
    					       al: JB.Alist,
    					       rcon: RealSolver.Context,
    					       lcon: PairSolver.Context]
					   RETURNS [ndl: Value, newal2: JB.Alist]
= {ndl ← NIL;
   WHILE args # NIL DO
      {arg: Value ← Car[args];
       args ← Cdr[args];
       SELECT TRUE FROM
         
         ~JB.InDomain[arg, al2] AND ~ Memq[arg, vl2] =>
           {u: Value ← JB.Lookup[arg, al];
            temp: Solvee ← NewSolvee[arg];
            ndl ← Cons[temp, ndl];
            IF Pair[u]
              THEN lcon.b ← Cons[Cons[u, temp], lcon.b]
              ELSE rcon.b ← Cons[Cons[u, temp], rcon.b]};
         
         JB.InDomain[arg, al2] =>
           ndl ← Cons[JB.Lookup[arg, al2], ndl];
         
         ~JB.InDomain[arg, al2] AND Memq[arg, vl2] =>
           {temp: Solvee ← NewSolvee[arg];
            al2 ← JB.NewBind[al2, arg, temp];
            ndl ← Cons[temp, ndl]}
         
       ENDCASE => ERROR};
     ENDLOOP;
     ndl ← Reverse[ndl];
     newal2 ← al2};


SolveLoop: PROC [eq: Value, rcon: RealSolver.Context, lcon: PairSolver.Context] 
             RETURNS [rc: ResultCode]
= {rc ← $solved;
DO SELECT TRUE FROM

rc # $refuted AND eq # NIL =>
{x: Solvee = NARROW[Car[Car[eq]]];
 y: Solvee = NARROW[Cadr[Car[eq]]];
 eq ← Cdr[eq];
 [eq, rc] ← PairSolver.Merge[x, y, eq, lcon];
 [eq, rc] ← RealSolver.Merge[x, y, eq, rcon]};

rc # $refuted AND lcon.b # NIL =>
 {u: Value ← Car[Car[lcon.b]];
  x: Solvee ← NARROW[Cadr[Car[lcon.b]]];
  lcon.b ← Cdr[lcon.b];
  [eq, rc] ← PairSolver.Fix[u, x, eq, lcon]};

rc # $refuted AND lcon.neq # NIL =>
 {x: Solvee ← NARROW[Car[Car[lcon.neq]]];
  y: Solvee ← NARROW[Cadr[Car[lcon.neq]]];
  lcon.neq ← Cdr[lcon.neq];
  rc ← PairSolver.Neq[x, y, lcon]};

rc # $refuted AND rcon.b # NIL =>
 {u: Value ← Car[Car[rcon.b]];
  x: Solvee ← NARROW[Cadr[Car[rcon.b]]];
  rcon.b ← Cdr[rcon.b];
  [eq, rc] ← RealSolver.Fix[u, x, eq, rcon]};

rc # $refuted AND lcon.pa # NIL =>
 {x: Solvee ← NARROW[Car[Car[lcon.pa]]];
  y: Solvee ← NARROW[Cadr[Car[lcon.pa]]];
  z: Solvee ← NARROW[Caddr[Car[lcon.pa]]];
  lcon.pa ← Cdr[lcon.pa];
  [eq, rc] ← PairSolver.IsPairOf[x, y, z, eq, lcon]};

rc # $refuted AND rcon.su # NIL =>
 {x: Solvee ← NARROW[Car[Car[rcon.su]]];
  y: Solvee ← NARROW[Cadr[Car[rcon.su]]];
  z: Solvee ← NARROW[Caddr[Car[rcon.su]]];
  rcon.su ← Cdr[rcon.su];
  [eq, rc] ← RealSolver.IsSumOf[x, y, z, eq, rcon]};

rc # $refuted AND rcon.po # NIL =>
  {x: Solvee ← NARROW[Car[Car[rcon.po]]];
   rcon.po ← Cdr[rcon.po];
   [eq, rc] ← RealSolver.IsPositive[x, rcon, eq]}

ENDCASE => EXIT ENDLOOP;

IF rc # $refuted AND rcon.pr # NIL 
THEN {rc ← $close; NewtonRaphson[]}}; --end of SolveLoop

NewtonRaphson: PROC;

Normalize: PROC [f: JS.Formula, defs:REF] RETURNS [g: Value, vl: JS.Varlist]
= {h: Value ← NIL;
   g ← NIL;
   vl ← NIL;
   
   -- h and g are lists of formulas; vl is a list of variables.
   -- h contains conjuncts of f that remain to be normalized;
   -- g contains the normalization as it is built up.
   
   DO SELECT TRUE FROM
   
   -- invariant:  Let f0 be the original formula f.  Then f0 is equivalent to
   -- 
   --         (E vl: f and g and h),
   --
   -- wherein the formula lists h and g represent the conjunctions of their elements.  
   -- Also, all formulas on g are atomic predicates applied to variables.  At the conclusion
   -- if the loop we will have h = nil and f is an atomic predicate applied to variables,
   -- hence the postcondition is established after the loop by g ← Cons[f, g].
   -- The variables on vl are all created by gensym, so we have f0 = (E vl: f0).
   
   JS.IsConjunction[f] =>
     {h ← Cons[JS.LHSOfConjunction[f], h];
      f ← JS.RHSOfConjunction[f]};
   
   -- (f1 and f2) and g and h == f2 and g and (f1 and h)
   
   JS.IsParenthesized[f] => f ← JS.ContentsOfParentheses[f]; 
   
   -- (f1) == f1
   
   JS.IsQuantification[f] AND JS.QuantifierOfQuantification[f] = JS.ex =>
   {vl2: JS.Varlist ← JS.QuantifierVarlistOfQuantification[f];
    f ← JS.QuantifieeOfQuantification[f];
    WHILE vl2 # NIL DO
      {temp: JS.Variable ← JS.Gensym[];
       vl ← Cons[temp, vl];
       f ← Subst[f, Car[vl2], temp];
       vl2 ← Cdr[vl2]}
    ENDLOOP};
    
    --     (E vl: (E vl2: f) and g and h) 
    -- == {if gl2 is a list of distinct symbols of the same length as vl2}
    --     (E vl: (E gl2: f(vl2:gl2)) and g and h)
    -- == {if no var in gl2 is free in g or h
    --     (E vl, gl2: f(vl2:gl2) and g and h)
    --
    -- The loop above constructs the concatenation of  gl2  and  vl  by repeated conses
    -- and simultaneously constructs  f(vl2:gl2)  by repeated substitutions.
    
    JS.IsAtomicFormula[f] AND JS.PredOfAtomicFormula[f] = JS.eq AND
      JS.IsVar[Car[JS.TermlistOfAtomicFormula[f]]] AND 
      LevelOneTerm[Cadr[JS.TermlistOfAtomicFormula[f]]]  AND
      NOT JS.IsConstant[Cadr[JS.TermlistOfAtomicFormula[f]]] =>
      -- I.e., if f is an equality between a variable and a function applied to a list of variables
    {lhs: JS.Variable = Car[JS.TermlistOfAtomicFormula[f]];
     rhs: JS.Term = Cadr[JS.TermlistOfAtomicFormula[f]];
     fun: JS.FunName = JS.FunctionOfTerm[rhs];
     args: JS.Termlist = JS.TermlistOfTerm[rhs];
     def: JS.FunDef = JunoLookup.GetFunDef[fun, defs];
     formals: JS.Varlist ← JS.VarlistOfFunDef[def];
     result: JS.Variable ← JS.ResultnameOfFunDef[def];
     body: JS.Formula ← JS.BodyOfFunDef[def];
     f ← MapSubst[body, Cons[result, formals], Cons[lhs, args]]};
     
     -- with f defined by  f(formals) = result == body , we have
     --
     --    lhs = f(args) == body(result, formals  :  lhs, args).
     --
     -- Note that the guard is stronger than logically necessary: we do not
     -- use this operation unless  args  is a list of variables.  This is to
     -- guarantee that the substitution does not blow up the size of  body .
    
    JS.IsAtomicFormula[f] AND JS.PredOfAtomicFormula[f] = JS.eq AND
      JS.IsVar[Cadr[JS.TermlistOfAtomicFormula[f]]] AND 
      LevelOneTerm[Car[JS.TermlistOfAtomicFormula[f]]] AND
      NOT JS.IsConstant[Car[JS.TermlistOfAtomicFormula[f]]]  =>    
     {tl: JS.Termlist ← JS.TermlistOfAtomicFormula[f];
      newtl: JS.Termlist ← Cons[Cadr[tl], Cons[Car[tl], NIL]];
      -- newtl = reverse(tl)
      f ← JS.MakeAtomicFormula[JS.eq, newtl]};
      
    -- f(varlist) = x   ==   x = f(varlist)
   
    -- termination of this loop depends on the next cases being below the two above
    
    JS.IsAtomicFormula[f] AND (NOT IsVarlist[JS.TermlistOfAtomicFormula[f]]) =>
    {temp: JS.Variable ← JS.Gensym[];
     t: JS.Term;
     pred: JS.PredName ← JS.PredOfAtomicFormula[f];
     tl: JS.Termlist ← JS.TermlistOfAtomicFormula[f];
     [tl, t] ← SwapVarForProperTerm[tl, temp];
     -- this finds the first non-variable in tl, replaces it by temp,
     -- sets tl to be the new list, and t to be the term that was replaced.
     f ← JS.MakeAtomicFormula[pred, tl];
     IF JS.IsConstant[t] 
     THEN h ← Cons[LIST[LIST[JS.constant, t], temp], h]
     ELSE  h ← Cons[JS.MakeAtomicFormula[JS.eq, Cons[temp, Cons[t, NIL]]], h];
     vl ← Cons[temp, vl]};
     
     -- pred(... t ...) == (E temp: pred(... temp ...) and temp = t)
    
    JS.IsAtomicFormula[f] AND IsVarlist[JS.TermlistOfAtomicFormula[f]]
    AND NOT JS.KernelPredicate[JS.PredOfAtomicFormula[f]] =>
    {def: JS.PredDef ← JunoLookup.GetPredDef[Car[f], defs];
     parameters: JS.Varlist ← JS.VarlistOfPredDef[def];
     body: JS.Formula ← JS.BodyOfPredDef[def];
     f ← MapSubst[body, parameters, JS.TermlistOfAtomicFormula[f]]};
    
    -- With p defined by  p(parameters) = body , we have  p(tl) == body(parameters: tl).
   
   JS.IsAtomicFormula[f] AND IsVarlist[JS.TermlistOfAtomicFormula[f]]
   AND JS.KernelPredicate[JS.PredOfAtomicFormula[f]] AND h # NIL =>
   {g ← Cons[f, g]; f ← Car[h]; h ← Cdr[h]};
   
   -- g and f and (h1 and h2) == (g and f) and h1 and h2
   
 ENDCASE => EXIT ENDLOOP;
 
 -- At this point f is an application of a Kernel predicate to a list of Level Zero terms,
 -- and h is NIL.  f may also be a constant predicate T or F. 
 
 IF f # JS.true 
 AND f # JS.false 
 AND NOT (JS.IsAtomicFormula[f] AND IsVarlist[JS.TermlistOfAtomicFormula[f]] AND h = NIL)
 THEN ERROR;
 
 g ← Cons[f, g]}; -- end of Normalize
 
 LevelOneTerm: PROC [t: JS.Term] RETURNS [BOOLEAN] 
 = {RETURN[IsVarlist[JS.TermlistOfTerm[t]]]};
 
IsVarlist: PROC [tl: JS.Termlist] RETURNS [BOOLEAN]
 = {WHILE tl # NIL AND JS.IsVar[Car[tl]] DO tl ← Cdr[tl] ENDLOOP;
     RETURN [tl = NIL]};
    
SwapVarForProperTerm: PROC[f:JS.Termlist, v: JS.Variable] 
   						 RETURNS [g: JS.Termlist, t: JS.Term] =
{l: JS.Termlist ← NIL;
 WHILE JS.IsVar[Car[f]] DO l ← Cons[Car[f], l]; f ← Cdr[f] ENDLOOP;
 t ← Car[f];
 f ← Cons[v, Cdr[f]];
 WHILE l # NIL DO f ← Cons[Car[l], f]; l ← Cdr[l] ENDLOOP;
 g ← f};
 
 ConstructSolution: 
  PROC[vl: JS.Varlist, alin: JB.Alist, rcon: RealSolver.Context, lcon: PairSolver.Context]
  RETURNS [alout: JB.Alist] = 
    {alout ← JB.EmptyBinding[];
     WHILE vl # NIL DO
       temp: LinearSolver.Unknown = NARROW[JB.Lookup[Car[vl], alin], Solvee].tableau;
       alout ← JB.NewBind[
                 alout,
                 Car[vl],
                 SELECT TRUE FROM 
                   temp = NIL => NIL, 
                   temp # NIL => NEW[REAL ← LinearSolver.Solution[temp]]
                 ENDCASE => ERROR];
       vl ← Cdr[vl]
     ENDLOOP    
    };

END.