\documentclass{article}
\usepackage{axiom}
\begin{document}
\title{\$SPAD/src/algebra interval.spad}
\author{Mike Dewar}
\maketitle
\begin{abstract}
\end{abstract}
\eject
\tableofcontents
\eject
\section{category INTCAT IntervalCategory}
<<category INTCAT IntervalCategory>>=
)abbrev category INTCAT IntervalCategory
+++ Author: Mike Dewar
+++ Date Created: November 1996
+++ Date Last Updated:
+++ Basic Functions:
+++ Related Constructors:
+++ Also See:
+++ AMS Classifications:
+++ Keywords:
+++ References:
+++ Description:
+++ This category implements of interval arithmetic and transcendental
+++ functions over intervals.
IntervalCategory(R: Join(FloatingPointSystem,TranscendentalFunctionCategory)):
 Category == Join(GcdDomain, OrderedSet, TranscendentalFunctionCategory, RadicalCategory, RetractableTo(Integer), Approximate) with
  interval : (R,R) -> %
    ++ interval(inf,sup) creates a new interval, either \axiom{[inf,sup]} if
    ++ \axiom{inf <= sup} or \axiom{[sup,in]} otherwise.
  qinterval : (R,R) -> %
    ++ qinterval(inf,sup) creates a new interval \axiom{[inf,sup]}, without
    ++ checking the ordering on the elements.
  interval : R -> %
    ++ interval(f) creates a new interval around f.
  interval : Fraction Integer -> %
    ++ interval(f) creates a new interval around f.
  inf : % -> R
    ++ inf(u) returns the infinum of \axiom{u}.
  sup : % -> R
    ++ sup(u) returns the supremum of \axiom{u}.
  width : % -> R
    ++ width(u) returns \axiom{sup(u) - inf(u)}.
  positive? : % -> Boolean
    ++ positive?(u) returns \axiom{true} if every element of u is positive,
    ++ \axiom{false} otherwise.
  negative? : % -> Boolean
    ++ negative?(u) returns \axiom{true} if every element of u is negative,
    ++ \axiom{false} otherwise.
  contains? : (%,R) -> Boolean
    ++ contains?(i,f) returns true if \axiom{f} is contained within the interval
    ++ \axiom{i}, false otherwise.

@
\section{domain INTRVL Interval}
<<domain INTRVL Interval>>=
)abbrev domain INTRVL Interval
+++ Author: Mike Dewar
+++ Date Created: November 1996
+++ Date Last Updated:
+++ Basic Functions:
+++ Related Constructors:
+++ Also See:
+++ AMS Classifications:
+++ Keywords:
+++ References:
+++ Description:
+++ This domain is an implementation of interval arithmetic and transcendental
+++ functions over intervals.
Interval(R:Join(FloatingPointSystem,TranscendentalFunctionCategory)): IntervalCategory(R) == add

  import Integer
--  import from R

  Rep := Record(Inf:R, Sup:R)

  roundDown(u:R):R ==
    if zero?(u) then float(-1,-(bits() pretend Integer))
                else float(mantissa(u) - 1,exponent(u))

  roundUp(u:R):R   ==
    if zero?(u) then float(1, -(bits()) pretend Integer)
                else float(mantissa(u) + 1,exponent(u))

  -- Sometimes the float representation does not use all the bits (e.g. when
  -- representing an integer in software using arbitrary-length Integers as
  -- your mantissa it is convenient to keep them exact).  This function
  -- normalises things so that rounding etc. works as expected.  It is only
  -- called when creating new intervals.
  normaliseFloat(u:R):R ==
    zero? u => u
    m : Integer := mantissa u
    b : Integer := bits() pretend Integer
    l : Integer := length(m)
    if l < b then
      BASE : Integer := base()$R pretend Integer
      float(m*BASE^((b-l) pretend PositiveInteger),exponent(u)-b+l)
    else
      u

  interval(i:R,s:R):% ==
    i > s =>  [roundDown normaliseFloat s,roundUp normaliseFloat i]
    [roundDown normaliseFloat i,roundUp normaliseFloat s]

  interval(f:R):% ==
    zero?(f) => 0
    one?(f)  => 1
    -- This next part is necessary to allow e.g. mapping between Expressions:
    -- AXIOM assumes that Integers stay as Integers!
