Adaptation for POPL 2012.
authorMatt McCutchen <matt@mattmccutchen.net>
Tue, 12 Jul 2011 03:58:41 +0000 (23:58 -0400)
committerMatt McCutchen <matt@mattmccutchen.net>
Tue, 12 Jul 2011 03:58:41 +0000 (23:58 -0400)
12 files changed:
program/ArrayStuff.hs
program/Evaluation.hs
program/Main.hs [new file with mode: 0644]
program/Makefile
program/PMConfig.hs
program/PMDefaults.hs
program/PMInstance.hs
program/PMInstanceGenerator.hs
program/ProposalMatcher.hs
program/Test.hs
program/TestUtils.hs
program/example.in [new file with mode: 0644]

index ceb7516..df0a969 100644 (file)
@@ -15,6 +15,9 @@ array2DtoListOfLists arr =
        let ((xlo, ylo), (xhi, yhi)) = bounds arr in
        map (\x -> map (\y -> arr ! (x, y)) $ range (ylo, yhi)) $ range (xlo, xhi)
 
+listOfListsToArray2D ll =
+       listArray ((0, 0), (length ll - 1, length (head ll) - 1)) $ concat ll
+
 -- Use instead of amap when the array implementation needs to change.
 -- E.g., mapping an unboxed array to an array whose elements must be boxed.
 amap2 f arr = funcArray (bounds arr) (\i -> f (arr ! i))
index 032a1a9..201f176 100644 (file)
@@ -9,8 +9,10 @@ import ArrayStuff
 
 type MatchingEvaluation = Array Int Wt
 
+-- FIXME this is not really ported to separate preference and expertise
+
 evaluateMatching :: PMConfig -> PMInstance -> PMatching -> MatchingEvaluation
-evaluateMatching cfg inst@(PMInstance numRvrs numProps rloadA prefA) matching =
+evaluateMatching cfg inst@(PMInstance numRvrs numProps rloadA prefA _) (PMatching matching) =
        let reviewersByProposal = accumArray (flip (:)) []
                (0, numProps-1) $ map (\(i,j) -> (j,i)) matching
                :: Array Int [Int] in
diff --git a/program/Main.hs b/program/Main.hs
new file mode 100644 (file)
index 0000000..e3dbfff
--- /dev/null
@@ -0,0 +1,32 @@
+import PMInstance
+import PMDefaults
+import ProposalMatcher
+import System.IO
+import Data.Array.IArray
+import ArrayStuff
+import Text.CSV
+
+-- pretty silly but it does the job
+swapTabCommaIn s = map (\c -> if c == '\t' then ',' else if c == ',' then '\t' else c) s
+removeQuotes s = filter (\c -> not (c == '"')) s
+parseTSV fname str = case parseCSV fname (swapTabCommaIn str) of
+       Left pe -> Left pe
+       Right ll -> Right $ map (map swapTabCommaIn) ll
+printTSV ll = removeQuotes $ swapTabCommaIn $ printCSV $ map (map swapTabCommaIn) ll
+
+main = do
+       incsv <- hGetContents stdin
+       -- handle errors another day, or let the platform do it
+       let Right inll = parseTSV "standard input" incsv
+       let loadList = head inll
+       let numRvrs = length loadList
+       let loadA = listArray (0, numRvrs-1) (map read loadList)
+       let numProps = length (tail inll) `div` 2
+       -- explicit type on the next line appears to be necessary
+       let pxarr = listOfListsToArray2D (tail inll) :: Array (Int,Int) String
+       -- careful, we end up transposing the array in here
+       let prefA = funcArray ((0,0), (numRvrs-1,numProps-1)) (\(i,j) -> read $ pxarr ! (2*j, i))
+       let expA = funcArray ((0,0), (numRvrs-1,numProps-1)) (\(i,j) -> read $ pxarr ! (2*j+1, i))
+       let theInst = PMInstance numRvrs numProps loadA prefA expA
+       let PMatching theMatching = doMatching pmDefaults theInst
+       hPutStr stdout $ printTSV $ map (\(i, j) -> map show [i, j]) theMatching
index 78eb667..8dcb4aa 100644 (file)
@@ -2,8 +2,12 @@
 # -cpp: Handle GHC 6.6.1 compatibility checks.
 # -fglasgow-exts: Handle rank-2 type of RandomizedMonad, among other things.
 all:
-       ghc -cpp -fglasgow-exts --make -c *.hs
+#      ghc -cpp -fglasgow-exts --make -c *.hs
+       ghc -cpp -fglasgow-exts --make *.hs -o match
 all-optimized:
