juno-machine/src/JunoSolve.m3


 Copyright (C) 1992, Digital Equipment Corporation                         
 All rights reserved.                                                      
 See the file COPYRIGHT for a full description.                            
                                                                           
 Last modified on Fri Jun  9 10:55:11 PDT 1995 by heydon                       
      modified on Thu Dec  8 15:46:40 1994 by gnelson                      
      modified on Mon Oct 31 18:34:30 PST 1994 by isard                    
      modified on Tue Jul 21 01:55:06 PDT 1992 by myers                    
<* PRAGMA LL *>

MODULE JunoSolve;

IMPORT Egraph, Equiv, RedundantSolve AS NonLinearSolve, RTVal;
IMPORT RefSeq, Word;

EXCEPTION Unsolvable;

TYPE
  Type = { Any, Pair, Num, Text, Null };
  (* There is a flat partial order on types, with "Type.Any" as bottom. *)

VAR (* CONST *)
  PlusOp, TimesOp, SinOp, CosOp, AtanOp, ExpOp, PairOp := Init(NEW(Var));
  zero := RTVal.FromReal(0.0);
  emptyString := RTVal.FromText("");

TYPE
  EC = Private;
  (* The type "EC" represents an equivalence class with an extra field to
     record if the class contains a pair expression. *)

REVEAL
  Private = Egraph.T BRANDED "JunoSolve.Private" OBJECT
    cons: EC;
  OVERRIDES
    union := Merge;
  END;
  Var = Public BRANDED "JunoSolve.Var" OBJECT
    type: Type;
    index: INTEGER;
    marked: BOOLEAN;
    uses: UseList;
    next, availLink: Var;
  END;

TYPE
  UseList = REF RECORD
    c: Constraint;
    mask := 0;
    next, availLink: UseList
  END;
A cons node is a node whose car is PairOp. It represents a pair expression (x, y).

The call v.union(w) where v is of type EC requires that v.root # w.root.

The following are invariants on valid EC's and Var's:

P1: If an equivalence class whose root is rt contains a cons node, then rt.cons points to a cons node in rt's class. Conversely, if rt's class does not contain a cons node, then rt.cons = NIL.

P2: The type field of a Var records its type; it is only valid for a variable that is the root of its equivalence class.

P3: For any variable rt that is the root of its equivalence class, rt.known => rt.type # Type.Any. (The converse is not true, since a REAL or TEXT constraint can set the type of an unknown.)

P4: The index field of a Var v reflects the index in the global array numeric_vars of the numeric unknown represented by v. It is -1 if v does not correspond to any variable in the array.

P5: The uses and next fields of a Var are relevant only in NumericSolve(), and they are valid only for Var's that are the roots of their equivalence classes.

TYPE
  Args = ARRAY [0..2] OF Var;
  ConType = { Equal, Cons, Plus, Times, Atan, Sin, Cos, Exp, Real, Text };

REVEAL
  Constraint = BRANDED "JunoSolve.Constraint" OBJECT
    type: ConType;
    arg: Args;
    hintCnt: CARDINAL;
    mask: CARDINAL;
    nls_con: NonLinearSolve.Constraint;
    unknown: Var;
    nls_con_mask: CARDINAL;
    next: Constraint;
    availLink: Constraint
  END;
  (* These fields are only valid during the procedure "NumericSolve". *)

VAR
  mu := NEW(MUTEX);
  <* LL >= { mu } *>
  unifyList := NEW(RefSeq.T).init(sizeHint := 100);
  numeric_vars := NEW(Vars, 60);
  numeric_vals := NEW(REF ARRAY OF RTVal.Real, 60);
  numeric_con := NEW(REF ARRAY OF NonLinearSolve.Constraint, 30);
The unify list (u1, v1, u2, v2, ...) contains all pairs (ui, vi) of cons nodes that are equivalent, but have not yet been merged.

All Var's in numeric_vars are roots of their equivalence classes.

PROCEDURE New(known := FALSE; val: RTVal.T := NIL): Var =
  BEGIN RETURN NewPair(NIL, NIL, known, val) END New;

VAR
  varAvail, varInUse: Var := NIL;
  conAvail, conInUse: Constraint := NIL;
  useAvail, useInUse: UseList := NIL;

PROCEDURE NewPair(car, cdr: Var; known := FALSE; val: RTVal.T := NIL): Var=
  VAR res: Var; BEGIN
    IF varAvail # NIL
      THEN res := varAvail; varAvail := varAvail.availLink
      ELSE res := NEW(Var)
    END;
    res.availLink := varInUse;
    varInUse := res;
    res.known := known;
    res.val := val;
    res.car := car;
    res.cdr := cdr;
    RETURN Init(res)
  END NewPair;

