File : intervals-ops.adb


with Reps.Ops, Round_Up;

pragma Elaborate_All (Reps.Ops);

package body Intervals.Ops is

  NegOne:  constant Rep := -One;
  NegTwo:  constant Rep := -Two;
  NegHalf: constant Rep := -Half;
  Three:   constant Rep := Rep(3);
  Quarter: constant Rep := Half/Two;
  Epsilon: constant Rep := Half**99;

  Interval_Zero: constant Interval := (Zero,Zero);
  Interval_One:  constant Interval := (One,Zero);
  PlusMinus:     constant Interval := (Zero,One);

  function Scal(R1,R2: Rep) return Interval is
    Sum: constant Rep := R1+R2;
  begin
    if R1 >= R2 then
      return (Half*Sum,Half*((R1-R2)+(Sum+(-R1-R2))));
    end if;
    return (Half*Sum,Half*((R2-R1)+(Sum+(-R1-R2))));
  end Scal;

  function Ball(S: Interval) return Interval is
  begin
    return (Zero,Abs(S.C)+S.R);
  end Ball;

  function Inf(S: Interval) return Rep is
  begin
    return -(S.R-S.C);
  end Inf;

  function Sup(S: Interval) return Rep is
  begin
    return S.C+S.R;
  end Sup;

  pragma Inline (Inf,Sup);

  function SupAbs(S: Interval) return Rep is
  begin
    return Abs(S.C)+S.R;
  end SupAbs;

  function "<"(S1,S2: Interval) return Boolean is
  begin
    if Sup(S1)<Inf(S2) then return True; end if;
    if Sup(S2)<=Inf(S1) then return False; end if;
    raise Undefined;
    return False;
  end "<";

  function IntFloor(S: Interval) return Integer is
    R: constant Rep := Rep'Floor(Inf(S));
  begin
    if Rep'Floor(Sup(S)) /= R then
      raise Undefined;
    end if;
    return Integer(R);
  end IntFloor;

  function IntCeiling(S: Interval) return Integer is
    R: constant Rep := Rep'Ceiling(Sup(S));
  begin
    if Rep'Ceiling(Inf(S)) /= R then
      raise Undefined;
    end if;
    return Integer(R);
  end IntCeiling;

  function "abs"(S: Interval) return Interval is
    R: Rep;
  begin
    if S.C >= S.R then
      return S;
    elsif -S.C >= S.R then
      return (-S.C,S.R);
    end if;
    R := Half*(Abs(S.C)+S.R);
    return (R,R);
  end "abs";

  function Min(S1,S2: Interval) return Interval is
    U1: constant Rep := Sup(S1);
    L2: constant Rep := Inf(S2);
    L1,U2: Rep;
  begin
    if U1 <= L2 then
      return S1;
    end if;
    L1 := Inf(S1);
    U2 := Sup(S2);
    if U2 <= L1 then
      return S2;
    end if;
    return Scal(Rep'Min(L1,L2),Rep'Min(U1,U2));
  end Min;

  function Max(S1,S2: Interval) return Interval is
    U1: constant Rep := Sup(S1);
    L2: constant Rep := Inf(S2);
    L1,U2: Rep;
  begin
    if U1 <= L2 then
      return S2;
    end if;
    L1 := Inf(S1);
    U2 := Sup(S2);
    if U2 <= L1 then
      return S1;
    end if;
    return Scal(Rep'Max(L1,L2),Rep'Max(U1,U2));
   end Max;

  function Cap(R: Radius; S: Interval) return Interval is
-- bound on characteristic_function for closed ball of radius R
  begin
    if Abs(S.C)+S.R <= R then
      return S;
    end if;
    return Scal(Rep'Max(-R,Rep'Min(Zero,Inf(S))),Rep'Min(R,Rep'Max(Zero,Sup(S))));
  end Cap;

  function Up(R: Rep; Dummy: Interval) return Rep is
-- only used in high level procedures to increase domains
  begin
    return R+Epsilon;         -- avoid Gnat's Succ in Round_Up mode
  end Up;

  function Widen(S: Interval) return Interval is
