diff --git a/ChangeLog.md b/ChangeLog.md index cbe3bf8..b97ef4c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,7 @@ ## Unreleased changes +- Correct fractional coefficients in pretty-printed expressions and remove trailing plus separators. ([#19](https://github.com/rasheedja/simplex-method/pull/19)) - Share shift and split coefficient-map transformations between objectives and constraints, and simplify access to their fields. ([#23](https://github.com/rasheedja/simplex-method/pull/23)) - Avoid ambiguous constraint record updates using the existing field lenses, group the substitution helpers together, and test them directly. - Use standard Data.Map selection and deletion operations for solver variable filtering. ([#20](https://github.com/rasheedja/simplex-method/pull/20)) diff --git a/src/Linear/Simplex/Prettify.hs b/src/Linear/Simplex/Prettify.hs index adb62ec..ea3e6f6 100644 --- a/src/Linear/Simplex/Prettify.hs +++ b/src/Linear/Simplex/Prettify.hs @@ -13,23 +13,24 @@ module Linear.Simplex.Prettify where import Data.Generics.Labels () +import Data.List (intercalate) import Data.Map qualified as M import Data.Ratio (denominator, numerator) import Linear.Simplex.Types (ObjectiveFunction (..), PolyConstraint (..), VarLitMapSum) -- | Convert a 'VarLitMapSum' into a human-readable 'String' prettyShowVarLitMapSum :: VarLitMapSum -> String -prettyShowVarLitMapSum = aux . M.toList +prettyShowVarLitMapSum = intercalate " + " . map prettyShowTerm . M.toList where - aux [] = "" - aux ((vName, vCoeff) : vs) = prettyShowRational vCoeff ++ " * " ++ show vName ++ " + " ++ aux vs + prettyShowTerm (vName, vCoeff) = prettyShowRational vCoeff ++ " * " ++ show vName + + prettyShowRational r = + if r < 0 then "(" ++ rendered ++ ")" else rendered where - prettyShowRational r = - if r < 0 - then "(" ++ r' ++ ")" - else r' - where - r' = if denominator r == 1 then show (numerator r) else show (numerator r) ++ " / " ++ show (numerator r) + rendered = + if denominator r == 1 + then show (numerator r) + else show (numerator r) ++ " / " ++ show (denominator r) -- | Convert a 'PolyConstraint' into a human-readable 'String' prettyShowPolyConstraint :: PolyConstraint -> String diff --git a/test/Linear/Simplex/Solver/TwoPhaseSpec.hs b/test/Linear/Simplex/Solver/TwoPhaseSpec.hs index 84debe3..561b1e7 100644 --- a/test/Linear/Simplex/Solver/TwoPhaseSpec.hs +++ b/test/Linear/Simplex/Solver/TwoPhaseSpec.hs @@ -3120,31 +3120,35 @@ spec = do prettyShowVarLitMapSum M.empty `shouldBe` "" it "shows single positive coefficient" $ do - prettyShowVarLitMapSum (M.fromList [(1, 3)]) `shouldBe` "3 * 1 + " + prettyShowVarLitMapSum (M.fromList [(1, 3)]) `shouldBe` "3 * 1" it "shows single negative coefficient with parentheses" $ do - prettyShowVarLitMapSum (M.fromList [(1, -2)]) `shouldBe` "(-2) * 1 + " + prettyShowVarLitMapSum (M.fromList [(1, -2)]) `shouldBe` "(-2) * 1" + + it "shows positive and negative fractional coefficients exactly" $ do + prettyShowVarLitMapSum (M.fromList [(1, 1 % 2), (2, (-3) % 4)]) + `shouldBe` "1 / 2 * 1 + (-3 / 4) * 2" it "shows multiple coefficients" $ do let result = prettyShowVarLitMapSum (M.fromList [(1, 2), (2, 3)]) - result `shouldBe` "2 * 1 + 3 * 2 + " + result `shouldBe` "2 * 1 + 3 * 2" describe "prettyShowPolyConstraint" $ do it "shows LEQ constraint" $ do - prettyShowPolyConstraint (LEQ (M.fromList [(1, 2)]) 10) `shouldBe` "2 * 1 + " ++ " <= " ++ show (10 :: Rational) + prettyShowPolyConstraint (LEQ (M.fromList [(1, 2)]) 10) `shouldBe` "2 * 1" ++ " <= " ++ show (10 :: Rational) it "shows GEQ constraint" $ do - prettyShowPolyConstraint (GEQ (M.fromList [(1, 1)]) 5) `shouldBe` "1 * 1 + " ++ " >= " ++ show (5 :: Rational) + prettyShowPolyConstraint (GEQ (M.fromList [(1, 1)]) 5) `shouldBe` "1 * 1" ++ " >= " ++ show (5 :: Rational) it "shows EQ constraint" $ do - prettyShowPolyConstraint (EQ (M.fromList [(1, 1)]) 3) `shouldBe` "1 * 1 + " ++ " == " ++ show (3 :: Rational) + prettyShowPolyConstraint (EQ (M.fromList [(1, 1)]) 3) `shouldBe` "1 * 1" ++ " == " ++ show (3 :: Rational) describe "prettyShowObjectiveFunction" $ do it "shows Max objective" $ do - prettyShowObjectiveFunction (Max (M.fromList [(1, 2)])) `shouldBe` "max: 2 * 1 + " + prettyShowObjectiveFunction (Max (M.fromList [(1, 2)])) `shouldBe` "max: 2 * 1" it "shows Min objective" $ do - prettyShowObjectiveFunction (Min (M.fromList [(1, 5)])) `shouldBe` "min: 5 * 1 + " + prettyShowObjectiveFunction (Min (M.fromList [(1, 5)])) `shouldBe` "min: 5 * 1" describe "twoPhaseSimplex with multiple objectives" $ do it "optimizes two objectives over the same feasible region" $ do