PROCEDURE NewCon(type: ConType): Constraint =
  VAR res: Constraint; BEGIN
    IF conAvail # NIL
      THEN res := conAvail; conAvail := conAvail.availLink
      ELSE res := NEW(Constraint)
    END;
    res.availLink := conInUse;
    conInUse := res;
    res.type := type;
    RETURN res
  END NewCon;

PROCEDURE NewEqual(x, y: Var): Constraint =
  VAR res := NewCon(ConType.Equal); BEGIN
    res.arg[0] := x; res.arg[1] := y;
    RETURN res
  END NewEqual;

PROCEDURE NewCons(x, y, z: Var): Constraint =
  VAR res := NewCon(ConType.Cons); BEGIN
    res.arg[0] := x; res.arg[1] := y; res.arg[2] := z;
    RETURN res
  END NewCons;

PROCEDURE NewPlus(x, y, z: Var): Constraint =
  VAR res := NewCon(ConType.Plus); BEGIN
    res.arg[0] := x; res.arg[1] := y; res.arg[2] := z;
    RETURN res
  END NewPlus;

PROCEDURE NewTimes(x, y, z: Var): Constraint =
  VAR res := NewCon(ConType.Times); BEGIN
    res.arg[0] := x; res.arg[1] := y; res.arg[2] := z;
    RETURN res
  END NewTimes;

PROCEDURE NewAtan(x, y, z: Var): Constraint =
  VAR res := NewCon(ConType.Atan); BEGIN
    res.arg[0] := x; res.arg[1] := y; res.arg[2] := z;
    RETURN res
  END NewAtan;

PROCEDURE NewSin(x, y: Var): Constraint =
  VAR res := NewCon(ConType.Sin); BEGIN
    res.arg[0] := x; res.arg[1] := y;
    RETURN res
  END NewSin;

PROCEDURE NewCos(x, y: Var): Constraint =
  VAR res := NewCon(ConType.Cos); BEGIN
    res.arg[0] := x; res.arg[1] := y;
    RETURN res
  END NewCos;

PROCEDURE NewExp(x, y: Var): Constraint =
  VAR res := NewCon(ConType.Exp); BEGIN
    res.arg[0] := x; res.arg[1] := y;
    RETURN res
  END NewExp;

PROCEDURE NewReal(x: Var): Constraint =
  VAR res := NewCon(ConType.Real); BEGIN
    res.arg[0] := x;
    RETURN res
  END NewReal;

PROCEDURE NewText(x: Var): Constraint =
  VAR res := NewCon(ConType.Text); BEGIN
    res.arg[0] := x;
    RETURN res
  END NewText;

PROCEDURE NewUse(con: Constraint; next: UseList): UseList =
  VAR res: UseList; BEGIN
    IF useAvail # NIL THEN
      res := useAvail; useAvail := useAvail.availLink;
      res.c := con; res.mask := 0; res.next := next
    ELSE
      res := NEW(UseList, c := con, next := next)
    END;
    res.availLink := useInUse;
    useInUse := res;
    RETURN res
  END NewUse;

PROCEDURE Dispose() =
  BEGIN
    VAR l := varInUse; BEGIN
      IF l # NIL THEN
      	WHILE l.availLink # NIL DO l := l.availLink END;
      	l.availLink := varAvail;
      	varAvail := varInUse;
      	varInUse := NIL
      END
    END;
    VAR l := conInUse; BEGIN
      IF l # NIL THEN
      	WHILE l.availLink # NIL DO l := l.availLink END;
      	l.availLink := conAvail;
      	conAvail := conInUse;
      	conInUse := NIL
      END
    END;
    VAR l := useInUse; BEGIN
      IF l # NIL THEN
      	WHILE l.availLink # NIL DO l := l.availLink END;
      	l.availLink := useAvail;
      	useAvail := useInUse;
      	useInUse := NIL
      END
    END
  END Dispose;

CONST NoIndex = -1;

PROCEDURE Init(v: Var): Var =
Modify v into a variable v' such that v'.root = v' and such that P1 - P5 hold of v'. Return v'.
  BEGIN
    EVAL Egraph.T.init(v);
    v.index := NoIndex;
    v.marked := FALSE;
    v.uses := NIL;
    v.next := NIL;
    IF v.known THEN
      TYPECASE v.val OF <* NOWARN *>
      | RTVal.Null => v.type := Type.Null
      | RTVal.Number => v.type := Type.Num
      | RTVal.Text => v.type := Type.Text
      | RTVal.Pair => v.type := Type.Pair
      END
    ELSE
      v.type := Type.Any
    END;
    IF v.car = PairOp
      THEN v.cons := v
      ELSE v.cons := NIL
    END;
    RETURN v
  END Init;

