)abbrev category CACHSET CachableSet
++ Sets whose elements can cache an integer
++ Author: Manuel Bronstein
++ Date Created: 31 Oct 1988
++ Description:
++   A cachable set is a set whose elements keep an integer as part
++   of their structure.
CachableSet : Category == SetCategory with
  position   : % -> NonNegativeInteger
    ++ position(x) returns the integer n associated to x.
  setPosition : (%, NonNegativeInteger) -> Void
    ++ setPosition(x, n) associates the integer n to x.

)abbrev package SCACHE SortedCache
++ Cache of elements in a set
++ Author: Manuel Bronstein
++ Date Created: 31 Oct 1988
++ Description:
++   A sorted cache of a cachable set S is a dynamic structure that
++   keeps the elements of S sorted and assigns an integer to each
++   element of S once it is in the cache. This way, equality and ordering
++   on S are tested directly on the integers associated with the elements
++   of S, once they have been entered in the cache.
SortedCache(S : CachableSet) : Exports == Implementation where
  N    ==> NonNegativeInteger
  DIFF ==> 1024

  Exports ==> with
    clearCache  : () -> Void
      ++ clearCache() empties the cache.
    enterInCache : (S, S -> Boolean) -> S
      ++ enterInCache(x, f) enters x in the cache, calling \spad{f(y)} to
      ++ determine whether x is equal to y. It returns x with an integer
      ++ associated with it.
    linearSearch : (S -> Boolean) -> Union(S, "failed")
      ++ linearSearch(f) searches the cache, calling \spad{f(y)} find
      ++ desired y.  It returns y from cache if f(y) is true or "failed"
      ++ if no such y exists.
    enterInCache : (S, (S, S) -> Integer) -> S
      ++ enterInCache(x, f) enters x in the cache, calling \spad{f(x, y)} to
      ++ determine whether \spad{x < y (f(x, y) < 0), x = y (f(x, y) = 0)}, or
      ++ \spad{x > y (f(x, y) > 0)}.
      ++ It returns x with an integer associated with it.
    binarySearch : (S, (S, S) -> Integer) -> Union(S, "failed")
      ++ binarySearch(x, f) searches x in the cache, calling \spad{f(x, y)}
      ++ to determine order.  It returns y from cache
      ++ if f(x, y) is 0 or "failed" if no such y exists.

  Implementation ==> add
    shiftCache   : (N, N) -> Void
    insertInCache : (N, S, N) -> S
    expandCache : (S) -> Void
    insertBefore : (N, S) -> Void

    cache : PrimitiveArray S := empty()$(PrimitiveArray S)
    cache_size : N := 0
    cache_use : N := 0

    expandCache(x) ==
        if cache_size = cache_use then
            ocache := cache
            cache_size := 2*cache_size + 10
            cache := new(cache_size, x)$(PrimitiveArray S)
            for k in 0..(cache_use - 1) repeat
                cache(k) := ocache(k)
        void

    insertBefore(l, x) ==
        k : Integer
        expandCache(x)
        vscan := cache
        for k in 0..(cache_use - l - 1) repeat
            vscan(cache_use - k) := vscan(cache_use - k - 1)
        vscan(l) := x
        cache_use := cache_use + 1
        void

    shiftCache(l, n) ==
        k : Integer
        vscan := cache
        for k in l..(cache_use - 1) repeat
            x := vscan(k)
            setPosition(x, n + position x)
        void

    clearCache() ==
        k : Integer
        vscan := cache
        for k in 0..(cache_use - 1) repeat
            x := vscan(k)
            setPosition(x, 0)
        cache := empty()$(PrimitiveArray S)
        cache_size := 0
        cache_use := 0
        void

    insertAtEnd(x : S) : Void ==
        expandCache(x)
        cache(cache_use) := x
        cache_use := cache_use + 1
        void

    linearSearch(equal? : S -> Boolean) ==
        k : Integer := 0
        -- Can not use for loop because equal? can insert new elements
        -- and change cache_use
        while k < cache_use repeat
            vscan := cache
            y := vscan(k)
            equal?(y) => return y
            vscan := cache
            -- skip over elements possibly inserted by equal?
            while not(EQ(y, vscan(k))$Lisp) repeat k := k + 1
            k := k + 1
        return "failed"

    enterInCache(x : S, equal? : S -> Boolean) ==
        (res := linearSearch(equal?)) case S => res
        setPosition(x, 1 + cache_use)
        insertAtEnd(x)
        x

    search_body ==>
        vscan := cache
        l : Integer := -1
        m : Integer := cache_use
        m0 := m
        while (l + 1) < m repeat
            vl : S
            vm : S
            m0 := cache_use
            if not(l < 0) then
                vl := qelt(vscan, l)
            has_vm := false
            if m < m0 then
                vm := qelt(vscan, m)
                has_vm := true
            i := shift(l + m, -1)
            cp := triage(x, y := qelt(vscan, i))
            zero?(cp) => return y
            vscan := cache
            if not(l < 0) then
                if not(EQ(vl, qelt(vscan, l))$Lisp) then
                    l0 := l
                    while not(EQ(vl, qelt(vscan, l))$Lisp) repeat
                        l := l + 1
                    i := i + l - l0
                    m := m + l - l0
            if not(EQ(y, qelt(vscan, i))$Lisp) then
                i0 := i
                while not(EQ(y, qelt(vscan, i))$Lisp) repeat
                    i := i + 1
                m := m + i - i0
            if has_vm then
                if not(EQ(vm, qelt(vscan, m))$Lisp) then
                    while not(EQ(vm, qelt(vscan, m))$Lisp) repeat
                        m := m + 1
            if cp < 0 then
                m := i
            else
                l := i

    binarySearch(x : S, triage : (S, S) -> Integer) ==
        zero?(cache_use) => "failed"
        search_body
        "failed"

    enterInCache(x : S, triage : (S, S) -> Integer) ==
        zero?(cache_use) =>
            setPosition(x, DIFF)
            insertAtEnd(x)
            return x
        search_body
        m = cache_use =>
            setPosition(x, (position qelt(vscan, m - 1)) + DIFF)
            insertAtEnd(x)
            return x
        pos : N :=
                l < 0 => 0
                position qelt(vscan, l)
        insertInCache((l+1)::N, x, pos)

    insertInCache(before, x, pos) ==
        y := cache(before)
        if ((pos+1) = position y) then shiftCache(before, DIFF)
        setPosition(x, pos + (((position y) - pos)::N quo 2))
        insertBefore(before, x)
        x

)abbrev domain MKCHSET MakeCachableSet
++ Make a cachable set from any set
++ Author: Manuel Bronstein
++ Description:
++   MakeCachableSet(S) returns a cachable set which is equal to S as a set.
MakeCachableSet(S : SetCategory) : Exports == Implementation where
  Exports ==> Join(CachableSet, CoercibleTo S) with
    coerce : S -> %
      ++ coerce(s) returns s viewed as an element of %.

  Implementation ==> add
    import from SortedCache(%)

    Rep := Record(setpart : S, pos : NonNegativeInteger)

    clearCache()

    position x             == x.pos
    setPosition(x, n)      == (x.pos := n; void)
    coerce(x : %) : S          == x.setpart
    coerce(x : %) : OutputForm == x::S::OutputForm
    coerce(s : S) : %          == enterInCache([s, 0]$Rep,
                                 (x1 +-> (s = x1::S))@(% -> Boolean))

