packages feed

aern2-mfun-0.2.9.0: src/AERN2/BoxFun/Optimisation.hs

module AERN2.BoxFun.Optimisation where

import qualified Prelude
import MixedTypesNumPrelude
import qualified Numeric.CollectErrors as CN
import AERN2.MP.Dyadic
import AERN2.MP.Ball
import AERN2.BoxFun.Box (Box)
import qualified AERN2.BoxFun.Box as Box
import AERN2.BoxFun.Type
import AERN2.Kleenean
import AERN2.Linear.Vector.Type as V
import AERN2.Linear.Matrix.Type
import AERN2.Linear.Matrix.Inverse

import qualified Data.List as List

import qualified AERN2.PQueue as Q

import AERN2.Util.Util

import Debug.Trace (trace)

globalMinimumGreaterThanN :: BoxFun -> Accuracy -> CN Rational -> Precision -> Bool
globalMinimumGreaterThanN f ac n initialPrecision =
    trace (show x)
    x !>! n
    where x = globalMinimum f ac initialPrecision 

minFun :: BoxFun -> Accuracy -> Precision -> (Integer, CN MPBall)
minFun f ac initialPrecision = 
    bestLocalMinimum f (domain f) ac initialPrecision

data SearchBox =
    SearchBox
    {
            extents :: Box
        ,   minimum :: CN MPBall 
    } deriving (Show)

instance 
    HasPrecision SearchBox
    where
    getPrecision (SearchBox b _) = getPrecision b

instance 
    CanSetPrecision SearchBox
    where
    setPrecision p (SearchBox b m) = SearchBox (setPrecision p b) m

instance Prelude.Eq SearchBox where
    (==) (SearchBox _ _) (SearchBox _ _) =
        False -- TODO: safe?

instance Prelude.Ord SearchBox where
    (<=) (SearchBox _ min0) (SearchBox _ min1) = 
        case (CN.toEither $ (lowerBound min0 :: CN MPBall), CN.toEither $ (lowerBound min1 :: CN MPBall)) of
            (Left _, Left _) -> True
            (Left _, Right _ ) -> True
            (Right _ , Left _) -> False
            (Right m0, Right m1) -> 
                centre m0 - (dyadic $ radius m0) <= centre m1 - (dyadic $ radius m1) -- TODO: radius should be 0

---

globalMinimumWithCutoff :: BoxFun -> Accuracy -> CN MPBall -> Precision -> CN MPBall
globalMinimumWithCutoff f ac cutoff initialPrecision =
    if dimension f == 1 then
        let
            fl       = apply f (V.map lowerBound $ domain f)
            fr       = apply f (V.map upperBound $ domain f)
            localMin = snd $ bestLocalMinimumWithCutoff f (domain f) ac cutoff initialPrecision
        in
            min fl $ min localMin fr
    else 
        let
            localMin       = snd $ bestLocalMinimumWithCutoff f (domain f) ac cutoff initialPrecision
            boundaryFuns   = boundaryRestrictions f
            boundaryMinima = List.map (\g -> globalMinimumWithCutoff g ac (min cutoff ((upperBound localMin :: CN MPBall))) initialPrecision) boundaryFuns
        in
            List.foldl' min localMin boundaryMinima


globalMinimum :: BoxFun -> Accuracy -> Precision -> CN MPBall
globalMinimum f ac initialPrecision =
    globalMinimumWithCutoff f ac (apply f (centre boxp)) initialPrecision
    where
    boxp = setPrecision initialPrecision (domain f)

bestLocalMinimum :: BoxFun -> Box -> Accuracy -> Precision -> (Integer, CN MPBall)
bestLocalMinimum f box ac initialPrecision =
    bestLocalMinimumWithCutoff f box ac (apply f (centre boxp)) initialPrecision
    where
    boxp = setPrecision initialPrecision box

