@@ -49,6 +49,7 @@ import qualified Data.Set as Set
4949import GHC.Real (Ratio )
5050import Linear.Simplex.Types
5151import Linear.Simplex.Util
52+ import qualified Control.Applicative as LPPaver
5253
5354-- | Find a feasible solution for the given system of 'PolyConstraint's by performing the first phase of the two-phase simplex method
5455-- All variables in the 'PolyConstraint' must be positive.
@@ -402,7 +403,9 @@ optimizeFeasibleSystem objFunction fsys@(FeasibleSystem {dict = phase1Dict, ..})
402403-- Variables not in the VarDomainMap are assumed to be Unbounded (no lower bound).
403404-- This function applies necessary transformations before solving and unapplies them after.
404405-- The returned Result contains variable values and objective value in the original space.
405- -- TODO: use this as twoPhaseSimplex, add instructions in CHANGELOG for old users
406+ -- TODO: we need to be able to support multiple objective functions for the LPPaver.
407+ -- one way to do this is to have a list of objective functions and optimize them one by one.
408+ -- think about cases where the opitmal result is infinity
406409twoPhaseSimplex :: (MonadIO m , MonadLogger m ) => VarDomainMap -> ObjectiveFunction -> [PolyConstraint ] -> m (Maybe Result )
407410twoPhaseSimplex domainMap objFunction constraints = do
408411 logMsg LevelInfo $
@@ -489,26 +492,40 @@ collectAllVars objFunction constraints =
489492-- Returns updated (transforms, nextFreshVar).
490493generateTransform :: M. Map Var VarDomain -> Var -> ([VarTransform ], Var ) -> ([VarTransform ], Var )
491494generateTransform domainMap var (transforms, nextFreshVar) =
492- let domain = M. findWithDefault Unbounded var domainMap
493- in case getTransform nextFreshVar var domain of
494- Nothing -> (transforms, nextFreshVar)
495- Just t@ (AddLowerBound {}) -> (t : transforms, nextFreshVar)
496- Just t@ (Shift {}) -> (t : transforms, nextFreshVar + 1 )
497- Just t@ (Split {}) -> (t : transforms, nextFreshVar + 2 )
498-
499- -- | Determine what transform (if any) is needed for a variable given its domain.
500- getTransform :: Var -> Var -> VarDomain -> Maybe VarTransform
501- getTransform nextFreshVar var domain =
502- case domain of
503- NonNegative -> Nothing
504-
505- LowerBound l
506- | l == 0 -> Nothing
507- | l > 0 -> Just $ AddLowerBound var l
508- | otherwise -> Just $ Shift var nextFreshVar l -- l < 0, need to shift
509-
510- Unbounded ->
511- Just $ Split var nextFreshVar (nextFreshVar + 1 )
495+ let domain = M. findWithDefault unbounded var domainMap
496+ (newTransforms, varOffset) = getTransform nextFreshVar var domain
497+ in (newTransforms ++ transforms, nextFreshVar + varOffset)
498+
499+ -- | Determine what transforms are needed for a variable given its domain.
500+ -- Returns a list of transforms and the number of fresh variables consumed.
501+ getTransform :: Var -> Var -> VarDomain -> ([VarTransform ], Var )
502+ getTransform nextFreshVar var (Bounded mLower mUpper) =
503+ let -- Handle lower bound
504+ (lowerTransforms, varOffset) = case mLower of
505+ Nothing -> ([] , 0 ) -- No lower bound: will need Split
506+ Just l
507+ | l == 0 -> ([] , 0 ) -- NonNegative: no transform needed
508+ | l > 0 -> ([AddLowerBound var l], 0 ) -- Positive lower bound: add constraint
509+ | otherwise -> ([Shift var nextFreshVar l], 1 ) -- Negative lower bound: shift
510+
511+ -- Handle upper bound (if present)
512+ upperTransforms = case mUpper of
513+ Nothing -> []
514+ Just u -> [AddUpperBound var u]
515+
516+ -- If no lower bound (Nothing), we need Split transformation
517+ -- Split replaces the variable, so upper bound would apply to the original var
518+ -- which gets expressed as posVar - negVar
519+ (finalTransforms, finalOffset) = case mLower of
520+ Nothing ->
521+ -- Unbounded: split the variable
522+ -- Note: upperTransforms will still be added and will apply to the original variable
523+ -- expression (posVar - negVar) via the constraint system
524+ (Split var nextFreshVar (nextFreshVar + 1 ) : upperTransforms, 2 )
525+ Just _ ->
526+ (lowerTransforms ++ upperTransforms, varOffset)
527+
528+ in (finalTransforms, finalOffset)
512529
513530-- | Apply all transforms to the objective function and constraints.
514531applyTransforms :: [VarTransform ] -> ObjectiveFunction -> [PolyConstraint ] -> (ObjectiveFunction , [PolyConstraint ])
@@ -523,6 +540,10 @@ applyTransform transform (objFunction, constraints) =
523540 AddLowerBound v bound ->
524541 (objFunction, GEQ (M. singleton v 1 ) bound : constraints)
525542
543+ -- AddUpperBound: Add a LEQ constraint for the variable
544+ AddUpperBound v bound ->
545+ (objFunction, LEQ (M. singleton v 1 ) bound : constraints)
546+
526547 -- Shift: originalVar = shiftedVar + shiftBy (where shiftBy < 0)
527548 -- Substitute: wherever we see originalVar, replace with shiftedVar
528549 -- and adjust the RHS by -coeff * shiftBy
@@ -624,6 +645,9 @@ unapplyTransform transform result@(Result {varValMap = valMap, ..}) =
624645 -- AddLowerBound: No variable substitution was done, nothing to unapply
625646 AddLowerBound {} -> result
626647
648+ -- AddUpperBound: No variable substitution was done, nothing to unapply
649+ AddUpperBound {} -> result
650+
627651 -- Shift: originalVar = shiftedVar + shiftBy
628652 -- So originalVar's value = shiftedVar's value + shiftBy
629653 Shift origVar shiftedVar shiftBy ->
0 commit comments