/*

 File: cinterval.cpp, 2005/08/06

 CoStLy (COmplex interval STandard functions LibrarY), Version 2.0.4

 Copyright (C) Markus Neher, markus.neher@math.uni-karlsruhe.de
               Ingo Eble,    ingoeble@web.de

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

/*-----------------------------------------------------------------------------

  Functions in this file:

  1. f 
     Auxiliary function for complex interval division.

  2. MinMax
     Auxiliary function for complex interval division.

  3. Operator /=
     Division of complex rectangular intervals
     (for use with filib++; C-XSC supplies satisfactory complex division). 

  4. Operator <<
     Output stream for complex interval.

  5. Operator >>
     Input stream for complex interval.

-----------------------------------------------------------------------------*/

#ifndef FILIB_VERSION
#include "cinterval.hpp" //Required for complex division subroutine.
#endif

#include "cinterval.h"
#include "error.h"

#include <sstream>
#include <limits>
using std::numeric_limits;

using Adapt::Interval;
using Adapt::inf;
using Adapt::sup;

int sign(const double& a)
{
  return ( a > 0.0 ? 1 : ( a < 0.0 ? -1 : 0 ) );
}

//-- f ------------------------------------------------------------------------
//
double f(const double& a, const double& b, const double& c, const double& d, int round)
  //
  // Auxiliary function: calculates (a*c+b*d)/(c*c+d*d) with optimal accuracy
  //
{
  double value;

  switch( round )
    {
    case -1: //downwards rounded
      {
	double z, n, tmp;
#ifdef FILIB_VERSION
	MUL_DOWN( z, a, c ); // z = a*c, rounding downwards
	MUL_DOWN( tmp, b, d ); // tmp = b*d, rounding downwards
	ADD_DOWN_UPD( z, tmp ); // z = z + tmp, rounding downwards

	MUL_UP( n, c, c ); // n = c*c, rounding upwards
	MUL_UP( tmp, d, d ); // tmp = d*d, rounding upwards
	ADD_UP_UPD( n, tmp ); // n = n + tmp, rounding upwards

	DIV_DOWN( value, z, n );
#else //CXSC
	z   = cxsc::_double( cxsc::multdown( a, c   ) ); // z = a*c, rounding downwards
	tmp = cxsc::_double( cxsc::multdown( b, d   ) ); // tmp = b*d, rounding downwards
	z   = cxsc::_double( cxsc::adddown ( z, tmp ) ); // z = z + tmp, rounding downwards

	n   = cxsc::_double( cxsc::multup( c, c   ) ); // n = c*c, rounding upwards
	tmp = cxsc::_double( cxsc::multup( d, d   ) ); // tmp = d*d, rounding upwards
	n   = cxsc::_double( cxsc::addup ( n, tmp ) ); // n = n + tmp, rounding upwards

	value = cxsc::_double( cxsc::divdown( z, n ) );
#endif

	return value;
      }
    case  0: //rounding to nearest
      {
      }
    case  1: //rounding upwards
      {
	double z, n, tmp;
#ifdef FILIB_VERSION
	MUL_UP( z, a, c ); // z = a*c, rounding upwards
	MUL_UP( tmp, b, d ); // tmp = b*d, rounding upwards
	ADD_UP_UPD( z, tmp ); // z = z + tmp, rounding upwards

	MUL_DOWN( n, c, c ); // n = c*c, rounding downwards
	MUL_DOWN( tmp, d, d ); // tmp = d*d, rounding downwards
	ADD_DOWN_UPD( n, tmp ); // n = n + tmp, rounding downwards

	DIV_UP( value, z, n );
#else //CXSC
	z   = cxsc::_double( cxsc::multup( a, c   ) ); // z = a*c, rounding upwards
	tmp = cxsc::_double( cxsc::multup( b, d   ) ); // tmp = b*d, rounding upwards
	z   = cxsc::_double( cxsc::addup ( z, tmp ) ); // z = z + tmp, rounding upwards

	n   = cxsc::_double( cxsc::multdown( c, c   ) ); // n = c*c, rounding downwards
	tmp = cxsc::_double( cxsc::multdown( d, d   ) ); // tmp = d*d, rounding downwards
	n   = cxsc::_double( cxsc::adddown ( n, tmp ) ); // n = n + tmp, rounding downwards

	value = cxsc::_double( cxsc::divup( z, n ) );
#endif
	
	return value;
      }
    }
}
//
//-- end f --------------------------------------------------------------------