--    x < y ==
--      if position(x) = 0 then enterInCache(x, x1+->(x::S = x1::S))
--      if position(y) = 0 then enterInCache(y, x1+->(y::S = x1::S))
--      position(x) < position(y)

    x = y ==
      if position(x) = 0 then
           x := enterInCache(x, (x1 +-> (x::S = x1::S))@(% -> Boolean))
      if position(y) = 0 then
           y := enterInCache(y, (x1 +-> (y::S = x1::S))@(% -> Boolean))
      position(x) = position(y)

)abbrev category KERCAT KernelCategory
++ Operators applied to elements of a set
++ Author: Manuel Bronstein
++ Date Created: 22 March 1988
++ Description:
++ A kernel over a set S is an operator applied to a given list
++ of arguments from S.
KernelCategory(S : Comparable) : Category == Exports where
  O  ==> OutputForm
  N  ==> NonNegativeInteger
  OP ==> BasicOperator

  Exports ==> Join(OrderedSet, Patternable(S)) with
    name    : % -> Symbol
      ++ name(op(a1, ..., an)) returns the name of op.
    operator : % -> OP
      ++ operator(op(a1, ..., an)) returns the operator op.
    argument : % -> List S
      ++ argument(op(a1, ..., an)) returns \spad{[a1, ..., an]}.
    height  : % -> N
      ++ height(k) returns the nesting level of k.
    kernel  : (OP, List S, N) -> %
      ++ kernel(op, [a1, ..., an], m) returns the kernel \spad{op(a1, ..., an)}
      ++ of nesting level m.
      ++ Error: if op is k-ary for some k not equal to n.
    kernel  : Symbol -> %
      ++ kernel(x) returns x viewed as a kernel.
    symbolIfCan : % -> Union(Symbol, "failed")
      ++ symbolIfCan(k) returns k viewed as a symbol if k is a symbol, and
      ++ "failed" otherwise.
    is?     : (%, OP) -> Boolean
      ++ is?(op(a1, ..., an), f) tests if op = f.
    is?     : (%, Symbol) -> Boolean
      ++ is?(op(a1, ..., an), s) tests if the name of op is s.
    if S has ConvertibleTo InputForm then ConvertibleTo InputForm


)abbrev domain KERNEL Kernel
++ Operators applied to elements of a set
++ Author: Manuel Bronstein
++ Date Created: 22 March 1988
++ Description:
++ A kernel over a set S is an operator applied to a given list
++ of arguments from S.
Kernel(S : Comparable) : Exports == Implementation where
  O  ==> OutputForm
  N  ==> NonNegativeInteger
  OP ==> BasicOperator

  Exports ==> Join(CachableSet, KernelCategory(S))

  Implementation ==> add
    import from SortedCache(%)

    operator(k : %) : OP == SPAD_KERNEL_-OP(k)$Lisp
    argument(k : %) : List S == SPAD_KERNEL_-ARG(k)$Lisp
    height(k) == SPAD_KERNEL_-NEST(k)$Lisp
    position(k : %) : N == SPAD_KERNEL_-POSIT(k)$Lisp
    setPosition(k, n) == SET_SPAD_KERNEL_POSIT(k, n)$Lisp
    mkKer(o : OP, a : List S, n : N) : % == makeSpadKernel(o, a, n)$Lisp

    SYMBOL  := '%symbol
    PMPRED  := '%pmpredicate
    PMOPT   := '%pmoptional
    PMMULT  := '%pmmultiple
    PMCONST := '%pmconstant
    SPECIALDISP  := '%specialDisp
    SPECIALEQUAL := '%specialEqual
    SPECIALINPUT := '%specialInput


    clearCache()

    B2Z   : Boolean -> Integer
    triage : (%, %) -> Integer
    preds : OP      -> List Any

    is?(k : %, s : Symbol) == is?(operator k, s)
    is?(k : %, o : OP)     == (operator k) = o
    name k             == name operator k
    B2Z flag           == (flag => -1; 1)
    kernel s           == kernel(assert(operator(s, 0), SYMBOL), [], 1)

    preds o ==
      (u := property(o, PMPRED)) case "failed" => []
      (u@None) pretend List(Any)

    symbolIfCan k ==
      has?(operator k, SYMBOL) => name operator k
      "failed"

    kerEqual(k1 : %, k2 : %, f : (%, %) -> Boolean) : Boolean ==
        height(k1)   ~= height(k2)   => false
        operator(k1) ~= operator(k2) => false
        #(argument k1) ~= #(argument k2) => false
        f(k1, k2)

    kerEqual0(k1 : %, k2 : %) : Boolean ==
        triage(k1, k2) = 0

    kernelEnterInCache(k : %) : % ==
        (res := binarySearch(k, triage)) case % => res
        if (f0 := property(operator k, SPECIALEQUAL)) case None then
            f1 := (f0@None) pretend ((%, %) -> Boolean)
            (res := linearSearch(y +-> kerEqual(k, y, f1))) case % =>
                return res
        else
            (res := linearSearch(y +-> kerEqual0(k, y))) case % =>
                return res
        enterInCache(k, triage)

    k1 = k2 ==
      p1 := position(k1)
      p2 := position(k2)
      p1 ~= 0 and p2 ~= 0 => p1 = p2
      if p1 = 0 then k1 := kernelEnterInCache(k1)
      if p2 = 0 then k2 := kernelEnterInCache(k2)
      position(k1) = position(k2)

    k1 < k2 ==
      p1 := position(k1)
      p2 := position(k2)
      p1 ~= 0 and p2 ~= 0 => p1 < p2
      if p1 = 0 then k1 := kernelEnterInCache(k1)
      if p2 = 0 then k2 := kernelEnterInCache(k2)
      position(k1) < position(k2)

    kernel(fn, x, n) ==
      ((u := arity fn) case N) and (#x ~= u@N)
                                    => error "Wrong number of arguments"
      kernelEnterInCache(mkKer(fn, x, n))

    -- SPECIALDISP contains a map List S -> OutputForm
    -- it is used when the converting the arguments first is not good,
    -- for instance with formal derivatives.
    coerce(k : %) : OutputForm ==
      (v := symbolIfCan k) case Symbol => v@Symbol::OutputForm
      (f := property(o := operator k, SPECIALDISP)) case None =>
        ((f@None) pretend (List S -> OutputForm)) (argument k)
      l := [x::OutputForm for x in argument k]$List(OutputForm)
      (u := display o) case "failed" => prefix(name(o)::OutputForm, l)
      (u::(List OutputForm -> OutputForm)) l

    triage(k1, k2) ==
      height(k1)   ~= height(k2)   => B2Z(height(k1)   < height(k2))
      operator(k1) ~= operator(k2) => B2Z(operator(k1) < operator(k2))
      (n1 := #(argument k1)) ~= (n2 := #(argument k2)) => B2Z(n1 < n2)
      -- Handled by linear search earlier
      -- ((func := property(operator k1, SPECIALEQUAL)) case None) and
      --  (((func::None) pretend ((%, %) -> Boolean)) (k1, k2)) => 0
      for x1 in argument(k1) for x2 in argument(k2) repeat
        x1 ~= x2 => return B2Z(smaller?(x1, x2))
      0

    if S has ConvertibleTo InputForm then
      convert(k : %) : InputForm ==
        (v := symbolIfCan k) case Symbol => convert(v@Symbol)@InputForm
        (f := property(o := operator k, SPECIALINPUT)) case None =>
          ((f@None) pretend (List S -> InputForm)) (argument k)
        l := [convert x for x in argument k]$List(InputForm)
        (u := input operator k) case "failed" =>
          convert concat(convert name operator k, l)
        (u::(List InputForm -> InputForm)) l

    if S has ConvertibleTo Pattern Integer then
      convert(k : %) : Pattern(Integer) ==
        o := operator k
        (v := symbolIfCan k) case Symbol =>
          s  := patternVariable(v::Symbol,
                      has?(o, PMCONST), has?(o, PMOPT), has?(o, PMMULT))
          empty?(l := preds o) => s
          setPredicates(s, l)
        o [convert x for x in argument(k)]$List(Pattern Integer)

    if S has ConvertibleTo Pattern Float then
      convert(k : %) : Pattern(Float) ==
        o := operator k
        (v := symbolIfCan k) case Symbol =>
          s  := patternVariable(v@Symbol,
                      has?(o, PMCONST), has?(o, PMOPT), has?(o, PMMULT))
          empty?(l := preds o) => s
          setPredicates(s, l)
        o [convert x for x in argument(k)]$List(Pattern Float)

)abbrev package KERNEL2 KernelFunctions2
++ Description:
++ This package exports some auxiliary functions on kernels
KernelFunctions2(R : Comparable, S : Comparable) : with
  constantKernel : R -> Kernel S
        ++ constantKernel(r) \undocumented
  constantIfCan : Kernel S -> Union(R, "failed")
        ++ constantIfCan(k) \undocumented

 == add
  import from BasicOperatorFunctions1(R)

  constantKernel r == kernel(constantOperator r, [], 1)
  constantIfCan k  == constantOpIfCan operator k

--Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd.
--All rights reserved.
--
--Redistribution and use in source and binary forms, with or without
--modification, are permitted provided that the following conditions are
--met:
--
--    - Redistributions of source code must retain the above copyright
--      notice, this list of conditions and the following disclaimer.
--
--    - Redistributions in binary form must reproduce the above copyright
--      notice, this list of conditions and the following disclaimer in
--      the documentation and/or other materials provided with the
--      distribution.
--
--    - Neither the name of The Numerical ALgorithms Group Ltd. nor the
--      names of its contributors may be used to endorse or promote products
--      derived from this software without specific prior written permission.
--
--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
--IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
--TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
--OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
--EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
--PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
--PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
--LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
--NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-- SPAD files for the functional world should be compiled in the
-- following order:
--
--   op  KL  expr function
