<* PRAGMA LL *> MODULEA; 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; JunoSolve 
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. 
PROCEDURENew (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; PROCEDURENewPair (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; PROCEDURENewCon (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; PROCEDURENewEqual (x, y: Var): Constraint = VAR res := NewCon(ConType.Equal); BEGIN res.arg[0] := x; res.arg[1] := y; RETURN res END NewEqual; PROCEDURENewCons (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; PROCEDURENewPlus (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; PROCEDURENewTimes (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; PROCEDURENewAtan (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; PROCEDURENewSin (x, y: Var): Constraint = VAR res := NewCon(ConType.Sin); BEGIN res.arg[0] := x; res.arg[1] := y; RETURN res END NewSin; PROCEDURENewCos (x, y: Var): Constraint = VAR res := NewCon(ConType.Cos); BEGIN res.arg[0] := x; res.arg[1] := y; RETURN res END NewCos; PROCEDURENewExp (x, y: Var): Constraint = VAR res := NewCon(ConType.Exp); BEGIN res.arg[0] := x; res.arg[1] := y; RETURN res END NewExp; PROCEDURENewReal (x: Var): Constraint = VAR res := NewCon(ConType.Real); BEGIN res.arg[0] := x; RETURN res END NewReal; PROCEDURENewText (x: Var): Constraint = VAR res := NewCon(ConType.Text); BEGIN res.arg[0] := x; RETURN res END NewText; PROCEDURENewUse (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; PROCEDUREDispose () = 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; PROCEDUREInit (v: Var): Var =
Modifyvinto a variablev'such thatv'.root = v'and such that P1 - P5 hold ofv'. Returnv'.
  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; PROCEDUREConstructECs (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; PROCEDURESetFuncType (v: Var; t: Type) RAISES {Equiv.Forbidden} =
Sets the type ofvtot. Verifies that the type of each argument inv.cdris at mosttin the flat partial order withType.Anyas bottom, and promotes the type of each argument with typeType.Anytot.Requires
vto be a root. RaisesEquiv.Forbiddenif some argument inv.cdrhas a type that is not at mostt.
  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 containingvtot, or raisesEquiv.Forbiddenif the current type ofv's class is different fromtand 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 onEquiv.T.union(),xandymust be the roots of their equivalence classes. They will also be of typeJunoSolve.Var. Letrxandrybe the results of narrowingxandy, respectively, to the typeJunoSolve.Var. Letrootbe the root of their resulting combined equivalence class. ThenMergeeither raises the exception or establishes the following post-conditions:Q1:
root.type= the meet ofrx.typeandry.typein the flat partial order. If the meet is undefined, raiseEquiv.Forbidden.Q2:
root.known = rx.known OR ry.known.root.val = rx.valifrx.known, androot.val = ry.valifry.known. If both are known, but their values are different, raiseEquiv.Forbidden. IfNOT root.known, thenroot.valisrx.valorry.valfor whichever of those two values is non-NIL, or either if both are non-NIL.Q3:
root.consis selected fromrx.consandry.consso that it isNILonly if both of them are. If both are non-NIL, then the valuesrx.consandry.consare added to the global unify list.Q4: If
root.cons # NILandroot.valis a pair, then the car and cdr ofroot.valhave been propagated to the arguments of the cons. Ifroot.knownandroot.cons # NIL, raiseEquiv.Forbiddenif 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 aVarthat is the root of its equivalence class and appears in a numeric constraint inc. We say a variablevis {\em labeled} ifv.index # NoIndexandnumeric_vars[v.index] = v. We say a variablev{\em occurs} in a constraintconif it is unlabeled andconcontains aVarwhose root isv.Q1: For each constraint
con,con.hintCntis the number of distinct unlabeled variables occurring incon, andcon.maskis2_b2b1b0, wherebi== ``con.arg[i]is unlabeled''.Q2: There are two linked lists of constraints whose heads are
ghostReadyandtrueReady. Constraints on both lists havehintCnt <= 1, and have validnls_conandunknownfields set by Functional(). Functional constraints withhintCntequal to 1 are on theghostReadyqueue, while non-functional constraints withhintCntequal to 1 are on thetrueReadyqueue. All constraints withhintCntequal to 0 appear on one or the other of the two queues.Q3: For any variable
v,v.usesis a list that contains an entryulfor each constraint in whichvoccurs. The valueul.cis the constraint, andul.maskis2_b2b1b0wherebi==(ul.c.arg[i] = v).Q4: The hint for an unlabeled variable is either
NILor a numeric value.Q5: The variable
var_cntis the number of non-constant variables. The variablenumeric_cntis the number of numeric constraints.Q6: Every constant has been labeled by a number between
next_const + 1andLAST(numeric_vars^).Q7: The variable
hvlis the list of hinted unlabeled variables (possibly containing some variables which have become labeled), anduvlis 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
lois the number of labeled ``true'' variables, each of which has been labeled with a number between0andlo - 1.R2: The expression
var_cnt - hiis the number of labeled ghost variables, each of which has been labeled by a number betweenhiandvar_cnt - 1.R3: The variable
c_lois the number of ghost constraints; these constraints are stored in the firstc_loentries ofnumeric_con. The expressionnumeric_cnt - c_hiis the number of true constraints; these constraints are stored in the last entries ofnumeric_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 =
Requirescon.hintCnt <= 1, i.e., that there is at most one unknown in the constraint.If
con.mask# 0 and it indicates thatconis functional in the single unknown variable, then setcon.unknownto that unknown variable, setcon.nls_conto a constraint that solves it, and return TRUE.Otherwise, set
con.nls_conto aNonLinearSolve.Constraintclone ofcon, setcon.unknownto NIL, and return FALSE.In either case, the arguments in
con.nls_concorresponding 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 thevalfield of each unknown incto 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.