bool p[5];  // global variable; 
            // initialized in operator /=, used in /= and function MinMax

//-- MinMax -------------------------------------------------------------------
//
double MinMax(bool minimum, const double& a, const double& b, const double& y0, const Interval& X, int i, int j)
{
  /* 

     Computes local extrema of f = (ac+bd)/(cc+dd) 
     at interior points of X = [c.inf,c.sup] ( a,b,d=y0 fixed ).         
     If minimum = TRUE, the minimum is computed, otherwise the maximum.

  */

  double minmax;

  if( minimum ) 
    minmax =  numeric_limits<double>::max();
  else
    minmax = -numeric_limits<double>::max();

  if( inf(X)==sup(X) )
    {
      if( p[i] && p[j] ) minmax = f( a, b, inf(X), y0, 1-2*minimum );
      p[i] = false;
      p[j] = false;
    }
  else
    {
      if( a==0.0 )
	{
	  if( b==0.0 || y0==0.0 )
	    {
	      minmax = 0.0;
	      p[i]  = false;
	      p[j]  = false;
	    }
	  else
	    {
	      if( in( 0.0, X ) )
		{
		  if( minimum && ( sign(b)!=sign(y0) ) ) 
		    {
#ifdef FILIB_VERSION
		      DIV_DOWN( minmax, b, y0 );
#else //CXSC
		      minmax = cxsc::_double( cxsc::divdown( b, y0 ) );
#endif
		      p[i]  = false;
		      p[j]  = false;
		    }
		  else
		    {
		      if( !minimum && ( sign(b)==sign(y0) ) )
			{
#ifdef FILIB_VERSION
			  DIV_UP( minmax, b, y0 );
#else //CXSC
		          minmax = cxsc::_double( cxsc::divup( b, y0 ) );
#endif
			  p[i]  = false;
			  p[j]  = false;
			}
		    }
		}
	    }
	}
      else  // a != 0.0 
	{
	  if( y0==0.0 )
	    {
	      if( minimum )
		{
		  if( a > 0.0 ) { 
#ifdef FILIB_VERSION
		    DIV_DOWN( minmax, a, sup(X) );
#else //CXSC
		    minmax = cxsc::_double( cxsc::divdown( a, sup(X) ) );
#endif
		  }
		  else { 
#ifdef FILIB_VERSION
		    DIV_DOWN( minmax, a, inf(X) );
#else //CXSC
		    minmax = cxsc::_double( cxsc::divdown( a, inf(X) ) );
#endif
		  }

		}
	      else 
		{
		  if( a > 0.0 ) { 
#ifdef FILIB_VERSION
		    DIV_UP( minmax, a, inf(X) );
#else //CXSC
		    minmax = cxsc::_double( cxsc::divup( a, inf(X) ) );
#endif
		  }
		  else {
#ifdef FILIB_VERSION
		    DIV_UP( minmax, a, sup(X) );
#else //CXSC
		    minmax = cxsc::_double( cxsc::divup( a, sup(X) ) );
#endif
		  }
		}

	      p[i] = false;
	      p[j] = false;
	    }
	  else  // y0 != 0.0,  compute extremal point, min, and max
	    {
	      // compute t = sign(a) * ( |b/a| + sqrt( 1+|b/a|^2 ) ) 
	      
	      Interval T,Q( abs( Interval(b)/Interval(a) ) ); 
	      
	      if( a < 0.0 ) 
		T = -( Q + sqrt( 1+Q*Q ) );
	      else
		T =  ( Q + sqrt( 1+Q*Q ) );

	      double x, ay0 = std::abs(y0);

	      // case differentiation for min or max
	      if( ( sign(b)==sign(y0) )== minimum ) 
		{
		  // lower bound for x = |y0| * t, t in T.
#ifdef FILIB_VERSION
		  MUL_DOWN( x, ay0, inf(T) );
#else //CXSC
		  x = cxsc::_double( cxsc::multdown( ay0, inf(T) ) );
#endif
		}
	      else
		{
		  // lower bound for x = |y0| / t, t in T.
#ifdef FILIB_VERSION
		  DIV_DOWN( x, ay0, sup(T) );
#else //CXSC
		  x = cxsc::_double( cxsc::divdown( ay0, sup(T) ) );
#endif
		}
	      if( minimum ) x = -x; // If minimum=TRUE then computed lower bound becomes upper bound
	      
	      if( in( x, X ) )
		{
		  // If minimum=TRUE then compute lower bound for a / ( 2*x ),
		  // otherwise compute upper bound
		  if( minimum )
		    {
		      double tmp;
#ifdef FILIB_VERSION
		      MUL_UP( tmp, 2.0, x );
		      DIV_DOWN( minmax, a, tmp );
#else //CXSC
		      tmp    = cxsc::_double( cxsc::multup ( 2.0, x   ) );
		      minmax = cxsc::_double( cxsc::divdown( a  , tmp ) );
#endif
		    }
		  else
		    {
		      double tmp;
#ifdef FILIB_VERSION
		      MUL_DOWN( tmp, 2.0, x );
		      DIV_UP( minmax, a, tmp );
#else //CXSC
		      tmp    = cxsc::_double( cxsc::multdown( 2.0, x   ) );
		      minmax = cxsc::_double( cxsc::divup   ( a  , tmp ) );
#endif
		    }
		  p[i] = false;
		  p[j] = false;
		}
	    }
	}
    }
  return minmax;
}
//
//-- end MinMax ---------------------------------------------------------------