bestLocalMinimumWithCutoff :: BoxFun -> Box -> Accuracy -> CN MPBall -> Precision -> (Integer, CN MPBall)
bestLocalMinimumWithCutoff f box ac initialCutoff initialPrecision =
    aux initialQueue initialCutoff 0 dummyBox
    where
    boxp             = setPrecision initialPrecision box
    initialRange     = apply f boxp
    initialSearchBox = SearchBox boxp initialRange
    initialQueue     = Q.singleton initialSearchBox
    dummyBox         = SearchBox (V.fromList [cn $ mpBall $ 10^6]) initialRange -- TODO: hack...

    aux q cutoff steps (SearchBox _lastBox rng) =  
        case Q.minView q of
            Nothing -> trace ("no local minimum.") $ (steps, rng)
            Just (minBox, q') ->
                --trace ("value: "++ (show $ val)) $
                trace ("min box: "++ (show $ minBox)) $
                --trace ("box acc: "++ (show $ getAccuracy $ ext)) $
                --trace (show $ Box.width (extents minBox)) $
                --trace ("lower bound "++ (show $ Box.lowerBound $ val)) $
                --trace ("val' "++ (show $ val')) $
                trace ("cutoff: "++ (show $ cutoff)) $
                trace ("queue size: "++ (show $ Q.size q)) $
                --trace ("cutoff == 0? "++(show $ cutoff == (mpBall 0))) $
                --trace ("precision: "++ (show $ precision)) $
                --trace ("dist to last "++ (show $ distToLast)) $
                --trace ("accuracy: "++ (show $ getAccuracy val')) $
                --trace ("precision centre: "++ (show $ fmap (getPrecision . centre) val)) $
                if getAccuracy val' >= ac then
                    (steps, val')
                else 
                    aux q'' newCutoff (steps + 1) (SearchBox ext rng)
                where
                val' = fromEndpointsAsIntervals (lowerBound val) (cutoff)
                SearchBox ext val = minBox

                (newCutoff, newBoxes) = 
                    processBox f ac cutoff minBox

                q'' = foldr (Q.insert) q' newBoxes

lipschitzContraction :: BoxFun -> Box -> SearchBox -> SearchBox
lipschitzContraction f g (SearchBox box m) =
    {-trace("fa: "++(show $ getAccuracy (apply f box))) $
    trace("la: "++(show $ getAccuracy $ dotProduct)) $
    trace("ba: "++(show $ getAccuracy $ box ! int 0)) $-}
    {-if (radius $ (~!) $ newRange) < (radius $ (~!) $ m) then
        trace ("Lipschitz better.")
        SearchBox box m'
    else -}
    SearchBox box m'
    where
    boxCentre      = centre box
    centreValue    = apply f boxCentre
    difference     = box - boxCentre
    dotProduct     = g * difference 
    newRange       = centreValue + dotProduct
    m'             = intersectCN m newRange

lipschitzRange :: BoxFun -> CN MPBall -> Box -> Box -> Box -> CN MPBall -> CN MPBall
lipschitzRange _f fc c g box m =
    m'
    where
    difference     = box - c
    normG          = Box.ellOneNorm g
    normDiff       = Box.inftyNorm  difference
    dotProduct     = normG * normDiff
    newRange       = fc + (fromEndpointsAsIntervals (-dotProduct) dotProduct :: CN MPBall)
    m'             = intersectCN m newRange

applyLipschitz :: BoxFun -> Box -> CN MPBall
applyLipschitz f box = 
    lipschitzRange f fbc bc dfb' box fb
    where
    (fb, dfb') = valueGradient f box
    bc  = centre box
    fbc  = apply f bc

increasePrecision :: Precision -> Precision
increasePrecision p =
    p + (prec $ (integer p) `Prelude.div` 2)

newtonStep :: BoxFun -> Accuracy -> Vector (CN MPBall) -> Vector (CN MPBall) -> Matrix (CN MPBall) -> SearchBox -> Bool -> Maybe (Bool, SearchBox)
newtonStep f ac c dfc hInv b@(SearchBox box m) newtonSuccesful = 
    --Just $ SearchBox box' m'
    {-trace ("precision m "++(show $ (fmap getPrecision) m)) $
    trace ("precision m' "++(show $ (fmap getPrecision) m')) $
    trace ("precision box centre "++(show $ getPrecision c)) $
    trace ("precision box "++(show $ getPrecision box)) $
    trace ("precision newton box "++(show $ getPrecision newtonBox)) $
    trace ("precision box' "++(show $ getPrecision box')) $
    trace ("precision hInv "++(show $ getPrecision (entries hInv ! int 0))) $-}
    if getAccuracy m >= ac then
        Just (newtonSuccesful, b)
    --else if not hInvDefined then
    --    Just (newtonSuccesful, b)
    else if Box.intersectionCertainlyEmpty box newtonBox then
        Nothing
    else if Box.width box' !<=! (dyadic $ 0.75) * Box.width box then
        if getAccuracy m' > getAccuracy m then
            newtonStep f ac c dfc hInv (SearchBox box' m') True
        else 
            Just (True, SearchBox (setPrecision (increasePrecision $ getPrecision box') box') m')
    else 
        Just (newtonSuccesful, SearchBox box' m')
    where
    {-c           = centre box
    dfc         = gradient f c-}
    -- hInvDefined = V.foldl' (&&) (True) $ V.map (isJust . fst . ensureNoCN) (entries hInv)
    newtonBox   = c - hInv * (dfc)
    box'        = Box.nonEmptyIntersection box newtonBox
    m'          = apply f box'

processBox :: BoxFun -> Accuracy -> CN MPBall -> SearchBox -> (CN MPBall, [SearchBox])
processBox f ac cutoff box =
    if getAccuracy ext < bits 10 then 
        split f (gradient f ext) cutoff ext  
    else 
        result
    where
    ext            = extents box
    (_fb, dfb, hfb) = valueGradientHessian f ext
    c              = centre ext
    dfc            = gradient f c
    maybeHinv      = inverse hfb
    -- p              = getPrecision box
    box'           = --Just (False, box)
        case maybeHinv of 
            Nothing   -> Just (False, box)
            Just hInv -> newtonStep f ac c dfc hInv box False
    result =
        case box' of 
            Nothing -> (cutoff, [])
            Just (newtonSuccesful, bx@(SearchBox bxe m)) ->
                let
                    c' = min (upperBound $ apply f $ centre bxe :: CN MPBall) cutoff    
                in
                if newtonSuccesful then
                    if getAccuracy m >= ac then
                        (c', [bx])
                    else
                        processBox f ac c' bx
                else
                    split f dfb c' bxe

split :: BoxFun -> Vector (CN MPBall) -> CN MPBall -> Box -> (CN MPBall, [SearchBox])
split f dfb cutoff bxe = 
    let
    diff    = bxe - centre bxe
    dir i   = (fmap dyadic) $ (fmap radius) $ (dfb ! i) * (diff ! i) :: CN Dyadic
    dirs    = V.map dir $ V.enumFromTo 0 (V.length bxe - 1)
    dirsDefined = V.foldl' (&&) True $ V.map (not . CN.hasError) dirs
    aux k j d = 
        if k == V.length bxe then 
            j 
        else 
            let
                d' = unCN $ dirs ! k
            in
            if d' > d then 
                aux (k + 1) k d'
            else
                aux (k + 1) j d
    splittingIndex = 
        if dirsDefined then (aux 1 0 (unCN $ dirs ! 0)) else Box.widestDirection bxe
    (a , b)    = Box.bisect splittingIndex bxe
    (fa, dfa') = valueGradient f a
    (fb, dfb') = valueGradient f b
    ac  = centre a
    bc  = centre b
    fac  = apply f ac
    fbc  = apply f bc
    fa'  = lipschitzRange f fac ac dfa' a fa
    fb'  = lipschitzRange f fbc bc dfb' b fb
    cutoff'       = min (upperBound fac :: CN MPBall) $ min (upperBound fbc :: CN MPBall) cutoff
    leftMonotone  = V.foldl' (||) False $ V.map (!/=! 0) dfa'
    rightMonotone = V.foldl' (||) False $ V.map (!/=! 0) dfb'
    boxes = 
        case (leftMonotone || fa' !>! cutoff', rightMonotone || fb' !>! cutoff') of
            (True,  True)  -> []
            (True,  False) -> [SearchBox b fb']
            (False, True)  -> [SearchBox a fa']
            (False, False) -> [SearchBox a fa', SearchBox b fb']
    in
        (cutoff', boxes)

-- Precondition: f and g must have the same domain
maxBoxFunGreaterThanN :: BoxFun -> BoxFun -> CN Rational -> Precision -> Bool
maxBoxFunGreaterThanN f g n initialPrecision =
    case Box.getEndpoints fbox == Box.getEndpoints gbox of
        CertainTrue ->
            checkMaxAboveN f g ||
                (Box.width fboxp !>! cutoff && Box.width gboxp !>! cutoff) &&
                    let
                        newBoxes = Box.fullBisect fboxp

                        updateDomain z = BoxFun (dimension z) (bf_eval z)

                        checkBoxes [] = True
                        checkBoxes (box : boxes) = 
                            if checkMaxAboveN (updateDomain f box) (updateDomain g box) 
                                then checkBoxes boxes
                                else maxBoxFunGreaterThanN f' g' n initialPrecision && checkBoxes boxes
                                where
                                    f' = updateDomain f box
                                    g' = updateDomain g box

                    in
                        checkBoxes newBoxes

        _ ->
            trace "Domain of f not equal to domain of g"
            False
    where
        cutoff = 1/2^10

        fbox                = domain f
        fboxp               = setPrecision initialPrecision fbox 

        gbox                = domain g
        gboxp               = setPrecision initialPrecision gbox

        checkMaxAboveN h i = applyMinimum h !>! n || applyMinimum i !>! n