--    import from Union(value1:Integer,failed:"failed")
    fnew : R := normaliseFloat f
    retractIfCan(f)@Union(Integer,"failed") case "failed" =>
      [roundDown fnew, roundUp fnew]
    [fnew,fnew]

  qinterval(i:R,s:R):% ==
    [roundDown normaliseFloat i,roundUp normaliseFloat s]

  exactInterval(i:R,s:R):% == [i,s]
  exactSupInterval(i:R,s:R):% == [roundDown i,s]
  exactInfInterval(i:R,s:R):% == [i,roundUp s]

  inf(u:%):R == u.Inf
  sup(u:%):R == u.Sup
  width(u:%):R == u.Sup - u.Inf

  contains?(u:%,f:R):Boolean == (f > inf(u)) and (f < sup(u))

  positive?(u:%):Boolean == inf(u) > 0
  negative?(u:%):Boolean == sup(u) < 0

  _< (a:%,b:%):Boolean ==
    if inf(a) < inf(b) then
      true
    else if inf(a) > inf(b) then
      false
    else
      sup(a) < sup(b)

  _+ (a:%,b:%):% ==
    -- A couple of blatent hacks to preserve the Ring Axioms!
    if zero?(a) then return(b) else if zero?(b) then return(a)
    if a = b then return qinterval(2*inf(a),2*sup(a))
    qinterval(inf(a) + inf(b), sup(a) + sup(b))


  _- (a:%,b:%):% ==
    if zero?(a) then return(-b) else if zero?(b) then return(a)
    if a = b then 0 else qinterval(inf(a) - sup(b), sup(a) - inf(b))


  _* (a:%,b:%):% ==
    -- A couple of blatent hacks to preserve the Ring Axioms!
    if one?(a) then return(b) else if one?(b) then return(a)
    if zero?(a) then return(0) else if zero?(b) then return(0)
    prods : List R :=  sort [inf(a)*inf(b),sup(a)*sup(b),
                             inf(a)*sup(b),sup(a)*inf(b)]
    qinterval(first prods, last prods)


  _* (a:Integer,b:%):% ==
    if (a > 0) then
      qinterval(a*inf(b),a*sup(b))
    else if (a < 0) then
      qinterval(a*sup(b),a*inf(b))
    else
      0

  _* (a:PositiveInteger,b:%):% == qinterval(a*inf(b),a*sup(b))

  _^ (a:%,n:PositiveInteger):% ==
    contains?(a,0) and zero?((n pretend Integer) rem 2) =>
      interval(0,max(inf(a)^n,sup(a)^n))
    interval(inf(a)^n,sup(a)^n)

  _- (a:%):% == exactInterval(-sup(a),-inf(a))

  _= (a:%,b:%):Boolean == (inf(a)=inf(b)) and (sup(a)=sup(b))
  _~_= (a:%,b:%):Boolean == (inf(a)~=inf(b)) or (sup(a)~=sup(b))

  1 ==
        one : R := normaliseFloat 1
        [one,one]

  0 == [0,0]

  recip(u:%):Union(%,"failed") ==
   contains?(u,0) => "failed"
   vals:List R := sort [1/inf(u),1/sup(u)]$List(R)
   qinterval(first vals, last vals)


  unit?(u:%):Boolean == contains?(u,0)

  _exquo(u:%,v:%):Union(%,"failed") ==
   contains?(v,0) => "failed"
   one?(v) => u
   u=v => 1
   u=-v => -1
   vals:List R := sort [inf(u)/inf(v),inf(u)/sup(v),sup(u)/inf(v),sup(u)/sup(v)]$List(R)
   qinterval(first vals, last vals)


  gcd(u:%,v:%):% == 1

  coerce(u:Integer):% ==
    ur := normaliseFloat(u::R)
    exactInterval(ur,ur)


  interval(u:Fraction Integer):% ==