PROCEDURE P(READONLY c: ARRAY OF Constraint): BOOLEAN =
  BEGIN
    LOCK mu DO
      IF NUMBER(numeric_vars^) < 3 * NUMBER(c) THEN
         numeric_vars := NEW(Vars, 3 * NUMBER(c));
      END;
      TRY
        EVAL unifyList.init();
        ConstructECs(c);
        UnifyClose();
        NumericSolve(c);
        ConstructSoln(c);
      EXCEPT
        Equiv.Forbidden, Unsolvable => RETURN FALSE
      END;
      RETURN TRUE
    END
  END P;

PROCEDURE EtpLogConstraint(<*UNUSED*>type: INTEGER) =
Types are: 0 = Equal; 1 = CONS; 2 = +; 3 = *; 4 = REAL; 5 = TEXT; 6 = ATAN; 7 = SIN; 8 = COS; 9 = EXP.
  BEGIN END EtpLogConstraint;

PROCEDURE ConstructECs(READONLY c: ARRAY OF Constraint)
    RAISES {Equiv.Forbidden} =
  VAR con: Constraint; BEGIN
    FOR i := FIRST(c) TO LAST(c) DO
      con := c[i];
      CASE con.type OF <* NOWARN *>
      | ConType.Equal =>
          EtpLogConstraint(0);
          WITH r1 = con.arg[0].root, r2 = con.arg[1].root DO
            IF r1 # r2 THEN EVAL r1.union(r2) END
          END
      | ConType.Cons =>
          EtpLogConstraint(1);
          VAR pair := List3(PairOp, con.arg[1], con.arg[2]); BEGIN
            pair.type := Type.Pair;
            EVAL (con.arg[0].root).union(pair)
          END
      | ConType.Plus =>
          EtpLogConstraint(2);
          VAR plus := List3(PlusOp, con.arg[1], con.arg[2]); BEGIN
            SetFuncType(plus, Type.Num);
            EVAL (con.arg[0].root).union(plus)
          END
      | ConType.Times =>
          EtpLogConstraint(3);
          VAR times := List3(TimesOp, con.arg[1], con.arg[2]); BEGIN
            SetFuncType(times, Type.Num);
            EVAL (con.arg[0].root).union(times)
          END
      | ConType.Atan =>
          EtpLogConstraint(6);
          VAR atan := List3(AtanOp, con.arg[1], con.arg[2]); BEGIN
            SetFuncType(atan, Type.Num);
            EVAL (con.arg[0].root).union(atan)
          END
      | ConType.Sin =>
          EtpLogConstraint(7);
          VAR sin := List2(SinOp, con.arg[1]); BEGIN
            SetFuncType(sin, Type.Num);
            EVAL (con.arg[0].root).union(sin)
          END
      | ConType.Cos =>
          EtpLogConstraint(8);
          VAR cos := List2(CosOp, con.arg[1]); BEGIN
            SetFuncType(cos, Type.Num);
            EVAL (con.arg[0].root).union(cos)
          END
      | ConType.Exp =>
          EtpLogConstraint(9);
          VAR exp := List2(ExpOp, con.arg[1]); BEGIN
            SetFuncType(exp, Type.Num);
            EVAL (con.arg[0].root).union(exp)
          END
      | ConType.Real => EtpLogConstraint(4); SetType(con.arg[0], Type.Num)
      | ConType.Text => EtpLogConstraint(5); SetType(con.arg[0], Type.Text)
      END
    END;
  END ConstructECs;

PROCEDURE SetFuncType(v: Var; t: Type) RAISES {Equiv.Forbidden} =
Sets the type of v to t. Verifies that the type of each argument in v.cdr is at most t in the flat partial order with Type.Any as bottom, and promotes the type of each argument with type Type.Any to t.

Requires v to be a root. Raises Equiv.Forbidden if some argument in v.cdr has a type that is not at most t.

  BEGIN
    <* ASSERT v.root = v *>
    v.type := t;
    VAR curr: Var := v.cdr; BEGIN
      WHILE curr # NIL DO
        SetType(curr.car, t);
        curr := curr.cdr
      END
    END
  END SetFuncType;

PROCEDURE SetType(v: Var; t: Type) RAISES {Equiv.Forbidden} =
Sets the type of the class containing v to t, or raises Equiv.Forbidden if the current type of v's class is different from t and greater than bottom.
  VAR rt: Var := v.root; BEGIN
    IF rt.type = Type.Any THEN
      rt.type := t
    ELSIF rt.type # t THEN
      RAISE Equiv.Forbidden
    END;
  END SetType;

PROCEDURE List3(x, y, z: Egraph.T): Var =
  BEGIN
    RETURN NewPair(car := x, cdr := NewPair(car := y,
      cdr := NewPair(car := z, cdr := NIL)))
  END List3;

