From 4575ca91adfabd81ecab5e83790ff3c5289daf6d Mon Sep 17 00:00:00 2001 From: Junaid Rasheed Date: Fri, 2 Oct 2026 11:23:10 +0100 Subject: [PATCH 1/2] fix(prettify): render fractions and separators correctly --- src/Linear/Simplex/Prettify.hs | 19 ++++++++++--------- test/Linear/Simplex/Solver/TwoPhaseSpec.hs | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) 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 9ca2246..f6c5673 100644 --- a/test/Linear/Simplex/Solver/TwoPhaseSpec.hs +++ b/test/Linear/Simplex/Solver/TwoPhaseSpec.hs @@ -3058,31 +3058,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 From 0819de4c7a6c46a4b304b1a8da984a2ab5f67061 Mon Sep 17 00:00:00 2001 From: Junaid Rasheed Date: Fri, 2 Oct 2026 11:46:20 +0100 Subject: [PATCH 2/2] docs(changelog): document changes in PR #19 --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index d47e0a4..4b52eb0 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)) - `twoPhaseSimplex` now takes a `VarDomainMap` as its first argument - Specify each variable's domain using smart constructors: `nonNegative`, `unbounded`, `lowerBoundOnly`, `upperBoundOnly`, or `boundedRange` - Variables not in the `VarDomainMap` are assumed to be `unbounded`