--    import   log2 : % -> %
--             coerce : Integer -> %
--             retractIfCan : % -> Union(value1:Integer,failed:"failed")
--    from Float
    flt := u::R

    -- Test if the representation in R is exact
    --den := denom(u)::Float
    bin : Union(Integer,"failed") := retractIfCan(log2(denom(u)::Float))
    bin case Integer and length(numer u)$Integer < (bits() pretend Integer) =>
      flt := normaliseFloat flt
      exactInterval(flt,flt)

    qinterval(flt,flt)


  retractIfCan(u:%):Union(Integer,"failed") ==
    not zero? width(u) => "failed"
    retractIfCan inf u


  retract(u:%):Integer ==
    not zero? width(u) =>
      error "attempt to retract a non-Integer interval to an Integer"
    retract inf u


  coerce(u:%):OutputForm ==
    bracket([coerce inf(u), coerce sup(u)]$List(OutputForm))

  characteristic():NonNegativeInteger == 0


  -- Explicit export from TranscendentalFunctionCategory
  pi():% == qinterval(pi(),pi())

  -- From ElementaryFunctionCategory
  log(u:%):% ==
    positive?(u) => qinterval(log inf u, log sup u)
    error "negative logs in interval"


  exp(u:%):% == qinterval(exp inf u, exp sup u)

  _^ (u:%,v:%):% ==
    zero?(v) => if zero?(u) then error "0^0 is undefined" else 1
    one?(u)  => 1
    expts : List R :=  sort [inf(u)^inf(v),sup(u)^sup(v),
                             inf(u)^sup(v),sup(u)^inf(v)]
    qinterval(first expts, last expts)

  -- From TrigonometricFunctionCategory

  -- This function checks whether an interval contains a value of the form
  -- `offset + 2 n pi'.
  hasTwoPiMultiple(offset:R,ipi:R,i:%):Boolean ==
    next : Integer := retract ceiling( (inf(i) - offset)/(2*ipi) )
    contains?(i,offset+2*next*ipi)


  -- This function checks whether an interval contains a value of the form
  -- `offset + n pi'.
  hasPiMultiple(offset:R,ipi:R,i:%):Boolean ==
    next : Integer := retract ceiling( (inf(i) - offset)/ipi )
    contains?(i,offset+next*ipi)


  sin(u:%):% ==
    ipi : R := pi()$R
    hasOne? : Boolean := hasTwoPiMultiple(ipi/(2::R),ipi,u)
    hasMinusOne? : Boolean := hasTwoPiMultiple(3*ipi/(2::R),ipi,u)

    if hasOne? and hasMinusOne? then
      exactInterval(-1,1)
    else
      vals : List R := sort [sin inf u, sin sup u]
      if hasOne? then
        exactSupInterval(first vals, 1)
      else if hasMinusOne? then
        exactInfInterval(-1,last vals)
      else
        qinterval(first vals, last vals)



  cos(u:%):% ==
    ipi : R := pi()
    hasOne? : Boolean := hasTwoPiMultiple(0,ipi,u)
    hasMinusOne? : Boolean := hasTwoPiMultiple(ipi,ipi,u)

    if hasOne? and hasMinusOne? then
      exactInterval(-1,1)
    else
      vals : List R := sort [cos inf u, cos sup u]
      if hasOne? then
        exactSupInterval(first vals, 1)
      else if hasMinusOne? then
        exactInfInterval(-1,last vals)
      else
        qinterval(first vals, last vals)



  tan(u:%):% ==
    ipi : R := pi()
    if width(u) > ipi then
      error "Interval contains a singularity"
    else
      -- Since we know the interval is less than pi wide, monotonicity implies
      -- that there is no singularity.  If there is a singularity on a endpoint
      -- of the interval the user will see the error generated by R.
      lo : R := tan inf u
      hi : R := tan sup u

      lo > hi => error "Interval contains a singularity"
      qinterval(lo,hi)



  csc(u:%):% ==
    ipi : R := pi()
    if width(u) > ipi then
      error "Interval contains a singularity"
    else
--      import from Integer
      -- singularities are at multiples of Pi
      if hasPiMultiple(0,ipi,u) then error "Interval contains a singularity"
      vals : List R := sort [csc inf u, csc sup u]
      if hasTwoPiMultiple(ipi/(2::R),ipi,u) then
        exactInfInterval(1,last vals)
      else if hasTwoPiMultiple(3*ipi/(2::R),ipi,u) then
        exactSupInterval(first vals,-1)
      else
        qinterval(first vals, last vals)



  sec(u:%):% ==
    ipi : R := pi()
    if width(u) > ipi then
      error "Interval contains a singularity"
    else
