Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Domain type for representing bounds #23

Merged
merged 1 commit into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions numeric-optimization/src/Numeric/Optimization.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ import Data.Default.Class
import Data.Functor.Contravariant
import Data.IORef
import Data.Maybe
import qualified Data.Vector as V
import Data.Vector.Storable (Vector)
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Generic.Mutable as VGM
Expand Down Expand Up @@ -350,9 +349,7 @@ instance Exception OptimizationException
-- If your problem is a constrained problem. You can add constraints
-- using the wrappers: 'WithBounds' and 'WithConstraints'. For example:
--
-- > import Data.Vector (fromList)
-- >
-- > (\(x,y) -> x**2 + y**2) `WithBounds` (fromList [(-1,1), (-2,2)])
-- > (\(x,y) -> x**2 + y**2) `WithBounds` ((-1,-2), (1,2))
--
-- You can use [numeric-optimization-ad](https://hackage.haskell.org/package/numeric-optimization-ad),
-- [numeric-optimization-ad-delcont](https://hackage.haskell.org/package/numeric-optimization-ad-delcont),
Expand Down Expand Up @@ -414,7 +411,7 @@ class IsProblem prob where

-- | Bounds
--
bounds :: prob -> Maybe (V.Vector (Double, Double))
bounds :: prob -> Maybe (Domain prob, Domain prob)
bounds _ = Nothing

-- | Constraints
Expand Down Expand Up @@ -484,14 +481,18 @@ hasOptionalDict = Just Dict
data Constraint

-- | Bounds for unconstrained problems, i.e. (-∞,+∞).
boundsUnconstrained :: Int -> V.Vector (Double, Double)
boundsUnconstrained n = V.replicate n (-infinity, infinity)
boundsUnconstrained :: IsProblem prob => prob -> Domain prob -> (Domain prob, Domain prob)
boundsUnconstrained prob x = (lb, ub)
where
v = toVector prob x
lb = updateFromVector prob x $ VG.map (\_ -> -infinity) v
ub = updateFromVector prob x $ VG.map (\_ -> infinity) v

-- | Whether all lower bounds are -∞ and all upper bounds are +∞.
isUnconstainedBounds :: V.Vector (Double, Double) -> Bool
isUnconstainedBounds = V.all p
where
p (lb, ub) = isInfinite lb && lb < 0 && isInfinite ub && ub > 0
isUnconstainedBounds :: IsProblem prob => prob -> (Domain prob, Domain prob) -> Bool
isUnconstainedBounds prob (lb, ub) =
VG.all (\b -> isInfinite b && b < 0) (toVector prob lb) &&
VG.all (\b -> isInfinite b && b > 0) (toVector prob ub)


-- | Minimization of scalar function of one or more variables.
Expand Down Expand Up @@ -769,8 +770,8 @@ minimize_LBFGSB params prob x0 = do
let bounds' =
case bounds prob of
Nothing -> []
Just vec -> map convertB (VG.toList vec)
convertB (lb, ub) =
Just (lb, ub) -> zipWith convertB (VG.toList lb) (VG.toList ub)
convertB lb ub =
( if isInfinite lb && lb < 0
then Nothing
else Just lb
Expand Down Expand Up @@ -988,7 +989,7 @@ instance IsProblem prob => Optionally (HasHessian (WithHessian prob)) where
-- ------------------------------------------------------------------------

-- | Wrapper type for adding bounds to a problem
data WithBounds prob = WithBounds prob (V.Vector (Double, Double))
data WithBounds prob = WithBounds prob (Domain prob, Domain prob)

instance IsProblem prob => IsProblem (WithBounds prob) where
type Domain (WithBounds prob) = Domain prob
Expand Down Expand Up @@ -1071,7 +1072,10 @@ instance IsProblem prob => IsProblem (AsVectorProblem prob) where
-- default implementation of 'writeToMVector' is what we want

func (AsVectorProblem prob x0) = func prob . updateFromVector prob x0
bounds (AsVectorProblem prob _x0) = bounds prob
bounds (AsVectorProblem prob _x0) =
case bounds prob of
Nothing -> Nothing
Just (lb, ub) -> Just (toVector prob lb, toVector prob ub)
constraints (AsVectorProblem prob _x0) = constraints prob

instance HasGrad prob => HasGrad (AsVectorProblem prob) where
Expand Down
10 changes: 5 additions & 5 deletions numeric-optimization/test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ main = hspec $ do

context "when given a problem with bounds" $ do
it "should throw UnsupportedProblem" $ do
minimize CGDescent def (rosenbrock `WithGrad` rosenbrock' `WithBounds` [(-4,2), (-5,2)]) (-3,-4)
minimize CGDescent def (rosenbrock `WithGrad` rosenbrock' `WithBounds` ((-4,-5), (2,2))) (-3,-4)
`shouldThrow` (\case { UnsupportedProblem _ -> True; _ -> False })

describe "minimize LBFGS" $ do
Expand Down Expand Up @@ -111,7 +111,7 @@ main = hspec $ do

context "when given a problem with bounds" $ do
it "should throw UnsupportedProblem" $ do
minimize LBFGS def (rosenbrock `WithGrad` rosenbrock' `WithBounds` [(-4,2), (-5,2)]) (-3,-4)
minimize LBFGS def (rosenbrock `WithGrad` rosenbrock' `WithBounds` ((-4,-5), (2,2))) (-3,-4)
`shouldThrow` (\case { UnsupportedProblem _ -> True; _ -> False })

describe "minimize LBFGSB" $ do
Expand Down Expand Up @@ -151,14 +151,14 @@ main = hspec $ do

context "when given rosenbrock function with bounds" $
it "returns the global optimum" $ do
let prob = rosenbrock `WithGrad` rosenbrock' `WithBounds` [(-4,2), (-5,2)]
let prob = rosenbrock `WithGrad` rosenbrock' `WithBounds` ((-4,-5), (2,2))
result <- minimize LBFGSB def prob (-3,-4)
resultSuccess result `shouldBe` True
assertAllClose (def :: Tol Double) (resultSolution result) (1,1)

context "when given rosenbrock function with bounds (-infinity, +infinity)" $
it "returns the global optimum" $ do
let prob = rosenbrock `WithGrad` rosenbrock' `WithBounds` boundsUnconstrained 2
let prob = rosenbrock `WithGrad` rosenbrock' `WithBounds` boundsUnconstrained prob (0,0)
result <- minimize LBFGSB def prob (-3,-4)
resultSuccess result `shouldBe` True
assertAllClose (def :: Tol Double) (resultSolution result) (1,1)
Expand Down Expand Up @@ -244,7 +244,7 @@ main = hspec $ do

context "when given a problem with bounds" $ do
it "should throw UnsupportedProblem" $ do
minimize Newton def (rosenbrock `WithGrad` rosenbrock' `WithHessian` rosenbrock'' `WithBounds` [(-4,2), (-5,2)]) (-3,-4)
minimize Newton def (rosenbrock `WithGrad` rosenbrock' `WithHessian` rosenbrock'' `WithBounds` ((-4,-5), (2,2))) (-3,-4)
`shouldThrow` (\case { UnsupportedProblem _ -> True; _ -> False })

-- https://en.wikipedia.org/wiki/Rosenbrock_function
Expand Down