-- only used in high level procedures to increase norms
  begin
    return (S.C,S.R+Epsilon); -- avoid Gnat's Succ in Round_Up mode
  end Widen;

  procedure ErrMult(R: in Rep; S: in out Interval) is
  begin
    S.R := Abs(R)*S.R;
  end ErrMult;

  function "+"(S,T: Interval) return Interval is
    Sum: constant Rep := S.C+T.C;
  begin
    return (Sum,S.R+T.R+(Sum+(-S.C-T.C)));
  end "+";

  function "-"(S,T: Interval) return Interval is
    Diff: constant Rep := S.C-T.C;
  begin
    return (Diff,S.R+T.R+(Diff+(T.C-S.C)));
  end "-";

  function "*"(R: Rep; S: Interval) return Interval is
    Prod: constant Rep := R*S.C;
  begin
    return (Prod,Abs(R)*S.R+(Prod+(-R)*S.C));
  end "*";

  function "/"(S: Interval; R: Rep) return Interval is
    Quot: constant Rep := S.C/R;
  begin
    return (Quot,S.R/Abs(R)+(Quot+S.C/(-R)));
  end "/";

  function Sqr(S: Interval) return Interval is
    Prod: constant Rep := S.C*S.C;
  begin
    return (Prod,(Two*Abs(S.C)+S.R)*S.R+(Prod+S.C*(-S.C)));
  end Sqr;

  function "*"(S,T: Interval) return Interval is
    Prod: constant Rep := S.C*T.C;
  begin
    return (Prod,Abs(S.C)*T.R+S.R*(Abs(T.C)+T.R)+(Prod+S.C*(-T.C)));
  end "*";

  function Inv(S: Interval) return Interval is
    Quot: constant Rep := One/S.C;
  begin
    if Abs(S.C) <= S.R then
      raise Constraint_Error;               -- division by zero
    end if;
    return (Quot,S.R/Abs(S.C)/(-(S.R-Abs(S.C)))+(Quot+NegOne/S.C));
  end Inv;

  function "/"(S,T: Interval) return Interval is
  begin
    return S*Inv(T);
  end "/";

  function "**"(S: Interval; I: Integer) return Interval is
  begin
    if I>0 then
      declare
        J: Integer := I;
        X,Y: Interval;
      begin
        X := S;
        if (J rem 2)=0 then
          Y := Interval_One;
        else
          Y := X;
        end if;
        J := J/2;
        while J>0 loop
          X := Sqr(X);
          if (J rem 2)>0 then
            Y := X*Y;
          end if;
          J := J/2;
        end loop;
        return Y;
      end;
    elsif I<0 then
      return Inv(S)**(-I);
    else
      return Interval_One;
    end if;
  end "**";

  procedure CoshSinh(S: in Interval; Sc,Ss: out Interval) is
    Iter: constant Integer := 6;
  begin
    if S = Interval_Zero then
      Sc := Interval_One;
      Ss := S;
    else
      declare
        K: Integer;
        R: Rep;
        X,T: Interval;
      begin
        K := 0;
        R := SupAbs(S);
        while R>Half loop
          K := K+1;
          R := R*Half;
        end loop;
        X := (Half**K)*S;
        T := Sqr(X);
        Sc := (One,Half);
        Ss := Sc;
        for I in reverse 1 .. Iter loop
          Sc := Interval_One+Sc*T/Rep((2*I)*(2*I-1));
          Ss := Interval_One+Ss*T/Rep((2*I)*(2*I+1));
        end loop;
        Ss := X*Ss;
        for I in 1 .. K loop
          T := Sc;
          Sc := Interval_One+Two*Sqr(Ss);
          Ss := Two*(T*Ss);
        end loop;
      end;
    end if;
  end CoshSinh;

  function Cosh(S: Interval) return Interval is
    Sc,Ss: Interval;
  begin
    CoshSinh(S,Sc,Ss);
    return Sc;
  end Cosh;

  function Sinh(S: Interval) return Interval is
    Sc,Ss: Interval;
  begin
    CoshSinh(S,Sc,Ss);
    return Ss;
  end Sinh;

  function Exp(S: Interval) return Interval is
  begin
    if S=Interval_Zero then return Scal(One); end if;   -- S=0
    if Sup(S)<Zero then return Inv(Exp(-S)); end if;    -- S<0
    declare
      X,Y: Interval;
    begin
      if Inf(S)<Zero then                               -- S contains 0
        CoshSinh(S,X,Y);
        return X+Y;
      end if;
      declare                                           -- S>0
        Iter: constant Integer := 12;
        K: Integer := 0;
        R: Rep;
      begin
        R := Sup(S);
        while R>Quarter loop
          K := K+1;
          R := R*Half;
        end loop;
        X := (Half**K)*S;
        Y := (Zero,Three/Two);
        for I in reverse 1 .. Iter loop
          Y := Interval_One+(X*Y)/Rep(I);
        end loop;
        for I in 1 .. K loop
          Y := Sqr(Y);
        end loop;
        return Y;
      end;
    end;
  end Exp;

  function Log(S: Interval) return Interval is
    use Reps.Ops;
  begin
    if S=Interval_One then return Interval_Zero; end if;
    declare
      Iter: constant Integer := 3;
      S0,LogS0,X,Y: Interval;
    begin
      LogS0 := Scal(Log(Approx(S)));   --- guess
      S0 := Exp(LogS0);
      X := (S0-S)/S0;                  --- Log(S) = Log(S0)+Log(1-X)
      Y := Interval_One-Scal(SupAbs(X));
      if Inf(Y) <= Zero then raise Constraint_Error; end if;
      Y := PlusMinus*Inv(Y)/Rep(Iter+1);
      for I in reverse 1 .. Iter loop
        Y := Inv(Scal(I))+X*Y;
      end loop;
      return LogS0-X*Y;
    end;
  end Log;

  function Lambda(M,N: Integer) return Interval is
    S: Interval;
  begin
    S := (Three/Two,Half);
    for I in 1 .. 100 loop
      S := Interval_One+Inv(S);
    end loop;
    return Scal(M)-Rep(N)*Inv(S);
  end Lambda;

begin

  Round_Up;

end Intervals.Ops;