packages feed

egison-5.1.0: lib/math/common/interval.egi

--
--
-- Rational interval arithmetic for constant expressions
--
--
-- Enclosures with exact rational endpoints, used as SIGN CERTIFICATES
-- for symbol-free values built from rationals and nested square roots
-- (design/cas-simplification.md, Section 3.7).  A numeric point
-- estimate can only suggest a sign; an enclosure that excludes zero
-- proves it.  The consumer is the sqrt application path (root.egi):
-- a constant radicand certified negative is normalized to
-- i * sqrt(-x), which makes every surviving sqrt atom have a
-- positive radicand -- and then the pair merge sqrt a * sqrt b =
-- sqrt (a b) is sound on principal branches.
--

-- Integer square root (floor), by Newton's method.
def iSqrtFloor (n: Integer) : Integer :=
  if n < 2
    then n
    else
      let go (x: Integer) : Integer :=
            let x' := i.quotient (x + i.quotient n x) 2
             in if x' >= x then x else go x'
       in go n

-- Floor of a rational value (MathValue holding p/q).
def ratFloor (v: MathValue) : Integer :=
  let p := numerator v
      q := denominator v
   in if p >= 0
        then i.quotient p q
        else - (i.quotient (- p) q) - (if i.modulo (- p) q = 0 then 0 else 1)

-- Enclosure of the square root of a positive rational interval, at
-- scale 2^k: sqrt(v) is enclosed by [s/2^k, (s'+1)/2^k] with
-- s = isqrt(floor(v_lo * 4^k)) and s' = isqrt(floor(v_hi * 4^k)).
def sqrtInterval (k: Integer) (lo: MathValue) (hi: MathValue) : (MathValue, MathValue) :=
  let scale := 2^k
      s  := iSqrtFloor (ratFloor (lo * scale * scale))
      s' := iSqrtFloor (ratFloor (hi * scale * scale))
   in (s / scale, (s' + 1) / scale)

-- Rational comparisons via the (always positive) denominator:
-- runtime Ord dispatch does not cover fractions, so compare through
-- integer numerators.
def ratLt (a: MathValue) (b: MathValue) : Bool := numerator (a - b) < 0

def ratPos (v: MathValue) : Bool := numerator v > 0

def ratNeg (v: MathValue) : Bool := numerator v < 0

-- Interval product ([min, max] of the endpoint products).
def mulInterval (a: (MathValue, MathValue)) (b: (MathValue, MathValue)) : (MathValue, MathValue) :=
  let (al, ah) := a
      (bl, bh) := b
      ps := [al * bl, al * bh, ah * bl, ah * bh]
   in (minimumMV ps, maximumMV ps)

def minimumMV (xs: [MathValue]) : MathValue :=
  match xs as list mathValue with
    | $x :: $rest -> foldl (\a b -> if ratLt b a then b else a) x rest

def maximumMV (xs: [MathValue]) : MathValue :=
  match xs as list mathValue with
    | $x :: $rest -> foldl (\a b -> if ratLt a b then b else a) x rest

def powInterval (n: Integer) (a: (MathValue, MathValue)) : (MathValue, MathValue) :=
  if n = 1 then a else mulInterval a (powInterval (n - 1) a)

-- Enclosure of a symbol-free value at precision k, or Nothing when
-- the value is outside the supported fragment (free symbols, i,
-- quotes, non-sqrt applications, negative exponents) or a nested
-- radicand cannot be certified positive at this precision.
def constInterval (k: Integer) (v: MathValue) : Maybe (MathValue, MathValue) :=
  match v as mathValue with
    | poly $ts ->
        foldl
          (\acc t -> match (acc, constTermInterval k t) as (maybe something, maybe something) with
            | (just $a, just $b) ->
                let (al, ah) := a
                    (bl, bh) := b
                 in Just (al + bl, ah + bh)
            | (_, _) -> Nothing)
          (Just (0, 0))
          ts
    | _ -> Nothing

def constTermInterval (k: Integer) (t: MathValue) : Maybe (MathValue, MathValue) :=
  match t as mathValue with
    | term $c $xs ->
        if isRational c
          then
            foldl
              (\acc (x, n) ->
                match acc as maybe something with
                  | nothing -> Nothing
                  | just $a ->
                      if n >= 1
                        then match constAtomInterval k x as maybe something with
                          | just $b -> Just (mulInterval a (powInterval n b))
                          | nothing -> Nothing
                        else Nothing)
              (Just (c, c))
              xs
          else Nothing

def constAtomInterval (k: Integer) (x: MathValue) : Maybe (MathValue, MathValue) :=
  match x as mathValue with
    | apply1 #sqrt $a ->
        match constInterval k a as maybe something with
          | just $iv ->
              let (lo, hi) := iv
               in if ratPos lo
                    then Just (sqrtInterval k lo hi)
                    else Nothing
          | nothing -> Nothing
    | _ -> Nothing

-- Certified sign of a symbol-free real value: "pos", "neg", or
-- "unknown" (outside the fragment, or zero cannot be separated even
-- at the highest precision).  Precision escalates until the
-- enclosure excludes zero.
def signOfConst (v: MathValue) : String := signOfConstLoop v [16, 64, 256]

def signOfConstAt (k: Integer) (v: MathValue) : String :=
  match constInterval k v as maybe something with
    | just $iv ->
        let (lo, hi) := iv
         in if ratPos lo then "pos" else if ratNeg hi then "neg" else "escalate"
    | nothing -> "unknown"

def signOfConstLoop (v: MathValue) (ks: [Integer]) : String :=
  match ks as list integer with
    | [] -> "unknown"
    | $k :: $rest ->
        match signOfConstAt k v as string with
          | #"pos" -> "pos"
          | #"neg" -> "neg"
          | #"unknown" -> "unknown"
          | _ -> signOfConstLoop v rest