--      import from Integer
      -- singularities are at Pi/2 + n Pi
      if hasPiMultiple(ipi/(2::R),ipi,u) then
        error "Interval contains a singularity"
      vals : List R := sort [sec inf u, sec sup u]
      if hasTwoPiMultiple(0,ipi,u) then
        exactInfInterval(1,last vals)
      else if hasTwoPiMultiple(ipi,ipi,u) then
        exactSupInterval(first vals,-1)
      else
        qinterval(first vals, last vals)




  cot(u:%):% ==
    ipi : R := pi()
    if width(u) > ipi then
      error "Interval contains a singularity"
    else
      -- Since we know the interval is less than pi wide, monotonicity implies
      -- that there is no singularity.  If there is a singularity on a endpoint
      -- of the interval the user will see the error generated by R.
      hi : R := cot inf u
      lo : R := cot sup u

      lo > hi => error "Interval contains a singularity"
      qinterval(lo,hi)



  -- From ArcTrigonometricFunctionCategory

  asin(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if (lo < -1) or (hi > 1) then error "asin only defined on the region -1..1"
    qinterval(asin lo,asin hi)


  acos(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if (lo < -1) or (hi > 1) then error "acos only defined on the region -1..1"
    qinterval(acos hi,acos lo)


  atan(u:%):% == qinterval(atan inf u, atan sup u)

  acot(u:%):% == qinterval(acot sup u, acot inf u)

  acsc(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if ((lo <= -1) and (hi >= -1)) or ((lo <= 1) and (hi >= 1)) then
      error "acsc not defined on the region -1..1"
    qinterval(acsc hi, acsc lo)


  asec(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if ((lo < -1) and (hi > -1)) or ((lo < 1) and (hi > 1)) then
      error "asec not defined on the region -1..1"
    qinterval(asec lo, asec hi)


  -- From HyperbolicFunctionCategory

  tanh(u:%):% == qinterval(tanh inf u, tanh sup u)

  sinh(u:%):% == qinterval(sinh inf u, sinh sup u)

  sech(u:%):% ==
    negative? u => qinterval(sech inf u, sech sup u)
    positive? u => qinterval(sech sup u, sech inf u)
    vals : List R := sort [sech inf u, sech sup u]
    exactSupInterval(first vals,1)


  cosh(u:%):% ==
    negative? u => qinterval(cosh sup u, cosh inf u)
    positive? u => qinterval(cosh inf u, cosh sup u)
    vals : List R := sort [cosh inf u, cosh sup u]
    exactInfInterval(1,last vals)


  csch(u:%):% ==
    contains?(u,0) => error "csch: singularity at zero"
    qinterval(csch sup u, csch inf u)


  coth(u:%):% ==
    contains?(u,0) => error "coth: singularity at zero"
    qinterval(coth sup u, coth inf u)


  -- From ArcHyperbolicFunctionCategory

  acosh(u:%):% ==
    inf(u)<1 => error "invalid argument: acosh only defined on the region 1.."
    qinterval(acosh inf u, acosh sup u)


  acoth(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if ((lo <= -1) and (hi >= -1)) or ((lo <= 1) and (hi >= 1)) then
      error "acoth not defined on the region -1..1"
    qinterval(acoth hi, acoth lo)


  acsch(u:%):% ==
    contains?(u,0) => error "acsch: singularity at zero"
    qinterval(acsch sup u, acsch inf u)


  asech(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if  (lo <= 0) or (hi > 1) then
      error "asech only defined on the region 0 < x <= 1"
    qinterval(asech hi, asech lo)


  asinh(u:%):% == qinterval(asinh inf u, asinh sup u)

  atanh(u:%):% ==
    lo : R := inf(u)
    hi : R := sup(u)
    if  (lo <= -1) or (hi >= 1) then
      error "atanh only defined on the region -1 < x < 1"
    qinterval(atanh lo, atanh hi)


  -- From RadicalCategory
  _^(u:%, n:Fraction Integer):% ==
       lo : R := inf(u)
       lo <= 0 => error "fractional power only defined for x > 0"
       interval(inf(u)^n,sup(u)^n)

@
\section{License}
<<license>>=
--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.
@
<<*>>=
<<license>>

<<category INTCAT IntervalCategory>>
<<domain INTRVL Interval>>

@
\eject
\begin{thebibliography}{99}
\bibitem{1} nothing
\end{thebibliography}
\end{document}


