{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ImplicitParams #-} {-| Module : Numeric.AERN.RealArithmetic.Interval.ElementaryFromFieldOps.Sqrt Description : an interval-specific implementation of sqrt Copyright : (c) Michal Konecny License : BSD3 Maintainer : mikkonecny@gmail.com Stability : experimental Portability : portable An interval-specific implementation of sqrt. -} module Numeric.AERN.RealArithmetic.Interval.ElementaryFromFieldOps.Sqrt where import qualified Numeric.AERN.RealArithmetic.NumericOrderRounding as ArithUpDn import Numeric.AERN.RealArithmetic.NumericOrderRounding.OpsImplicitEffort import Numeric.AERN.RealArithmetic.NumericOrderRounding.InPlace.OpsImplicitEffort --import qualified Numeric.AERN.RealArithmetic.RefinementOrderRounding as ArithInOut import Numeric.AERN.RealArithmetic.RefinementOrderRounding.OpsImplicitEffort import qualified Numeric.AERN.Basics.NumericOrder as NumOrd import qualified Numeric.AERN.Basics.RefinementOrder as RefOrd import Numeric.AERN.Basics.RefinementOrder.OpsImplicitEffort import Numeric.AERN.RealArithmetic.ExactOps import Numeric.AERN.RealArithmetic.Interval import Numeric.AERN.Basics.Interval import Numeric.AERN.Basics.Consistency import Numeric.AERN.Basics.Effort import Numeric.AERN.Basics.Mutable import Numeric.AERN.Basics.Exception import Control.Exception (throw) import Control.Monad.ST (ST) sqrtOutThinArg :: (HasZero e, HasOne e, Show e, NumOrd.RoundedLattice e, NumOrd.PartialComparison e, ArithUpDn.Convertible e Double, ArithUpDn.RoundedMixedField e Int, ArithUpDn.RoundedField e) => ArithUpDn.FieldOpsEffortIndicator e -> ArithUpDn.MixedFieldOpsEffortIndicator e Int -> NumOrd.MinmaxEffortIndicator e -> NumOrd.PartialCompareEffortIndicator e -> ArithUpDn.ConvertEffortIndicator e Double -> Int {-^ the highest number of iterations of Newton method to make -} -> e {-^ @x@ as a singleton interval -} -> (Interval e) {-^ @sqrt(x)@ -} sqrtOutThinArg effortField effortMixedField effortMinmax effortCompare effortToDouble maxIters x | sureIsZero x = zero | not (sureAbove0 x) = case (sureAbove0 (neg x)) of True -> throw $ AERNDomViolationException $ "sqrtOutThinArg: applied to a negative argument " ++ show x _ -> throw $ AERNMaybeDomViolationException $ "sqrtOutThinArg: cannot check that sqrt is applied to a positive argument " ++ show x | xRecipSqrtDownInFastRegion = -- unsafePrint ("AERN: sqrtOutThinArg: lower bound in fast region") $ Interval (x *. xRecipSqrtDown) (x *^ xRecipSqrtUp) -- best upper bound estimate based on an error estimate of the lower bound | sureAbove0 xRecipSqrtDown = -- unsafePrint ("AERN: sqrtOutThinArg: lower bound NOT in fast region, using division") $ Interval (x *. xRecipSqrtDown) (recipUp xRecipSqrtDown) -- an upper bound using division - introduces a fairly large error; used when iteration has not reached the fast region | otherwise = -- unsafePrint ("AERN: sqrtOutThinArg: lower bound too close to zero, using dummy upper bound") $ Interval (x *. xRecipSqrtDown) (NumOrd.maxUpEff effortMinmax x one) -- a dummy fallback upper bound where lower bound is too close to 0 where (xRecipSqrtDownPrev, xRecipSqrtDown) = recipSqrtDown xRecipSqrtDownInFastRegion = case ArithUpDn.convertDnEff effortToDouble t of Just lowerBound -> lowerBound > (0.381966012 :: Double) -- (3 - sqrt 5)/2 Nothing -> False where t = (xRecipSqrtDownPrev *. xRecipSqrtDownPrev) *. x xRecipSqrtUp = -- only valid in "fast" region, ie where the error is smaller -- than the gap between the results of the last two iterations (xRecipSqrtLastUp +^ xRecipSqrtLastUp) +^ (neg xRecipSqrtDownPrev) xRecipSqrtLastUp = (xRecipSqrtDownPrev /^| 2 ) *^ (3 |+^ (neg $ x *. (xRecipSqrtDownPrev *. xRecipSqrtDownPrev))) sureAbove0 t = case ArithUpDn.convertDnEff effortToDouble t of Just lowerBound -> lowerBound > (0 :: Double) Nothing -> False sureIsZero t = case NumOrd.pEqualEff effortCompare t zero of Just True -> True _ -> False x1 +^ x2 = ArithUpDn.addUpEff effortAdd x1 x2 x1 *^ x2 = ArithUpDn.multUpEff effortMult x1 x2 x1 *. x2 = ArithUpDn.multDnEff effortMult x1 x2 recipUp x = ArithUpDn.recipUpEff effortDiv x recipDn x = ArithUpDn.recipDnEff effortDiv x n |+^ x = ArithUpDn.mixedAddUpEff effortAddInt x (n :: Int) n |+. x = ArithUpDn.mixedAddDnEff effortAddInt x (n :: Int) x /^| n = ArithUpDn.mixedDivUpEff effortDivInt x (n :: Int) x /.| n = ArithUpDn.mixedDivDnEff effortDivInt x (n :: Int) effortAdd = ArithUpDn.fldEffortAdd x effortField effortMult = ArithUpDn.fldEffortMult x effortField effortDiv = ArithUpDn.fldEffortDiv x effortField effortAddInt = ArithUpDn.mxfldEffortAdd x (0::Int) effortMixedField effortDivInt = ArithUpDn.mxfldEffortDiv x (0::Int) effortMixedField recipSqrtDown | q0OK = -- computed an approximation in the stable region: iterRecipSqrt maxIters zero q0 -- then iterate! | otherwise = (zero, zero) -- zero is an always correct lower approximation where (q0OK, q0) = (sureAbove0 xPlusOneUp && sureAbove0 babylon2, recipDn babylon2) where -- babylon2 = (x+5)/4 - 1/(x+1) rounded upwards -- ie two Babylonian iterations -- \ t -> (t + x/t)/2 -- starting with t_0 = x: -- t_1 = (x + 1)/2 -- t_2 = ((x + 1)/2 + x/((x + 1)/2))/2 = -- ((x + 1)/2 + 2x/(x + 1))/2 = -- ((x + 1)^2 + 4x)/4(x+1) = -- (x^2 + 6x + 1)/4(x+1) = -- (x^2 + 6x + 5 - 4)/4(x+1) = -- ((x + 5)(x + 1) - 4)/4(x+1) = -- (x + 5) - 1/(x+1) babylon2 = xPlus5Div4Up +^ (neg xPlusOneRecipDn) xPlus5Div4Up = ((5::Int) |+^ x) /^| (4::Int) xPlusOneRecipDn = recipDn xPlusOneUp xPlusOneUp = (1::Int) |+^ x -- iteratively improve q, a lower bound on sqrt(1/x) -- using the formula q_{n+1} = (q_n / 2) * (3 - x * q_n * q_n) -- quoted eg in http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Iterative_methods_for_reciprocal_square_roots iterRecipSqrt maxIters qNm2 qNm1 | maxIters > 0 && sureAbove0 qNm1 = -- unsafePrint ("AERN: sqrtOutThinArg: recipSqrtDown: iterRecipSqrt: maxIters = " ++ show maxIters) $ iterRecipSqrt (maxIters - 1) qNm1 qN | otherwise = (qNm2, qNm1) where qN = (qNm1 /.| (2::Int)) *. ((3::Int) |+. (neg $ x *^ (qNm1 *^ qNm1))) sqrtOutThinArgInPlace :: (CanBeMutable e, HasZero e, HasOne e, Show e, NumOrd.RoundedLattice e, NumOrd.RoundedLatticeInPlace e, NumOrd.PartialComparison e, ArithUpDn.Convertible e Double, ArithUpDn.RoundedMixedField e Int, ArithUpDn.RoundedMixedFieldInPlace e Int, ArithUpDn.RoundedField e, ArithUpDn.RoundedFieldInPlace e) => ArithUpDn.FieldOpsEffortIndicator e -> ArithUpDn.MixedFieldOpsEffortIndicator e Int -> NumOrd.MinmaxEffortIndicator e -> NumOrd.PartialCompareEffortIndicator e -> ArithUpDn.ConvertEffortIndicator e Double -> Mutable (Interval e) s {-^ where to write the result @sqrt(x)@ -} -> Int {-^ the highest number of iterations of Newton method to make -} -> Mutable e s {-^ @x@ viewed as a singleton interval -} -> ST s () sqrtOutThinArgInPlace effortField effortMixedField effortMinmax effortCompare effortToDouble resM@(MInterval resLM resRM) maxIters xM = do -- we need x - a pure version of xM for branching conditions: x <- unsafeReadMutable xM -- unsafe is OK because we do not write into xM and we write to resM only as the last thing -- and the value of x does not escape beyond this function let ?addUpDnEffort = ArithUpDn.fldEffortAdd x effortField let ?multUpDnEffort = ArithUpDn.fldEffortMult x effortField let ?mixedAddUpDnEffort = ArithUpDn.mxfldEffortAdd x (0::Int) effortMixedField let ?mixedMultUpDnEffort = ArithUpDn.mxfldEffortMult x (0::Int) effortMixedField let ?mixedDivUpDnEffort = ArithUpDn.mxfldEffortDiv x (0::Int) effortMixedField computeSqrt xM x where computeSqrt xM x = do continue where continue | sureIsZero x = writeMutable resM zero | not (sureAbove0 x) = case (sureAbove0 (neg x)) of True -> throw $ AERNDomViolationException $ "sqrtOutThinArgInPlace: applied to a negative argument " ++ show x _ -> throw $ AERNMaybeDomViolationException $ "sqrtOutThinArgInPlace: cannot check that sqrt is applied to a positive argument " ++ show x | otherwise = do -- declare some variables: temp1M <- makeMutable zero temp2M <- makeMutable zero -- iterate using Newton's method, assign results of last two iterations to the above vars: prevFirst <- recipSqrtDown temp1M temp2M case prevFirst of True -> assignBounds temp1M temp2M False -> assignBounds temp2M temp1M where assignBounds xRecipSqrtDownPrevM xRecipSqrtDownM = do xRecipSqrtDown <- unsafeReadMutable xRecipSqrtDownM xRecipSqrtDownPrev <- unsafeReadMutable xRecipSqrtDownPrevM -- assign lower bound resL := x *. xRecipSqrtDown: ArithUpDn.multDnInPlaceEff effortMult resLM xM xRecipSqrtDownM -- assign upper bound the best applicable method out of three methods: constructUpperBound xRecipSqrtDownM xRecipSqrtDown xRecipSqrtDownPrevM xRecipSqrtDownPrev constructUpperBound xRecipSqrtDownM xRecipSqrtDown xRecipSqrtDownPrevM xRecipSqrtDownPrev | xRecipSqrtDownInFastRegion = do -- in fast region, use the difference between the last two approx: -- first, we need an upwards-rounded version of xRecipSqrtDown: -- xRecipSqrtLastUp := newton... xRecipSqrtDownPrev: let xRecipSqrtLastUpM = xRecipSqrtDownM -- safely reuse variable newtonIterateUp xRecipSqrtLastUpM xRecipSqrtDownPrevM -- now compute and use the difference: -- xRecipSqrtUp := 2*^xRecipSqrtLastUp -^ xRecipSqrtDownPrev: -- only valid in "fast" region, ie where the error is smaller -- than the gap between the results of the last two iterations let xRecipSqrtUpM = xRecipSqrtLastUpM -- safely reuse variable xRecipSqrtLastUpM *^|= (2 :: Int) xRecipSqrtLastUpM -^= xRecipSqrtDownPrevM -- assign upper bound resR := x *^ xRecipSqrtUp: ArithUpDn.multUpInPlaceEff effortMult resRM xM xRecipSqrtUpM | sureAbove0 xRecipSqrtDown = do -- compute upper bound resR := 1 /^ xRecipSqrtDown: -- introduces a fairly large error; -- used when iteration has not reached the fast region ArithUpDn.recipUpInPlaceEff effortDiv resRM xRecipSqrtDownM | otherwise = do -- a dummy fallback upper bound where lower bound is too close to 0: unsafeWriteMutable resRM $ NumOrd.maxUpEff effortMinmax x one where xRecipSqrtDownInFastRegion = case ArithUpDn.convertDnEff effortToDouble t of Just lowerBound -> lowerBound > (0.381966012 :: Double) -- (3 - sqrt 5)/2 Nothing -> False where t = (xRecipSqrtDownPrev *. xRecipSqrtDownPrev) *. x newtonIterateUp resM tM = -- assumes no aliasing between resM and tM, does not change tM do -- res := -- (t /^| 2 ) -- *^ -- (3 |+^ (neg $ x *. (t *. t))) ArithUpDn.multUpInPlaceEff effortMult resM tM tM resM *^= xM negInPlace resM resM resM +.|= (3 :: Int) resM *.= tM resM /.|= (2 :: Int) newtonIterateDn resM tM = -- assumes no aliasing between resM and tM, does not change tM do -- res := -- (t /.| 2 ) -- *. -- (3 |+. (neg $ x *^ (t *^ t))) ArithUpDn.multDnInPlaceEff effortMult resM tM tM resM *.= xM negInPlace resM resM resM +^|= (3 :: Int) resM *^= tM resM /^|= (2 :: Int) sureAbove0 t = case ArithUpDn.convertDnEff effortToDouble t of Just lowerBound -> lowerBound > (0 :: Double) Nothing -> False sureIsZero t = case NumOrd.pEqualEff effortCompare t zero of Just True -> True _ -> False effortAdd = ArithUpDn.fldEffortAdd x effortField effortMult = ArithUpDn.fldEffortMult x effortField effortDiv = ArithUpDn.fldEffortDiv x effortField effortMultInt = ArithUpDn.mxfldEffortMult x (0::Int) effortMixedField effortAddInt = ArithUpDn.mxfldEffortAdd x (0::Int) effortMixedField effortDivInt = ArithUpDn.mxfldEffortDiv x (0::Int) effortMixedField recipSqrtDown aM bM | q0OK = -- computed an approximation in the stable region: do writeMutable aM zero writeMutable bM q0 iterRecipSqrt maxIters True aM bM -- then iterate! | otherwise = do writeMutable aM zero -- zero is an always correct lower approximation writeMutable bM zero return True where (q0OK, q0) = (sureAbove0 xPlusOneUp && sureAbove0 babylon2, recipDn babylon2) where -- babylon2 = (x+5)/4 - 1/(x+1) rounded upwards -- ie two Babylonian iterations -- \ t -> (t + x/t)/2 -- starting with t_0 = x: -- t_1 = (x + 1)/2 -- t_2 = ((x + 1)/2 + x/((x + 1)/2))/2 = -- ((x + 1)/2 + 2x/(x + 1))/2 = -- ((x + 1)^2 + 4x)/4(x+1) = -- (x^2 + 6x + 1)/4(x+1) = -- (x^2 + 6x + 5 - 4)/4(x+1) = -- ((x + 5)(x + 1) - 4)/4(x+1) = -- (x + 5) - 1/(x+1) babylon2 = xPlus5Div4Up +^ (neg xPlusOneRecipDn) xPlus5Div4Up = ((5::Int) |+^ x) /^| (4::Int) xPlusOneRecipDn = recipDn xPlusOneUp xPlusOneUp = (1::Int) |+^ x recipDn = ArithUpDn.recipDnEff effortDiv -- iteratively improve q, a lower bound on sqrt(1/x) -- using the formula q_{n+1} = (q_n / 2) * (3 - x * q_n * q_n) -- quoted eg in http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Iterative_methods_for_reciprocal_square_roots iterRecipSqrt maxIters prevFirst aM bM = -- assuming aM and bM do not alias do qNm1 <- unsafeReadMutable qNm1M case maxIters > 0 && sureAbove0 qNm1 of False -> -- should not or cannot continue iterating return prevFirst -- indicate which of the two variables has the older result True -> do newtonIterateDn qN qNm1M iterRecipSqrt (maxIters - 1) (not prevFirst) bM aM -- swap the variables where (qNm2M, qNm1M) | prevFirst = (aM,bM) | otherwise = (bM,aM) qN = qNm2M