PROCEDURE List2(x, y: Egraph.T): Var =
  BEGIN
    RETURN NewPair(car := x, cdr := NewPair(car := y, cdr := NIL))
  END List2;

PROCEDURE Merge(x: EC; y: Equiv.T): Equiv.T RAISES {Equiv.Forbidden} =
By the precondition on Equiv.T.union(), x and y must be the roots of their equivalence classes. They will also be of type JunoSolve.Var. Let rx and ry be the results of narrowing x and y, respectively, to the type JunoSolve.Var. Let root be the root of their resulting combined equivalence class. Then Merge either raises the exception or establishes the following post-conditions:

Q1: root.type = the meet of rx.type and ry.type in the flat partial order. If the meet is undefined, raise Equiv.Forbidden.

Q2: root.known = rx.known OR ry.known. root.val = rx.val if rx.known, and root.val = ry.val if ry.known. If both are known, but their values are different, raise Equiv.Forbidden. If NOT root.known, then root.val is rx.val or ry.val for whichever of those two values is non-NIL, or either if both are non-NIL.

Q3: root.cons is selected from rx.cons and ry.cons so that it is NIL only if both of them are. If both are non-NIL, then the values rx.cons and ry.cons are added to the global unify list.

Q4: If root.cons # NIL and root.val is a pair, then the car and cdr of root.val have been propagated to the arguments of the cons. If root.known and root.cons # NIL, raise Equiv.Forbidden if the known value is not a pair.

  <* LL >= { mu } *>
  VAR
    rx: Var := x; ry: Var := y;
    propagateKnowns := rx.known # ry.known AND
                       (rx.cons = NIL) = rx.known AND
                       (ry.cons = NIL) = ry.known;
    propagateHints  := (rx.val # NIL) # (ry.val # NIL) AND
                       (rx.cons = NIL) = (rx.val # NIL) AND
                       (ry.cons = NIL) = (ry.val # NIL);
    (* Work is necessary to establish Q4 iff "propagateKnowns OR
       propagateHints". *)
  BEGIN
    <* ASSERT rx # ry *>
    VAR root: Var := Egraph.T.union(rx, ry); nonroot: Var; BEGIN
      IF root = rx THEN nonroot := ry ELSE nonroot := rx END;
      (* Establish Q1 *)
      IF root.type = Type.Any THEN
        root.type := nonroot.type
      ELSIF nonroot.type # Type.Any AND root.type # nonroot.type THEN
        RAISE Equiv.Forbidden
      END;
      (* Establish Q2 *)
      IF nonroot.known THEN
        IF NOT root.known THEN
          root.known := TRUE; root.val := nonroot.val
        ELSIF NOT RTVal.Equal(rx.val, ry.val) THEN
          RAISE Equiv.Forbidden
        END;
      ELSIF NOT root.known THEN
        IF nonroot.val # NIL THEN root.val := nonroot.val END
      END;
      (* Establish Q3 *)
      IF nonroot.cons # NIL THEN
        IF root.cons = NIL THEN
          root.cons := nonroot.cons;
        ELSE
          unifyList.addhi(rx.cons);
          unifyList.addhi(ry.cons)
        END
      END;
      (* Establish Q4 *)
      IF propagateHints OR propagateKnowns THEN
        TYPECASE root.val OF
        | RTVal.Pair (p) =>
            Fix(root.cons.cdr.car,     root.known, p.car);
            Fix(root.cons.cdr.cdr.car, root.known, p.cdr)
        ELSE
            IF root.known THEN RAISE Equiv.Forbidden END
        END;
      END;
      RETURN root
    END;
  END Merge;

PROCEDURE Fix(v: Var; known: BOOLEAN; val: RTVal.T) RAISES {Equiv.Forbidden} =
  VAR t := New(known, val); BEGIN EVAL (v.root).union(t) END Fix;

PROCEDURE UnifyClose() RAISES {Equiv.Forbidden} =
  BEGIN
    WHILE unifyList.size() # 0 DO
      VAR
        y: EC := unifyList.remhi();
        x: EC := unifyList.remhi();
      BEGIN
        <* ASSERT x.car = PairOp AND y.car = PairOp *>
        x := x.cdr; y := y.cdr;
        WHILE x # NIL DO
          VAR xa := x.car.root; ya := y.car.root; BEGIN
            IF xa # ya THEN EVAL xa.union(ya) END
          END;
          x := x.cdr; y := y.cdr
        END;
        <* ASSERT y = NIL *>
      END
    END
  END UnifyClose;

PROCEDURE NumericSolve(READONLY c: ARRAY OF Constraint) RAISES { Unsolvable } =
By a {\em variable}, we mean a Var that is the root of its equivalence class and appears in a numeric constraint in c. We say a variable v is {\em labeled} if v.index # NoIndex and numeric_vars[v.index] = v. We say a variable v {\em occurs} in a constraint con if it is unlabeled and con contains a Var whose root is v.

Q1: For each constraint con, con.hintCnt is the number of distinct unlabeled variables occurring in con, and con.mask is 2_b2b1b0, where bi == ``con.arg[i] is unlabeled''.

Q2: There are two linked lists of constraints whose heads are ghostReady and trueReady. Constraints on both lists have hintCnt <= 1, and have valid nls_con and unknown fields set by Functional(). Functional constraints with hintCnt equal to 1 are on the ghostReady queue, while non-functional constraints with hintCnt equal to 1 are on the trueReady queue. All constraints with hintCnt equal to 0 appear on one or the other of the two queues.

Q3: For any variable v, v.uses is a list that contains an entry ul for each constraint in which v occurs. The value ul.c is the constraint, and ul.mask is 2_b2b1b0 where bi == (ul.c.arg[i] = v).

Q4: The hint for an unlabeled variable is either NIL or a numeric value.

Q5: The variable var_cnt is the number of non-constant variables. The variable numeric_cnt is the number of numeric constraints.

Q6: Every constant has been labeled by a number between next_const + 1 and LAST(numeric_vars^).

Q7: The variable hvl is the list of hinted unlabeled variables (possibly containing some variables which have become labeled), and uvl is the list of unhinted, unlabeled variables (also possible containing some variables which have become labeled).

Q8: numeric_cnt <= NUMBER(numeric_con).

Q9: All knowns (constants) are labeled, and all unknowns are unlabeled.

R1: The variable lo is the number of labeled ``true'' variables, each of which has been labeled with a number between 0 and lo - 1.

R2: The expression var_cnt - hi is the number of labeled ghost variables, each of which has been labeled by a number between hi and var_cnt - 1.

R3: The variable c_lo is the number of ghost constraints; these constraints are stored in the first c_lo entries of numeric_con. The expression numeric_cnt - c_hi is the number of true constraints; these constraints are stored in the last entries of numeric_con.

      DURING VARIABLE PROCESSING:
      
                  numeric_vars[]           __
                     ________	      |..|
                    |        |	      |..| = unused
                    |  True  |	      |__|
                    |  Vars  |
                    |________|
              lo -> |........|
                    |........|    numeric_con[]
              hi -> |________|    _____________
                    |        |   |             |
                    |  Ghost |   |    Ghost    |
                    |  Vars  |   | Constraints |
                    |________|   |_____________|
         var_cnt -> |........|   |.............| <- c_lo
                    |........|   |.............|
      next_const -> |________|   |_____________| <- c_hi
                    |        |   |             |
                    | Consts |   |     True    |
                    |________|   | Constraints |
      			     |_____________|
                                 |.............| <- numeric_cnt
                                 |.............|
      			     |_____________|
      
      AFTER VARIABLE PROCESSING:
      
                  numeric_vars[] numeric_vals[]
                     ________       ________
                    |        |     |        |
                    |  True  |     |  True  |
                    |  Vars  |     |  Vals  |
                    |        |     |        |    numeric_con[]
              hi -> |________|     |________|    _____________
              lo -> |        |     |        |   |             |
                    |        |     |        |   |    Ghost    |
                    |        |     |        |   | Constraints |
                    |  Ghost |     |  Ghost |   |             |
                    |  Vars  |     |  Vals  |   |             |
                    |________|     |________|   |_____________| <- c_hi
         var_cnt -> |........|     |........|   |             | <- c_lo
                    |........|     |........|   |             |
      next_const -> |________|     |________|   |     True    |
                    |        |     |        |   | Constraints |
                    | Consts |     | Consts |   |_____________|
                    |________|     |________|   |.............| <- numeric_cnt
            		                    |.............|
                                                |_____________|
  VAR
    lo, hi, c_lo, c_hi: CARDINAL;
    var_cnt: CARDINAL := 0;		 (* total # of variables *)
    numeric_cnt: CARDINAL := 0;		 (* total # of numeric constraints *)
    next_const := LAST(numeric_vars^);	 (* index of next known value *)
    ghostReady, trueReady: Constraint := NIL;
    hvl, uvl: Var := NIL;

  PROCEDURE NumericArgCnt(c: Constraint): CARDINAL =
  (* Return the number of variables in constraint "c" if it is a numeric
     constraint; 0 otherwise. *)
    BEGIN
      CASE c.type OF
	ConType.Plus, ConType.Times, ConType.Atan => RETURN 3
      | ConType.Sin, ConType.Cos, ConType.Exp => RETURN 2
      ELSE RETURN 0
      END
    END NumericArgCnt;

  PROCEDURE AddConToQueue(con: Constraint) =
    BEGIN
      IF Functional(con)
        THEN con.next := ghostReady; ghostReady := con
        ELSE con.next := trueReady; trueReady := con
      END
    END AddConToQueue;

  PROCEDURE UpdateUses(l: UseList) =
    BEGIN
      WHILE l # NIL DO
        DEC(l.c.mask, l.mask);
        DEC(l.c.hintCnt);
        IF l.c.hintCnt = 1 THEN AddConToQueue(l.c) END;
        l := l.next
      END
    END UpdateUses;

  (* NumericSolve *)
  BEGIN
    FOR i := FIRST(c) TO LAST(c) DO
      VAR con := c[i]; cnt := NumericArgCnt(con); BEGIN
        IF cnt > 0 THEN
          INC(numeric_cnt);
          con.hintCnt := 0;
          con.mask := 0;
          FOR j := 0 TO cnt - 1 DO
            VAR v: Var := con.arg[j].root; BEGIN
              IF NOT v.known THEN	 (* "v" is an unknown *)
                IF v.uses = NIL THEN	 (* first time seeing "v" *)
                  INC(var_cnt);
                  (* disregard any non-numeric hint *)
                  IF v.val # NIL AND NOT ISTYPE(v.val, RTVal.Number) THEN
                    v.val := NIL
                  END;
                  IF v.val = NIL
                    THEN v.next := uvl; uvl := v (* unhinted *)
                    ELSE v.next := hvl; hvl := v (*  hinted  *)
                  END
                END;
                (* add "con" to "v.uses" if necessary *)
                IF v.uses = NIL OR v.uses.c # con THEN
                  v.uses := NewUse(con, next := v.uses);
                  INC(con.hintCnt)
                END;
                (* update the variable and constraint masks *)
                VAR bit := Word.LeftShift(1, j); BEGIN
                  v.uses.mask := Word.Or(v.uses.mask, bit);
                  con.mask := Word.Or(con.mask, bit)
                END
              ELSIF v.index = NoIndex THEN
                numeric_vars[next_const] := v;
                v.index := next_const;
                DEC(next_const)
              END
            END
          END; (* FOR *)
          (* Add to "ghostReady" or "trueReady" queue if necessary *)
          IF con.hintCnt <= 1 THEN AddConToQueue(con) END
        END
      END
    END;
    (* Q1 - Q7, Q9 *)
    IF numeric_cnt = 0 THEN RETURN END;

    IF numeric_cnt > NUMBER(numeric_con^) THEN
      numeric_con := NEW(REF ARRAY OF NonLinearSolve.Constraint,
        MAX(numeric_cnt, 2 * NUMBER(numeric_con^)))
    END;
    (* Q1 - Q8, Q9 *)

    lo := 0;
    hi := var_cnt;
    c_lo := 0;
    c_hi := numeric_cnt;
    LOOP
      (* Q1 - Q8, R1 - R3 *)
      IF ghostReady # NIL THEN
       	VAR con := ghostReady; BEGIN
          <* ASSERT con.hintCnt <= 1 *>
          ghostReady := ghostReady.next;
          IF con.hintCnt = 0 THEN
            (* add "con" to the "trueReady" queue *)
            con.next := trueReady;
            trueReady := con
          ELSE
            (* label "con.unknown" as a ghost variable *)
            DEC(hi);
            numeric_vars[hi] := con.unknown;
            con.unknown.index := hi;
            UpdateUses(con.unknown.uses);
            (* finish constructing "con.nls_con" *)
            con.nls_con.arg[0] := hi;
            (* make "con.nls_con" a ghost constraint *)
            numeric_con[c_lo] := con.nls_con;
            INC(c_lo)
          END
        END
      ELSIF hvl # NIL THEN
        IF hvl.index = NoIndex THEN
          (* make "hvl" a true variable *)
          numeric_vars[lo] := hvl;
          hvl.index := lo;
          INC(lo);
          UpdateUses(hvl.uses)
        END;
        hvl := hvl.next
      ELSIF trueReady # NIL THEN
        VAR con := trueReady; BEGIN
          <* ASSERT con.hintCnt <= 1 *>
          trueReady := trueReady.next;
          IF con.hintCnt = 1 THEN
            (* make "con.unknown" a true variable *)
            numeric_vars[lo] := con.unknown;
            con.unknown.index := lo;
            INC(lo);
            UpdateUses(con.unknown.uses)
          END;
          (* finish constructing "con.nls_con" from "con.nls_con_mask" *)
          VAR i := 0; BEGIN
            WHILE con.nls_con_mask > 0 DO
              IF Word.And(con.nls_con_mask, 2_1) = 2_1 THEN
                con.nls_con.arg[i] := con.unknown.index
              END;
              INC(i);
              con.nls_con_mask := Word.RightShift(con.nls_con_mask, 1)
            END
          END;
          (* make "con.nls_con" a true constraint *)
          DEC(c_hi);
          numeric_con[c_hi] := con.nls_con
        END
      ELSIF uvl # NIL THEN
        IF uvl.index = NoIndex THEN
          (* make "uvl" a true variable *)
          numeric_vars[lo] := uvl;
          uvl.index := lo;
          INC(lo);
          UpdateUses(uvl.uses)
        END;
        uvl := uvl.next
      ELSE EXIT
      END
    END;

    (* Solve *)
    InitVals(numeric_vals, numeric_vars, lo, next_const);
    WITH constraints = SUBARRAY(numeric_con^, 0, numeric_cnt) DO
      IF NOT NonLinearSolve.P(lo, var_cnt, numeric_vals^, constraints)
        THEN RAISE Unsolvable
      END
    END;
    NonLinearSolve.Dispose()
  END NumericSolve;

PROCEDURE InitVals(VAR numeric_vals: REF ARRAY OF RTVal.Real;
                   numeric_vars: Vars;
                   lo, next_const: INTEGER) =
  (* Fill in "numeric_vals" from "numeric_vars" *)
  BEGIN
    IF NUMBER(numeric_vals^) < NUMBER(numeric_vars^) THEN
      numeric_vals := NEW(REF ARRAY OF RTVal.Real, NUMBER(numeric_vars^))
    END;
    (* fill in ``true'' variable values *)
    FOR i := 0 TO lo - 1 DO
      TYPECASE numeric_vars[i].val OF
      | RTVal.Null => numeric_vals[i] := 0.0
      | RTVal.Number (r) => numeric_vals[i] := r.val
      ELSE numeric_vals[i] := 0.0
      END
    END;
    (* fill in constant (known) values *)
    FOR i := next_const + 1 TO LAST(numeric_vars^) DO
      TYPECASE numeric_vars[i].val OF <* NOWARN *>
      | RTVal.Number (r) => numeric_vals[i] := r.val
      END
    END
  END InitVals;

CONST NoArg = FIRST(INTEGER);

PROCEDURE Functional(con: Constraint): BOOLEAN =
Requires con.hintCnt <= 1, i.e., that there is at most one unknown in the constraint.

If con.mask # 0 and it indicates that con is functional in the single unknown variable, then set con.unknown to that unknown variable, set con.nls_con to a constraint that solves it, and return TRUE.

Otherwise, set con.nls_con to a NonLinearSolve.Constraint clone of con, set con.unknown to NIL, and return FALSE.

In either case, the arguments in con.nls_con corresponding to the unknown are unset.

  CONST LoBit = ARRAY [1..7] OF [0..2]{0, 1, 0, 2, 0, 1, 0}; BEGIN
    <* ASSERT con.hintCnt <= 1 *>
    IF con.mask = 0
      THEN con.unknown := NIL
      ELSE con.unknown := con.arg[LoBit[con.mask]].root
    END;
    CASE con.type OF <* NOWARN *>
      ConType.Plus =>
        CASE con.mask OF <* NOWARN *>
          2_001 =>
            con.nls_con := SetArgs(NonLinearSolve.NewPlus(), con, 1, 2);
            con.nls_con_mask := 2_001
        | 2_010 =>
            con.nls_con := SetArgs(NonLinearSolve.NewMinus(), con, 0, 2);
            con.nls_con_mask := 2_001
        | 2_100 =>
            con.nls_con := SetArgs(NonLinearSolve.NewMinus(), con, 0, 1);
            con.nls_con_mask := 2_001
        | 2_110 =>
            con.nls_con := SetArgs(NonLinearSolve.NewHalve(), con, 0);
            con.nls_con_mask := 2_001
        | 2_000, 2_101, 2_011, 2_111 =>
            con.nls_con := SetArgs(NonLinearSolve.NewPlus(), con, 1, 2, 0);
            con.nls_con_mask := con.mask;
            RETURN FALSE
        END
    | ConType.Times =>
        IF con.mask = 2_001 THEN
          con.nls_con := SetArgs(NonLinearSolve.NewTimes(), con, 1, 2);
          con.nls_con_mask := 2_001
        ELSE
          con.nls_con := SetArgs(NonLinearSolve.NewTimes(), con, 1, 2, 0);
          con.nls_con_mask := con.mask;
          RETURN FALSE
        END
    | ConType.Atan =>
        CASE con.mask OF <* NOWARN *>
        | 2_001 =>
            con.nls_con := SetArgs(NonLinearSolve.NewAtan(), con, 1, 2);
            con.nls_con_mask := 2_001
        | 2_010 =>
            con.nls_con := SetArgs(NonLinearSolve.NewMultTan(), con, 2, 0);
            con.nls_con_mask := 2_001
        | 2_000, 2_100, 2_011, 2_101, 2_110, 2_111 =>
            con.nls_con := SetArgs(NonLinearSolve.NewAtan(), con, 1, 2, 0);
            con.nls_con_mask := con.mask;
            RETURN FALSE
        END
    | ConType.Sin =>
        IF con.mask = 2_01 THEN
          con.nls_con := SetArgs(NonLinearSolve.NewSin(), con, 1);
          con.nls_con_mask := 2_01
        ELSE
          con.nls_con := SetArgs(NonLinearSolve.NewSin(), con, 1, NoArg, 0);
          con.nls_con_mask := con.mask;
          RETURN FALSE
        END
    | ConType.Cos =>
        IF con.mask = 2_01 THEN
          con.nls_con := SetArgs(NonLinearSolve.NewCos(), con, 1);
          con.nls_con_mask := 2_01
        ELSE
          con.nls_con := SetArgs(NonLinearSolve.NewCos(), con, 1, NoArg, 0);
          con.nls_con_mask := con.mask;
          RETURN FALSE
        END
    | ConType.Exp =>
        IF con.mask = 2_01 THEN
          con.nls_con := SetArgs(NonLinearSolve.NewExp(), con, 1);
          con.nls_con_mask := 2_01
        ELSE
          con.nls_con := SetArgs(NonLinearSolve.NewExp(), con, 1, NoArg, 0);
          con.nls_con_mask := con.mask;
          RETURN FALSE
        END
    END;
    RETURN TRUE
  END Functional;

PROCEDURE SetArgs(
    nlc: NonLinearSolve.Constraint;
    con: Constraint;
    a1: CARDINAL;
    a2, a0: INTEGER := NoArg)
  : NonLinearSolve.Constraint =
  BEGIN
    nlc.arg[1] := NARROW(con.arg[a1].root, Var).index;
    IF a2 # NoArg THEN nlc.arg[2] := NARROW(con.arg[a2].root, Var).index END;
    IF a0 # NoArg THEN nlc.arg[0] := NARROW(con.arg[a0].root, Var).index END;
    RETURN nlc
  END SetArgs;

PROCEDURE ConstructSoln(READONLY c: ARRAY OF Constraint) RAISES {Unsolvable} =
Set the val field of each unknown in c to the value of its root.
  BEGIN
    FOR i := FIRST(c) TO LAST(c) DO
      VAR argCnt: CARDINAL; BEGIN
        CASE c[i].type OF <* NOWARN *>
          ConType.Cons, ConType.Plus, ConType.Times, ConType.Atan =>
            argCnt := 3
        | ConType.Sin, ConType.Cos, ConType.Exp, ConType.Equal =>
            argCnt := 2
        | ConType.Real, ConType.Text =>
            argCnt := 1
        END;
        FOR j := 0 TO argCnt - 1 DO
          VAR arg := c[i].arg[j]; BEGIN
            IF NOT arg.known THEN arg.val := Value(arg.root) END
          END
        END
      END
    END
  END ConstructSoln;

PROCEDURE Value(v: Var): RTVal.T RAISES {Unsolvable} =
Return the value of v, which must be the root of its equivalence class.
  BEGIN
    <* ASSERT v = v.root *>
    IF NOT v.known THEN
      IF v.cons # NIL THEN
        IF v.marked THEN
          RAISE Unsolvable
        ELSE
          v.marked := TRUE;
          v.val := RTVal.FromPair(
            car := Value(v.cons.cdr.car.root),
            cdr := Value(v.cons.cdr.cdr.car.root))
        END
      ELSIF v.index # NoIndex THEN
        v.val := RTVal.FromReal(numeric_vals[v.index])
      ELSE
        (* "v" is an unknown var that is not equivalent to a cons nor involved
	   in any numeric constraints. It may not have a type, or it may have
           a non-trivial type due to a REAL or TEXT constraint. Assign "v" a
           valid value for its type if it does not already have one. *)
        CASE v.type OF <* NOWARN *>
	| Type.Any =>
            IF v.val = NIL THEN v.val := RTVal.nil END
        | Type.Num =>
            IF v.val = NIL OR NOT ISTYPE(v.val, RTVal.Number) THEN
              v.val := zero
            END
        | Type.Text =>
            IF v.val = NIL OR NOT ISTYPE(v.val, RTVal.Text) THEN
              v.val := emptyString
            END
        END
      END;
      v.known := TRUE;
    END;
    RETURN v.val
  END Value;

BEGIN
END JunoSolve.