/*--------------------------------------------------------------+
 | complex interval division:                                   |
 |                                                              |
 |    Z1 /= Z2    <=>   Z1 = Z1 / Z2                            |
 |                                                              |
 | see: Lohner, R., Gudenberg, W.v., Complex Interval Division  |
 | with Maximum Accuracy, Proc. Comp. Arith., Ed. Hwang, 1985.  |
 |                                                              |
 +--------------------------------------------------------------*/

//-- Operator /= --------------------------------------------------------------
//
CInterval& CInterval::operator /= (const CInterval& z)
{
#ifdef FILIB_VERSION
  if( in( 0.0, z.re() ) && in( 0.0, z.im() ) )
    throw division_by_zero();

  Interval
    rhsRe( z.re() ),
    rhsIm( z.im() );

  double 
    lhsReInf = inf( real_part ),
    lhsReSup = sup( real_part ),
    lhsImInf = inf( imag_part ),
    lhsImSup = sup( imag_part ),
    rhsReInf = inf( rhsRe     ),
    rhsReSup = sup( rhsRe     ),
    rhsImInf = inf( rhsIm     ),
    rhsImSup = sup( rhsIm     );

  //--- compute real part ---

  bool 
    a_repeat = ( rhsReInf < 0.0 ) && ( 0.0 < rhsReSup ),
    b_repeat = ( rhsImInf < 0.0 ) && ( 0.0 < rhsImSup );

  int rep;
  if( a_repeat || b_repeat ) rep = 2;
  else                       rep = 1;

  double a0,b0;
  if( rhsReInf >= 0.0 ) a0 = lhsReSup;
  else                  a0 = lhsReInf;
  if( rhsImInf >= 0.0 ) b0 = lhsImSup;
  else                  b0 = lhsImInf;

  double realteilSUP, realteilINF;
  realteilSUP = -numeric_limits<double>::max();

  for(int j = 1; j <= rep; j++)
    {
      p[1] = p[2] = p[3] = p[4] = true;

      double 
	mm1 = MinMax( false, a0, b0, rhsImInf, rhsRe, 1, 2 ),
	mm2 = MinMax( false, a0, b0, rhsImSup, rhsRe, 3, 4 ),
	mm3 = MinMax( false, b0, a0, rhsReInf, rhsIm, 1, 3 ),
	mm4 = MinMax( false, b0, a0, rhsReSup, rhsIm, 2, 4 );

      double 
	max1 = ( mm3>mm4 )?mm3:mm4,
	max2 = ( mm1>mm2 )?mm1:mm2,
	max3 = ( max1>max2 )?max1:max2;

      realteilSUP = ( realteilSUP>max3 )?realteilSUP:max3;

      if( p[1] )
	{
	  double y = f( a0, b0, rhsReInf, rhsImInf, +1 );
	  realteilSUP = ( realteilSUP>y )?realteilSUP:y;
	}
      if( p[2] )
	{
	  double y = f( a0, b0, rhsReSup, rhsImInf, +1 );
	  realteilSUP = ( realteilSUP>y )?realteilSUP:y;
	}
      if( p[3] )
	{
	  double y = f( a0, b0, rhsReInf, rhsImSup, +1 );
	  realteilSUP = ( realteilSUP>y )?realteilSUP:y;
	}
      if( p[4] )
	{
	  double y = f( a0, b0, rhsReSup, rhsImSup, +1 );
	  realteilSUP = ( realteilSUP>y )?realteilSUP:y;
	}
      
      if     ( a_repeat ) a0 = lhsReSup;
      else if( b_repeat ) b0 = lhsImSup;
    }

  if( rhsReInf>=0.0 ) a0 = lhsReInf;
  else                a0 = lhsReSup;
  if( rhsImInf>=0.0 ) b0 = lhsImInf;
  else                b0 = lhsImSup;

  realteilINF = numeric_limits<double>::max();

  for(int j = 1; j <= rep; j++ )
    {
      p[1] = p[2] = p[3] = p[4] = true;
      
      double 
	mm1 = MinMax( true, a0, b0, rhsImInf, rhsRe, 1, 2 ),
	mm2 = MinMax( true, a0, b0, rhsImSup, rhsRe, 3, 4 ),
	mm3 = MinMax( true, b0, a0, rhsReInf, rhsIm, 1, 3 ),
	mm4 = MinMax( true, b0, a0, rhsReSup, rhsIm, 2, 4 );

      double
	min1 = ( mm3<mm4 )?mm3:mm4,
	min2 = ( mm1<mm2 )?mm1:mm2,
	min3 = ( min1<min2 )?min1:min2;

      realteilINF = ( realteilINF<min3 )?realteilINF:min3;

      if( p[1] )
	{
	  double y = f( a0, b0, rhsReInf, rhsImInf, -1 );
	  realteilINF = ( realteilINF<y )?realteilINF:y;
	}
      if( p[2] )
	{
	  double y = f( a0, b0, rhsReSup, rhsImInf, -1 );
	  realteilINF = ( realteilINF<y )?realteilINF:y;
	}
      if( p[3] )
	{
	  double y = f( a0, b0, rhsReInf, rhsImSup, -1 );
	  realteilINF = ( realteilINF<y )?realteilINF:y;
	}
      if( p[4] )
	{
	  double y = f( a0, b0, rhsReSup, rhsImSup, -1 );
	  realteilINF = ( realteilINF<y )?realteilINF:y;
	}

      if     ( a_repeat ) a0 = lhsReInf;
      else if( b_repeat ) b0 = lhsImInf;
    }

  //--- compute imaginary part: g(a, b, c, d) = f(b, -a, c, d) ---

  a_repeat = ( rhsImInf < 0.0 ) && ( 0.0 < rhsImSup );
  b_repeat = ( rhsReInf < 0.0 ) && ( 0.0 < rhsReSup );

  if( rhsReInf >= 0.0 ) b0 = lhsImSup;
  else                  b0 = lhsImInf;
  if( rhsImInf >= 0.0 ) a0 = lhsReInf;
  else                  a0 = lhsReSup;

  double imagteilSUP, imagteilINF;
  imagteilSUP = -numeric_limits<double>::max();

  for(int j = 1; j <= rep; j++)
    {
      p[1] = p[2] = p[3] = p[4] = true;

      double
	mm1 = MinMax( false,  b0, -a0, rhsImInf, rhsRe, 1, 2 ),
	mm2 = MinMax( false,  b0, -a0, rhsImSup, rhsRe, 3, 4 ),
	mm3 = MinMax( false, -a0,  b0, rhsReInf, rhsIm, 1, 3 ),
	mm4 = MinMax( false, -a0,  b0, rhsReSup, rhsIm, 2, 4 );

      double
	max1 = ( mm1>mm2 )?mm1:mm2,
	max2 = ( mm3>mm4 )?mm3:mm4,
	max3 = ( max1>max2 )?max1:max2;
      
      imagteilSUP = ( imagteilSUP>max3 )?imagteilSUP:max3;

      if( p[1] )
	{
	  double y = f( b0, -a0, rhsReInf, rhsImInf, +1 );
	  imagteilSUP = ( imagteilSUP>y )?imagteilSUP:y;
	}
      if( p[2] )
	{
	  double y = f( b0, -a0, rhsReSup, rhsImInf, +1 );
	  imagteilSUP = ( imagteilSUP>y )?imagteilSUP:y;
	}
      if( p[3] )
	{
	  double y = f( b0, -a0, rhsReInf, rhsImSup, +1 );
	  imagteilSUP = ( imagteilSUP>y )?imagteilSUP:y;
	}
      if( p[4] )
	{
	  double y = f( b0, -a0, rhsReSup, rhsImSup, +1 );
	  imagteilSUP = ( imagteilSUP>y )?imagteilSUP:y;
	}

      if     ( b_repeat ) b0 = lhsImSup;
      else if( a_repeat ) a0 = lhsReInf;
    }
 
  if( rhsReInf>=0.0 ) b0 = lhsImInf;
  else                b0 = lhsImSup;
  if( rhsImInf>=0.0 ) a0 = lhsReSup;
  else                a0 = lhsReInf; 

  imagteilINF = numeric_limits<double>::max();
  
  for(int j = 1; j <= rep; j++)
    {
      p[1] = p[2] = p[3] = p[4] = true;
      
      double
	mm1 = MinMax( true,  b0, -a0, rhsImInf, rhsRe, 1, 2 ),
	mm2 = MinMax( true,  b0, -a0, rhsImSup, rhsRe, 3, 4 ),
	mm3 = MinMax( true, -a0,  b0, rhsReInf, rhsIm, 1, 3 ),
	mm4 = MinMax( true, -a0,  b0, rhsReSup, rhsIm, 2, 4 );
      
      double
	min1 = ( mm1<mm2 )?mm1:mm2,
	min2 = ( mm3<mm4 )?mm3:mm4,
	min3 = ( min1<min2 )?min1:min2;
      
      imagteilINF = ( imagteilINF<min3 )?imagteilINF:min3;
      
      if( p[1] )
	{
	  double y = f( b0, -a0, rhsReInf, rhsImInf, -1 );
	  imagteilINF = ( imagteilINF<y )?imagteilINF:y;
	}
      if( p[2] )
	{
	  double y = f( b0, -a0, rhsReSup, rhsImInf, -1 );
	  imagteilINF = ( imagteilINF<y )?imagteilINF:y;
	}
      if( p[3] )
	{
	  double y = f( b0, -a0, rhsReInf, rhsImSup, -1 );
	  imagteilINF = ( imagteilINF<y )?imagteilINF:y;
	}
      if( p[4] )
	{
	  double y = f( b0, -a0, rhsReSup, rhsImSup, -1 );
	  imagteilINF = ( imagteilINF<y )?imagteilINF:y;
	}
      
      if     ( b_repeat ) b0 = lhsImInf;
      else if( a_repeat ) a0 = lhsReSup;
    }

  real_part = Interval( realteilINF, realteilSUP );
  imag_part = Interval( imagteilINF, imagteilSUP );
#else //CXSC
  cxsc::cinterval 
    a( cxsc::interval(   real_part.inf(),   real_part.sup() ),
       cxsc::interval(   imag_part.inf(),   imag_part.sup() )  ),
    b( cxsc::interval( z.real_part.inf(), z.real_part.sup() ),
       cxsc::interval( z.imag_part.inf(), z.imag_part.sup() )  );

  a /= b; //Complex interval division of CXSC.

  real_part = Interval( cxsc::Re(a) );
  imag_part = Interval( cxsc::Im(a) );
#endif

  return *this;
}
//
//-- end Operator /= ----------------------------------------------------------