-       ghc -cpp -fglasgow-exts --make -O -hisuf O.hi -osuf O.o -c *.hs
+#      ghc -cpp -fglasgow-exts --make -O -hisuf O.hi -osuf O.o -c *.hs
+       ghc -cpp -fglasgow-exts --make -O -hisuf O.hi -osuf O.o *.hs -o match.O
 clean:
        rm -f *.hi *.o
+
+# Necessary libraries (on Fedora): ghc-fgl, ghc-csv.  Others I miss?
index d88f3d4..7e50fac 100644 (file)
@@ -12,8 +12,8 @@ import IMinCostFlow
 data PMConfig = PMConfig {
        minCostFlow :: MinCostFlowImpl,
        reviewsEachProposal :: Int,
-       prefIsExpert :: Wt -> Bool,
-       prefIsKnowledgeable :: Wt -> Bool,
+       expIsExpert :: Wt -> Bool,
+       expIsKnowledgeable :: Wt -> Bool,
        prefIsBoring :: Wt -> Bool,
        prefIsVeryBoring :: Wt -> Bool,
        prefIsConflict :: Wt -> Bool,
index 3504e06..7bb277f 100644 (file)
@@ -25,13 +25,13 @@ reviewsEachProposal = 3,
 
 -- === Interpretation of the preference values ===
 
-prefIsExpert = \p -> p <= 10,
-prefIsKnowledgeable = \p -> p <= 20,
+expIsExpert = \x -> x >= 3,
+expIsKnowledgeable = \x -> x >= 2,
 
-prefIsBoring = \p -> p > 15,
-prefIsVeryBoring = \p -> p > 25,
+prefIsBoring = \p -> p < 50,
+prefIsVeryBoring = \p -> p < 0,
 
-prefIsConflict = \p -> p >= 40,
+prefIsConflict = \p -> p <= -100,
 
 -- === Tuning parameters for the matcher ===
 
@@ -53,7 +53,7 @@ marginalVeryBoringCost = \lx -> 1000 + lx*1000,
 
 -- Cost to make a review.  Used by the evaluator too.
 -- I'm using quadratic cost functions as a first attempt.
-assignmentCost = \pref -> (widenInteger 10 + pref) ^ 2,
+assignmentCost = \pref -> (widenInteger 10 + prefNewToOld pref) ^ 2,
 
 -- Bonus for a first knowledgeable or expert review.
 knowledgeableBonus = 1000,
index 70d7529..aba03b2 100644 (file)
@@ -7,20 +7,29 @@ import Formatter
 type Wt = Double -- Can be any RealFrac.
 widenInteger x = fromInteger (toInteger x)
 
-data PMInstance = PMInstance
-       Int                 -- numReviewers
-       Int                 -- numProposals
-       (UArray Int Wt)     -- ! reviewer -> relative load
-       (UArray (Int, Int) Wt) -- ! (reviewer, proposal) -> pref
-       deriving Eq
+data PMInstance = PMInstance {
+       -- I feel like I am in C, having to namespace these!  I guess one
+       -- solution would be to have a typeclass for every field name.  A
+       -- specialized solution might desugar to that.
+       pmiNumReviewers :: Int,
+       pmiNumProposals :: Int,
+       pmiRLoad :: UArray Int Wt,
+       pmiRPPref :: UArray (Int, Int) Wt, -- preference: -100 (COI), -99 to 100
+       pmiRPExp :: UArray (Int, Int) Wt -- expertise: 0 to 3
+}
+
+-- Let us shoehorn in the new pref scale.
+prefOldToNew p' = 100 - 5 * p'
+prefNewToOld p = 20 - 0.2 * p
 
 instance Show PMInstance where
-       show (PMInstance numRvrs numProps loadA prefA) =
-               let theRvrs = [0..numRvrs-1]; theProps = [0..numProps-1] in
-               "Instance with " ++ show numRvrs ++ " reviewers and " ++ show numProps ++ " proposals:\n" ++ formatTable (
+       show pmi =
+               let theRvrs = [0..pmiNumReviewers pmi - 1]; theProps = [0..pmiNumProposals pmi - 1] in
+               "Instance with " ++ show (pmiNumReviewers pmi) ++ " reviewers and " ++ show (pmiNumProposals pmi) ++ " proposals:\n" ++ formatTable (
                            (       ""              : map (\i -> "R#" ++ show i       ) theRvrs) :
-                           (       "RLoad"         : map (\i -> show (loadA ! i)     ) theRvrs) :
-                       map (\j -> ("P#" ++ show j) : map (\i -> show (prefA ! (i, j))) theRvrs) theProps
+                           (       "RLoad"         : map (\i -> show (pmiRLoad pmi ! i)     ) theRvrs) :
+                       map (\j -> ("P#" ++ show j) : map (\i -> show (pmiRPPref pmi ! (i, j)) ++ ":" ++ show (pmiRPExp pmi ! (i, j))) theRvrs) theProps
                )
 
-type PMatching = [(Int, Int)]
+newtype PMatching = PMatching [(Int, Int)]
+       deriving Show
index 4324546..47a5ce4 100644 (file)
@@ -68,5 +68,16 @@ randomInstance cfg numRvrs numProps = do
                                PTopic1 jt1 -> expertnessToPref (iTE ! jt1)
                                PTopic2 jt1 jt2 -> (expertnessToPref (iTE ! jt1)
                                        + expertnessToPref (iTE ! jt2)) / 2
-               in if isConflict then 40 else topicPref * jD - 4)
-       return $ PMInstance numRvrs numProps loadA prefA
+               -- Use a formula designed for the old pref scale with the new.
+               in if isConflict then -100 else prefOldToNew (topicPref * jD - 4))
+       let expA = funcArray ((0, 0), (numRvrs-1, numProps-1)) (\(i,j) ->
+               let
+                       ReviewerInfo iTE iC = reviewerInfos ! i
+                       ProposalInfo jT jD = proposalInfos ! j
+                       isConflict = elem j iC
+                       topicExp = case jT of
+                               PTopic1 jt1 -> 1 + (iTE ! jt1)
+                               PTopic2 jt1 jt2 -> 1 + ((iTE ! jt1)
+                                       + (iTE ! jt2)) / 2
+               in topicExp)
+       return $ PMInstance numRvrs numProps loadA prefA expA
index d435d67..1047c4a 100644 (file)
@@ -9,8 +9,8 @@ import PMConfig
 
 prefBoringness cfg p = if prefIsVeryBoring cfg p then 2
        else if prefIsBoring cfg p then 1 else 0
