{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} module Math.Programming.Types where import Data.Bifunctor import Data.Traversable (fmapDefault, foldMapDefault) -- | A convient shorthand for the type of linear expressions used in a -- given model. type Expr m = LinearExpression (Numeric m) (Variable m) -- | A monad for formulating and solving linear programs. -- -- We manipulate linear programs and their settings using the -- 'Mutable' typeclass. class (Monad m, Show (Numeric m), RealFrac (Numeric m)) => LPMonad m where -- | The numeric type used in the model. type Numeric m :: * -- | The type of variables in the model. 'LPMonad' treats these as -- opaque values, but instances may expose more details. data Variable m :: * -- | The type of constraints in the model. 'LPMonad' treats these -- as opaque values, but instances may expose more details. data Constraint m :: * -- | The type of objectives in the model. 'LPMonad' treats these -- as opaque values, but instances may expose more details. data Objective m :: * -- | Create a new decision variable in the model. -- -- This variable will be initialized to be a non-negative continuous -- variable. addVariable :: m (Variable m) -- | Remove a decision variable from the model. -- -- The variable cannot be used after being deleted. removeVariable :: Variable m -> m () -- | Get the name of the variable. getVariableName :: Variable m -> m String -- | Set the name of the variable. setVariableName :: Variable m -> String -> m () -- | Get the allowed values of a variable. getVariableBounds :: Variable m -> m (Bounds (Numeric m)) -- | Constrain a variable to take on certain values. setVariableBounds :: Variable m -> Bounds (Numeric m) -> m () -- | Get the value of a variable in the current solution. getVariableValue :: Variable m -> m (Numeric m) -- | Add a constraint to the model represented by an inequality. addConstraint :: Inequality (LinearExpression (Numeric m) (Variable m)) -> m (Constraint m) -- | Remove a constraint from the model. -- -- The constraint cannot used after being deleted. removeConstraint :: Constraint m -> m () -- | Get the name of the constraint. getConstraintName :: Constraint m -> m String -- | Set the name of the constraint. setConstraintName :: Constraint m -> String -> m () -- | Get the value of the dual variable associated with the -- constraint in the current solution. -- -- This value has no meaning if the current solution is not an LP -- solution. getDualValue :: Constraint m -> m (Numeric m) -- | Add a constraint to the model represented by an inequality. addObjective :: LinearExpression (Numeric m) (Variable m) -> m (Objective m) -- | Get the name of the objective. getObjectiveName :: Objective m -> m String -- | Set the name of the objective. setObjectiveName :: Objective m -> String -> m () -- | Whether the objective is to be minimized or maximized. getObjectiveSense :: Objective m -> m Sense -- | Set whether the objective is to be minimized or maximized. setObjectiveSense :: Objective m -> Sense -> m () -- | Get the value of the objective in the current solution. getObjectiveValue :: Objective m -> m (Numeric m) -- | Get the number of seconds the solver is allowed to run before -- halting. getTimeout :: m Double -- | Set the number of seconds the solver is allowed to run before -- halting. setTimeout :: Double -> m () -- | Optimize the continuous relaxation of the model. optimizeLP :: m SolutionStatus -- | A (mixed) integer program. -- -- In addition to the methods of the 'LPMonad' class, this monad -- supports constraining variables to be either continuous or -- discrete. class ( LPMonad m ) => IPMonad m where -- | Optimize the mixed-integer program. optimizeIP :: m SolutionStatus -- | Get the domain of a variable. getVariableDomain :: Variable m -> m Domain -- | Set the domain of a variable. setVariableDomain :: Variable m -> Domain -> m () -- | Get the allowed relative gap between LP and IP solutions. getRelativeMIPGap :: m Double -- | Set the allowed relative gap between LP and IP solutions. setRelativeMIPGap :: Double -> m () -- | Whether a math program is minimizing or maximizing its objective. data Sense = Minimization | Maximization deriving ( Eq , Ord , Read , Show ) -- | The outcome of an optimization. data SolutionStatus = Optimal -- ^ An optimal solution has been found. | Feasible -- ^ A feasible solution has been found. The result may or may not -- be optimal. | Infeasible -- ^ The model has been proven to be infeasible. | Unbounded -- ^ The model has been proven to be unbounded. | Error -- ^ An error was encountered during the solve. Instance-specific -- methods should be used to determine what occurred. deriving ( Eq , Ord , Read , Show ) -- | An interval of the real numbers. data Bounds b = NonNegativeReals -- ^ The non-negative reals. | NonPositiveReals -- ^ The non-positive reals. | Interval b b -- ^ Any closed interval of the reals. | Free -- ^ Any real number. deriving ( Read , Show ) -- | The type of values that a variable can take on. -- -- Note that the @Integer@ constructor does not interfere with the -- @Integer@ type, as the @Integer@ type does not define a constuctor -- of the same name. The ambiguity is unfortunate, but other natural -- nomenclature such as @Integral@ are similarly conflicted. data Domain = Continuous -- ^ The variable lies in the real numbers | Integer -- ^ The variable lies in the integers | Binary -- ^ The variable lies in the set @{0, 1}@. deriving ( Read , Show ) class Nameable m a where getName :: a -> m String setName :: a -> String -> m () instance LPMonad m => Nameable m (Variable m) where getName = getVariableName setName = setVariableName instance LPMonad m => Nameable m (Constraint m) where getName = getConstraintName setName = setConstraintName instance LPMonad m => Nameable m (Objective m) where getName = getObjectiveName setName = setObjectiveName -- | A linear expression containing symbolic variables of type @b@ and -- numeric coefficients of type @a@. -- -- Using 'String's to denote variables and 'Double's as our numeric -- type, we could express /3 x + 2 y + 1/ as -- -- @ -- LinearExpression [(3, "x"), (2, "y")] 1 -- @ data LinearExpression a b = LinearExpression [(a, b)] a deriving ( Read , Show ) -- | Implements addition of 'LinearExpression a b' terms instance Num a => Semigroup (LinearExpression a b) where (LinearExpression termsLhs constantLhs) <> (LinearExpression termsRhs constantRhs) = LinearExpression (termsLhs <> termsRhs) (constantLhs + constantRhs) -- | Using '0' as the identity element instance Num a => Monoid (LinearExpression a b) where mempty = LinearExpression [] 0 instance Functor (LinearExpression a) where fmap = fmapDefault instance Bifunctor LinearExpression where first f (LinearExpression terms constant) = LinearExpression (fmap (first f) terms) (f constant) second f (LinearExpression terms constant) = LinearExpression (fmap (fmap f) terms) constant instance Foldable (LinearExpression a) where foldMap = foldMapDefault -- | Useful for substituting values in a monadic/applicative context instance Traversable (LinearExpression a) where traverse f (LinearExpression terms constant) = LinearExpression <$> traverse (traverse f) terms <*> pure constant -- | Non-strict inequalities. data Inequality a = Inequality Ordering a a deriving ( Read , Show ) instance Functor Inequality where fmap = fmapDefault instance Foldable Inequality where foldMap = foldMapDefault instance Traversable Inequality where traverse f (Inequality sense lhs rhs) = Inequality sense <$> f lhs <*> f rhs