//-- Operator << --------------------------------------------------------------
//
std::ostream& operator << (std::ostream& os, const CInterval& z)
{
  os << "(" << z.re() << "," << z.im() << ")";
  return os;
}


//-- Operator >> --------------------------------------------------------------
//
std::istream& operator >> (std::istream& is, CInterval& z)
{
  char ch;
  while( is && (ch = is.get()) && !(isdigit(ch) || ch == '+' || ch == '-') ) {} //

  std::string s1 = "";
 
  if( ch != -1 ) s1 += ch; 

  while( is && (ch = is.get()) &&  (isalnum(ch) || ch == '.' || ch == ',' || ch == 'E' || ch == 'e' || ch == '+' || ch == '-') ) s1 += ch;

  while( is && (ch = is.get()) && !(isdigit(ch) || ch == '+' || ch == '-') ) {} //

  std::string s2 = ""; 

  if( ch != -1 ) s2 += ch;

  while( is && (ch = is.get()) &&  (isalnum(ch) || ch == '.' || ch == ',' || ch == 'E' || ch == 'e' || ch == '+' || ch == '-') ) s2 += ch;

  z.real_part = z.imag_part = 0.0;

  if( s1 != "" )
    {
      std::istringstream in1("["+s1+"]");
      in1 >> z.real_part;
    }
  if( s2 != "" )
    {
      std::istringstream in2("["+s2+"]");
      in2 >> z.imag_part;
    }

  return is;
}
//
//-- end Operator >> ----------------------------------------------------------

/*

  End of File: cinterval.cpp

*/