-prefExpertness cfg p = if prefIsExpert cfg p then 2
-       else if prefIsKnowledgeable cfg p then 1 else 0
+expExpertness cfg x = if expIsExpert cfg x then 2
+       else if expIsKnowledgeable cfg x then 1 else 0
 
 data REdge = REdge {
        reIdx  :: Int,
@@ -42,7 +42,7 @@ indexEdges i ((v1, v2, re):es) =
        (imax, (v1, v2, re{ reIdx = i }) : ies)
 
 doReduction :: PMConfig -> PMInstance -> ReductionResult
-doReduction cfg (PMInstance numRvrs numProps rloadA prefA) =
+doReduction cfg (PMInstance numRvrs numProps rloadA prefA expA) =
        let
                source = 0
                sink = 1
@@ -76,11 +76,12 @@ doReduction cfg (PMInstance numRvrs numProps rloadA prefA) =
                        i <- [0 .. numRvrs - 1]
                        j <- [0 .. numProps - 1]
                        let pref = prefA ! (i, j)
+                       let xp = expA ! (i, j)
                        -- We must generate an edge even if there is a conflict
                        -- of interest; otherwise we'll fail to read its flow
                        -- value in doMatching.
                        [(rvrNode i (prefBoringness cfg pref),
-                               propNode j (prefExpertness cfg pref),
+                               propNode j (expExpertness cfg xp),
                                REdge (edIdx (i, j))
                                        (if prefIsConflict cfg pref then 0 else 1)
                                        (assignmentCost cfg pref))]
@@ -102,7 +103,7 @@ doReduction cfg (PMInstance numRvrs numProps rloadA prefA) =
 
 -- Returns a list of reviews as ordered pairs (reviewer#, proposal#).
 doMatching :: PMConfig -> PMInstance -> PMatching
-doMatching cfg inst@(PMInstance numRvrs numProps _ _) =
+doMatching cfg inst@(PMInstance numRvrs numProps _ _ _) =
        let ReductionResult graph source sink idxBounds edIdx = doReduction cfg inst in
        let flowArray = minCostFlow cfg idxBounds reIdx reCap reCost graph (source, sink) in
        let pairs = do
@@ -112,4 +113,4 @@ doMatching cfg inst@(PMInstance numRvrs numProps _ _) =
                        then [(i, j)]
                        else []
                in
-       sort pairs -- for prettiness
+       PMatching (sort pairs) -- for prettiness
index d0bd6e2..6dc2ecc 100644 (file)
@@ -64,13 +64,21 @@ myPrefsArray = array ((0,0), (myNumRvrs-1,myNumProps-1)) [
 
 (myNumRvrs, myNumProps) = (5, 3)
 
-myPrefs = transposeArray $ listArray ((0,0), (myNumProps-1,myNumRvrs-1)) [
+myPrefs = transposeArray $ listArray ((0,0), (myNumProps-1,myNumRvrs-1)) $
+       map prefOldToNew [
        15, 10, 15, 40, 20,
        30,  7, 10, 15, 15,
        15, 25, 20, 20, 15
        ] :: UArray (Int, Int) Wt
 
-myInst = PMInstance myNumRvrs myNumProps (constArray (0, myNumRvrs-1) 1) myPrefs
+-- This data is pretty made-up... ~ 2011-07-11
+myExps = transposeArray $ listArray ((0,0), (myNumProps-1,myNumRvrs-1)) [
+        2,  2,  1,  3,  1,
+        0,  3,  2,  2,  2,
+        2,  1,  1,  2,  2
+       ] :: UArray (Int, Int) Wt
+
+myInst = PMInstance myNumRvrs myNumProps (constArray (0, myNumRvrs-1) 1) myPrefs myExps
 
 rdnResult = doReduction pmDefaults myInst
 ReductionResult rrg rrso rrsi rreib rredi = rdnResult
index 96a170c..0259630 100644 (file)
@@ -34,7 +34,7 @@ flowAnnotate g fa =
                (n1, n2, REdgeF i ca (fa ! i) co)) $ labEdges g) :: Gr () REdgeF
 
 showInstanceAsGraph :: PMInstance -> PMatching -> Gr String String
-showInstanceAsGraph (PMInstance numRvrs numProps rloadA prefA) matchedPairs =
+showInstanceAsGraph (PMInstance numRvrs numProps rloadA prefA expA) (PMatching matchedPairs) =
        let
                rvrNode i = i
                propNode j = numRvrs + j
@@ -48,7 +48,7 @@ showInstanceAsGraph (PMInstance numRvrs numProps rloadA prefA) matchedPairs =
                        i <- [0..numRvrs-1]
                        j <- [0..numProps-1]
                        return (rvrNode i, propNode j,
-                               parenthesizeIf (elem (i, j) matchedPairs) $ show (prefA ! (i, j)))
+                               parenthesizeIf (elem (i, j) matchedPairs) $ show (prefA ! (i, j)) ++ ":" ++ show (expA ! (i, j)))
        in mkGraph theNodes theEdges
 
 goFile :: String -> IO ()
@@ -91,14 +91,14 @@ l1 /\ l2 = (l1 \\ l2, l2 \\ l1)
 runEvaluation cfg nr np = do
        let inst = runRandom myGen $ randomInstance cfg nr np
        putStr (show inst ++ "\n")
-       let m0 = doMatching cfg{loadTolerance = 0} inst
+       let PMatching m0 = doMatching cfg{loadTolerance = 0} inst
        putStr ("Matching with load tolerance 0:\n" ++ show m0 ++ "\n")
-       let m1 = doMatching cfg{loadTolerance = 1} inst
+       let PMatching m1 = doMatching cfg{loadTolerance = 1} inst
        putStr ("Matching with load tolerance 1:\n" ++ show m1 ++ "\n")
        putStr ("Differences:\n" ++ show (m0 /\ m1) ++ "\n")
-       let e0 = evaluateMatching cfg{loadTolerance = 0} inst m0
+       let e0 = evaluateMatching cfg{loadTolerance = 0} inst (PMatching m0)
        putStr ("Evaluation of first matching:\n" ++ show e0 ++ "\n")
-       let e1 = evaluateMatching cfg{loadTolerance = 1} inst m1
+       let e1 = evaluateMatching cfg{loadTolerance = 1} inst (PMatching m1)
        putStr ("Evaluation of second matching:\n" ++ show e1 ++ "\n")
        putStr ("Evaluation differences:\n" ++
                show (sortedDiffEvaluations e0 e1) ++ "\n")
diff --git a/program/example.in b/program/example.in
new file mode 100644 (file)
index 0000000..5d46307
--- /dev/null
@@ -0,0 +1,7 @@
+1      1       1       1       1
+25     50      25      -100    0
+2      2       1       3       1
+-50    65      50      25      25
+0      3       2       2       2
+25     -25     0       0       25
+2      1       1       2       2